<?php
if (!function_exists('easter_date')) {
/**
* @see https://github.com/steinger/easter-date/blob/master/easter.php
* @return int unix timestamp of the day of easter
*/
function easter_date(int $year): int {
$J = date('Y', mktime(0, 0, 0, 1, 1, $year));
$K = floor( $J/ 100 );
$M = 15 + floor(( 3*$K+3 ) / 4 ) - floor(( 8*$K+13 ) / 25 );
$S = 2 - floor(( 3*$K+3 ) / 4 );
$A = $J%19;
$D = ( 19 * $A + $M) % 30;
$R = floor( $D / 29 ) + ( floor( $D / 28 ) - floor( $D / 29 )) * floor( $A / 11 );
$OG = 21 + $D - $R;
$SZ = 7 - ( ($J + floor( $J / 4 ) + $S ) % 7 );
$OE = 7 - ( ($OG - $SZ) % 7 );
$OS = $OG + $OE;
return mktime(0,0,0,3,$OS,$J);
}
}
//observed closures
$closures = [
'2017' => [
'2017-08-28',
'2017-08-29',
'2017-08-30',
'2017-08-31',
'2017-09-01'
]
];
file_put_contents(Holiday::CLOSURES_FILE, '<?php return ' . var_export($closures, true) . ';');
class Holiday
{
public const CLOSURES_FILE = '/tmp/closures.php';
private static $closuresLoaded = false;
/**
* date format = N (1 = Monday, ...)
* @var array
*/
private static $workingDays = [1, 2, 3, 4, 5];
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')]));
}
public static function isWorkDay(\DateTimeInterface $date): bool
{
if (!\in_array($date->format('N'), self::$workingDays)) {
return false;
}
if (self::isHoliday($date)) {
return false;
}
return true;
}
/**
* Count number of weekdays within a given date span, excluding holidays
*
* @param \DateTimeInterface $from
* @param \DateTimeInterface $to
* @return int
* @throws Exception
*/
public static function countWeekDays(\DateTimeInterface $from, \DateTimeInterface $to = null)
{
if (is_null($to)) {
return null;
}
// from stackoverflow:
// http://stackoverflow.com/questions/336127/calculate-business-days#19221403
$from = clone $from;
$from->setTime(0, 0, 0);
$to = clone $to;
$to->setTime(0, 0, 0);
$interval = new DateInterval('P1D');
$to->add($interval);
$period = new DatePeriod($from, $interval, $to);
$days = 0;
/** @var DateTime $date */
foreach ($period as $date) {
if (self::isWorkDay($date)) {
$days++;
}
}
return $days;
}
/**
* Return count of weekdays in given month
*
* @param int $month
* @param int $year
* @return int
* @throws Exception
*/
public static function countWeekDaysInMonthToDate(int $month, int $year): int
{
$today = new \DateTimeImmutable();
$d1 = $today->setDate($year, $month, 1);
$d2 = $d1->modify('last day of this month');
return self::countWeekDays($d1, min($d2, $today));
}
/**
* @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 $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 ($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');
}
}
//optionally do not use include file
//return self::$observableHolidays;
$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');
$holidays = Holiday::getHolidays($date);
var_export($holidays);
echo \PHP_EOL;
var_dump(Holiday::isHoliday($date));
preferences:
34.13 ms | 416 KiB | 5 Q