118 lines
4.0 KiB
PHP
118 lines
4.0 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Czech Holidays & Work Fund Calculator
|
||
*
|
||
* Provides Czech public holidays (including movable Easter dates)
|
||
* and monthly work fund calculations.
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
class CzechHolidays
|
||
{
|
||
/** @var array<int, list<string>> Static cache for holidays by year */
|
||
private static array $holidayCache = [];
|
||
|
||
/**
|
||
* Get all Czech public holidays for a given year.
|
||
* Returns array of 'Y-m-d' strings (11 fixed + 2 Easter-based).
|
||
* Results are cached per-request to avoid recalculation.
|
||
*
|
||
* @return list<string>
|
||
*/
|
||
public static function getHolidays(int $year): array
|
||
{
|
||
if (isset(self::$holidayCache[$year])) {
|
||
return self::$holidayCache[$year];
|
||
}
|
||
// Fixed holidays
|
||
$holidays = [
|
||
sprintf('%04d-01-01', $year), // Den obnovy samostatného českého státu
|
||
sprintf('%04d-05-01', $year), // Svátek práce
|
||
sprintf('%04d-05-08', $year), // Den vítězství
|
||
sprintf('%04d-07-05', $year), // Den slovanských věrozvěstů Cyrila a Metoděje
|
||
sprintf('%04d-07-06', $year), // Den upálení mistra Jana Husa
|
||
sprintf('%04d-09-28', $year), // Den české státnosti
|
||
sprintf('%04d-10-28', $year), // Den vzniku samostatného československého státu
|
||
sprintf('%04d-11-17', $year), // Den boje za svobodu a demokracii
|
||
sprintf('%04d-12-24', $year), // Štědrý den
|
||
sprintf('%04d-12-25', $year), // 1. svátek vánoční
|
||
sprintf('%04d-12-26', $year), // 2. svátek vánoční
|
||
];
|
||
|
||
// Easter-based holidays (Anonymous Gregorian algorithm)
|
||
$easterSunday = self::getEasterSunday($year);
|
||
$goodFriday = date('Y-m-d', strtotime($easterSunday . ' -2 days'));
|
||
$easterMonday = date('Y-m-d', strtotime($easterSunday . ' +1 day'));
|
||
$holidays[] = $goodFriday; // Velký pátek
|
||
$holidays[] = $easterMonday; // Velikonoční pondělí
|
||
|
||
sort($holidays);
|
||
self::$holidayCache[$year] = $holidays;
|
||
return $holidays;
|
||
}
|
||
|
||
/**
|
||
* Check if a date is a Czech public holiday.
|
||
*/
|
||
public static function isHoliday(string $date): bool
|
||
{
|
||
$year = (int)date('Y', strtotime($date));
|
||
$formatted = date('Y-m-d', strtotime($date));
|
||
return in_array($formatted, self::getHolidays($year), true);
|
||
}
|
||
|
||
/**
|
||
* Get number of business days (Mon-Fri, excluding holidays) in a month.
|
||
*/
|
||
public static function getBusinessDaysInMonth(int $year, int $month): int
|
||
{
|
||
$holidays = self::getHolidays($year);
|
||
$daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year);
|
||
$businessDays = 0;
|
||
|
||
for ($day = 1; $day <= $daysInMonth; $day++) {
|
||
$date = sprintf('%04d-%02d-%02d', $year, $month, $day);
|
||
$dayOfWeek = (int)date('N', strtotime($date)); // 1=Mon, 7=Sun
|
||
if ($dayOfWeek <= 5 && !in_array($date, $holidays, true)) {
|
||
$businessDays++;
|
||
}
|
||
}
|
||
|
||
return $businessDays;
|
||
}
|
||
|
||
/**
|
||
* Get monthly work fund in hours (business days × 8).
|
||
*/
|
||
public static function getMonthlyWorkFund(int $year, int $month): float
|
||
{
|
||
return self::getBusinessDaysInMonth($year, $month) * 8.0;
|
||
}
|
||
|
||
/**
|
||
* Calculate Easter Sunday date using the Anonymous Gregorian algorithm.
|
||
* Returns 'Y-m-d' string.
|
||
*/
|
||
private static function getEasterSunday(int $year): string
|
||
{
|
||
$a = $year % 19;
|
||
$b = intdiv($year, 100);
|
||
$c = $year % 100;
|
||
$d = intdiv($b, 4);
|
||
$e = $b % 4;
|
||
$f = intdiv($b + 8, 25);
|
||
$g = intdiv($b - $f + 1, 3);
|
||
$h = (19 * $a + $b - $d - $g + 15) % 30;
|
||
$i = intdiv($c, 4);
|
||
$k = $c % 4;
|
||
$l = (32 + 2 * $e + 2 * $i - $h - $k) % 7;
|
||
$m = intdiv($a + 11 * $h + 22 * $l, 451);
|
||
$month = intdiv($h + $l - 7 * $m + 114, 31);
|
||
$day = (($h + $l - 7 * $m + 114) % 31) + 1;
|
||
|
||
return sprintf('%04d-%02d-%02d', $year, $month, $day);
|
||
}
|
||
}
|