I have a working symfony project that includes a Holiday
class with several grouped static functions around counting non-holiday weekdays. The heart of the code was taken from several answers in this thread.
I use the static methods all around my code-base, including twig extensions, reports, chart creation, etc.
The biggest downside to these is that I periodically have to go into these methods and manually key in Holidays for the coming year (yes, I know I could do several years all at once), but there are occasional "holidays" that I add for things like off-days due to bad weather. Manually adding a holiday and pushing the code to my git repository just feels "wrong", or at least not elegant.
The solution would seem to have holidays into a database and never touch the code again. This means working with doctrine, but this means some dependency injection to get an entity manager to my static methods, and once again this does not feel so elegant.
So what's the solution??
Here's my Holiday
class, although the code here works perfectly for my needs. I'm looking for an elegant way to move the $holidays
array to a database.
<?php
namespace App\Controller;
use DateInterval;
use DatePeriod;
use DateTime;
use Exception;
class Holiday
{
private static $workingDays = [1, 2, 3, 4, 5]; # date format = N (1 = Monday, ...)
/**
* Is submitted date a holiday?
*
* @param DateTime $date
* @return bool
*/
public static function isHoliday(DateTime $date): bool
{
$holidays = self::getHolidays();
return
in_array($date->format('Y-m-d'), $holidays) ||
in_array($date->format('*-m-d'), $holidays);
}
public static function isWorkDay(DateTime $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 DateTime $from
* @param DateTime $to
* @return int
* @throws Exception
*/
public static function countWeekDays(DateTime $from, DateTime $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
{
$d1 = new DateTime($year . '-' . $month . '-01');
$d2 = new DateTime($d1->format('Y-m-t'));
$today = new DateTime();
return self::countWeekDays($d1, min($d2, $today));
}
/**
* Returns an array of strings representing holidays in format 'Y-m-d'
*
* @return array
*/
private static function getHolidays(): array
{
// TODO: Move holidays to database (?)
$holidays = ['*-12-25', '*-01-01', '*-07-04',
'2017-04-14', # Good Friday
'2017-05-29', # Memorial day
'2017-08-28', # Hurricane Harvey closure
'2017-08-29', # Hurricane Harvey closure
'2017-08-30', # Hurricane Harvey closure
'2017-08-31', # Hurricane Harvey closure
'2017-09-01', # Hurricane Harvey closure
'2017-09-04', # Labor day
'2017-11-23', # Thanksgiving
'2017-11-24', # Thanksgiving
#'2018-03-30', # Good Friday
'2018-05-28', # Memorial day
'2018-09-03', # Labor day
'2018-11-22', # Thanksgiving
'2018-11-23', # Thanksgiving
'2018-12-24', # Christmas Eve
'2018-12-31', # New Year's Eve
'2019-04-19', # Good Friday
'2019-05-27', # Memorial day
'2019-09-02', # Labor day
'2019-11-28', # Thanksgiving
'2019-11-29', # Thanksgiving
]; # variable and fixed holidays
return $holidays;
}
}