Согласно моему предложению использовать относительные форматы для заметных праздников.Я добавил возможность использовать списки и периоды дат для более сложных дат.Я также включил пасху как праздник в качестве варианта использования, используя функцию easter_date
из расширения calendar
PHP.
Я также предлагаю сегментировать даты по годам и сохранять их встатическое свойство, чтобы определить, были ли они уже загружены, чтобы предотвратить их повторную загрузку на нескольких итерациях.
(Демонстрирует использование файла)
class Holiday
{
/**
* holidays in [YYYY => [YYYY-MM-DD]] format
* array|string[][]
*/
private static $observableHolidays = [];
//....
/**
* Is submitted date a holiday?
*
* @param DateTime $date
* @return bool
*/
public static function isHoliday(\DateTimeInterface $date): bool
{
$holidays = self::getHolidays($date);
return \array_key_exists($date->format('Y-m-d'), array_flip($holidays[$date->format('Y')]));
}
/**
* @see https://www.php.net/manual/en/function.easter-date.php
* @param \DateTimeInterface $date
* @return \DateTimeImmutable
*/
private static function getEaster(\DateTimeInterface $date): \DateTimeImmutable
{
return (new \DateTimeImmutable())->setTimestamp(\easter_date($date->format('Y')));
}
/**
* Returns an array of strings representing holidays in format 'Y-m-d'
*
* @return array|string[][]
*/
public static function getHolidays(\DateTimeInterface $start): array
{
//static prevents redeclaring the variable
static $relativeHolidays = [
'new years day' => 'first day of January',
'easter' => [__CLASS__, 'getEaster'],
'memorial day' => 'second Monday of May',
'independence day' => 'July 4th',
'labor day' => 'first Monday of September',
'thanksgiving' => 'fourth Thursday of November',
'black friday' => 'fourth Thursday of November + 1 day',
'christmas' => 'December 25th',
'new years eve' => 'last day of December',
//... add others like Veterans Day, MLK, Columbus Day, etc
];
if (!$start instanceof \DateTimeImmutable) {
//force using DateTimeImmutable
$start = \DateTimeImmutable::createFromMutable($start);
}
//build the holidays to the specified year
$start = $start->modify('first day of this year');
//always generate an entire years worth of holidays
$period = new \DatePeriod($start, new \DateInterval('P1Y'), 0);
foreach ($period as $date) {
$year = $date->format('Y');
if (array_key_exists($year, self::$observableHolidays)) {
continue;
}
self::$observableHolidays[$year] = [];
foreach (self::$relativeHolidays as $relativeHoliday) {
if (\is_callable($relativeHoliday)) {
$holidayDate = $relativeHoliday($date);
} elseif (0 === \strpos($relativeHoliday, 'P')) {
$holidayDate = $date->add(new \DateInterval($relativeHoliday));
} else {
$holidayDate = $date->modify($relativeHoliday);
}
self::$observableHolidays[$year][] = $holidayDate->format('Y-m-d');
}
}
return self::$observableHolidays;
}
}
$holidays = Holiday::getHolidays(new \DateTime('2017-08-28'));
Результаты:
array (
2017 =>
array (
0 => '2017-01-01',
1 => '2017-04-16',
2 => '2017-05-08',
3 => '2017-07-04',
4 => '2017-09-04',
5 => '2017-11-23',
6 => '2017-11-24',
7 => '2017-12-25',
8 => '2017-12-31',
),
)
Конкретные даты закрытия для вашей организации, которые нельзя сохранить в относительном формате, желательнохранится в РСУБД, такой как MySQL или SQLite.Которые затем могут быть восстановлены после стандартных наблюдаемых праздников, когда эти годы встречаются.
Однако, при отсутствии базы данных, вы можете использовать файл включения, очень похожий на то, что Symfony использует в своих декларациях службы контейнеров, что также выгодно при использовании OPcache
.В качестве альтернативы вы можете использовать компонент Symfony Serializer для генерации, загрузки и сохранения относительных данных в нужном вам формате (JSON, CSV, XML, YAML).
Вот примериспользования словаря включаемых файлов с вашим текущим классом.
class Holiday
{
public const CLOSURES_FILE = '/tmp/closures.php';
//...
public static function getHolidays(\DateTimeInterface $date): array
{
//...
$holidays = self::$observableHolidays;
if (\is_file(self::CLOSURES_FILE)) {
foreach (include self::CLOSURES_FILE as $year => $dates) {
if (!\array_key_exists($year, $holidays)) {
$holidays[$year] = [];
}
$holidays[$year] = array_merge($holidays[$year], $dates);
}
}
return $holidays;
}
}
$date = new \DateTime('2017-08-28');
var_export(Holiday::getHolidays($date));
var_dump(Holiday::isHoliday($date));
Результаты:
array (
2017 =>
array (
0 => '2017-01-01',
1 => '2017-04-16',
2 => '2017-05-08',
3 => '2017-07-04',
4 => '2017-09-04',
5 => '2017-11-23',
6 => '2017-11-24',
7 => '2017-12-25',
8 => '2017-12-31',
9 => '2017-08-28',
10 => '2017-08-29',
11 => '2017-08-30',
12 => '2017-08-31',
13 => '2017-09-01',
),
)
bool(true)
Чтобы сохранить словарь включаемых файлов, вы можете загрузить и сохранить этизначения в вашем приложении, как вам нравится, например, с помощью простой формы $_POST
значения.
$dictionary_array = ['year' => ['date1', 'date2']];
file_put_contents(Holiday::CLOSURES_FILE, '<?php return ' . var_export($dictionary_array) . ';');
Поскольку ваше приложение управляется версией, используйте или установите желаемый путь к файлу словаря, который игнорируется в вашем приложении.Файл .gitignore
, такой как var/holidays/holiday_dates.php
.
В моем проекте Symfony я добавляю известные относительные и не относительные значения даты в качестве параметров из конфигов к моему праздникусервис, такой как 2001-09-11
.Затем я использую службу запросов для вставки неизвестных не относительных дат из базы данных в класс Holiday, таких как закрытие вашего урагана.Сервис My Holiday используется во всем приложении только с использованием DI, в том числе внутри функций Twig, в отличие от вызовов метода static
, но это можно сделать с помощью CompilerPass в статическом методе для установкидаты.
Так как вы используете статические методы для прямого доступа к результатам, для любого сервиса, использующего статический метод Holiday, потребуется серьезный рефакторинг в пользу его использования в качестве сервиса Symfony.