You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
379 lines
14 KiB
PHP
379 lines
14 KiB
PHP
<?php
|
|
|
|
namespace WebSite;
|
|
|
|
use DateInterval;
|
|
use DateTime;
|
|
use DateTimeImmutable;
|
|
use DateTimeInterface;
|
|
use DateTimeZone;
|
|
use Eluceo\iCal\Domain\Entity\Calendar;
|
|
use Eluceo\iCal\Domain\Entity\Event;
|
|
use Eluceo\iCal\Domain\Entity\TimeZone;
|
|
use Eluceo\iCal\Domain\ValueObject\Date;
|
|
use Eluceo\iCal\Domain\ValueObject\SingleDay;
|
|
use Eluceo\iCal\Domain\ValueObject\UniqueIdentifier;
|
|
use Eluceo\iCal\Presentation\Factory\CalendarFactory;
|
|
use Exception;
|
|
use JetBrains\PhpStorm\NoReturn;
|
|
use Throwable;
|
|
use UnexpectedValueException;
|
|
|
|
/**
|
|
* A calendar for public holidays
|
|
*/
|
|
class PublicHolidayCalendar {
|
|
/**
|
|
* Generate the calendar
|
|
*
|
|
* Arguments :
|
|
* - YearStart: int The start year of the calendar (with century). Default : current year
|
|
* - YearNumber: int The number of year fo the calendar. Default : 5
|
|
* - WeeksNumbers: bool (0/1) Include weeks number ? Default : 0
|
|
* - debug: bool(0/1)
|
|
*
|
|
* All times are UTC
|
|
*
|
|
* @return void
|
|
*/
|
|
#[NoReturn] public function proceed (): void {
|
|
$debug = !isset($_GET['debug']) || $_GET['debug'] == '' ? 0 : $_GET['debug'];
|
|
if (preg_match('#^\s*(?<bool>[01])?\s*$#i', $debug, $match) !== 1) {
|
|
$debug = false;
|
|
}
|
|
else {
|
|
$debug = isset($match['bool']) && $match['bool'];
|
|
}
|
|
|
|
try {
|
|
//region Extract arguments
|
|
//region YearStart
|
|
$yearStart = !isset($_GET['YearStart']) || $_GET['YearStart'] == '' ? (new DateTimeImmutable('now'))->format('Y') : $_GET['YearStart'];
|
|
if (!is_numeric($yearStart)) {
|
|
throw new UnexpectedValueException('The "YearStart" argument must be an integer');
|
|
}
|
|
$yearStart = intval($yearStart);
|
|
//endregion
|
|
//region YearNumber
|
|
$yearNumber = !isset($_GET['YearNumber']) || $_GET['YearNumber'] == '' ? 5 : $_GET['YearNumber'];
|
|
if (!is_numeric($yearNumber)) {
|
|
throw new UnexpectedValueException('The "YearNumber" argument must be a strictly positive integer');
|
|
}
|
|
$yearNumber = intval($yearNumber);
|
|
if ($yearNumber < 0) {
|
|
throw new UnexpectedValueException('The "YearNumber" argument must be a strictly positive integer');
|
|
}
|
|
//endregion
|
|
//region WeeksNumbers
|
|
$weeksNumbers = !isset($_GET['WeeksNumbers']) || $_GET['WeeksNumbers'] == '' ? 0 : $_GET['WeeksNumbers'];
|
|
if (preg_match('#^\s*(?<bool>[01])?\s*$#i', $weeksNumbers, $match) !== 1) {
|
|
throw new UnexpectedValueException('The "WeeksNumber" argument must be a boolean (0 or 1)');
|
|
}
|
|
$weeksNumbers = isset($match['bool']) && $match['bool'];
|
|
//endregion
|
|
//endregion
|
|
|
|
//region Create the calendar
|
|
$calendar = new Calendar();
|
|
$calendar->addTimeZone(TimeZone::createFromPhpDateTimeZone(new DateTimeZone('UTC')));
|
|
|
|
$yearEnd = $yearStart + $yearNumber;
|
|
for ($yearCurrent = $yearStart; $yearCurrent <= $yearEnd; $yearCurrent++) {
|
|
$events = $this->createEvents($yearCurrent, $weeksNumbers);
|
|
foreach ($events as $event) {
|
|
$calendar->addEvent($event);
|
|
}
|
|
}
|
|
//endregion
|
|
|
|
//region Export to iCal
|
|
$icalFactory = new CalendarFactory();
|
|
$icalContent = (string)$icalFactory->createCalendar($calendar);
|
|
|
|
if ($debug) {
|
|
header('Content-Type: text/plain; charset=UTF-8', true, 200);
|
|
}
|
|
else {
|
|
header('Content-Type: text/calendar; charset=UTF-8', true, 200);
|
|
header('Content-Length: ' . strlen($icalContent)); // Don't use mb_strlen because we NEED the number of bytes
|
|
header('Content-Disposition: attachment; filename=PublicHolidayCalendar.ics');
|
|
}
|
|
echo $icalContent;
|
|
exit(0);
|
|
//endregion
|
|
}
|
|
catch (Throwable $e) {
|
|
header('Content-Type: text/plain; charset=UTF-8', true, 500);
|
|
echo 'Exception [' . get_class($e) . ']: ' . $e->getMessage() . PHP_EOL;
|
|
if ($debug) {
|
|
echo $e->getTraceAsString() . PHP_EOL;
|
|
}
|
|
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create all events of a year
|
|
*
|
|
* @param int $year The year
|
|
* @param bool $weeksNumbers Include weeks numbers ?
|
|
*
|
|
* @return Event[] The list of events
|
|
*/
|
|
private function createEvents (int $year, bool $weeksNumbers): array {
|
|
$events = array_merge(
|
|
$this->createStaticPublicHolidays($year),
|
|
$this->createVariablePublicHolidays($year),
|
|
$this->createNameDays($year),
|
|
);
|
|
|
|
if ($weeksNumbers) {
|
|
$events = array_merge(
|
|
$events,
|
|
$this->createWeeksNumbers($year)
|
|
);
|
|
}
|
|
|
|
return $events;
|
|
}
|
|
/**
|
|
* Create static public holidays
|
|
*
|
|
* @param int $year The year
|
|
*
|
|
* @return Event[] The list of events
|
|
*/
|
|
private function createStaticPublicHolidays (int $year): array {
|
|
return [
|
|
static::createEvent('Jour de l\'an', '1er janvier', true, static::createDate($year, 1, 1)),
|
|
static::createEvent('Fête du travail', '1er mai', true, static::createDate($year, 5, 1)),
|
|
static::createEvent('Armistice 1945', '8 mai', true, static::createDate($year, 5, 8)),
|
|
static::createEvent('Prise de la bastille', '14 juillet', true, static::createDate($year, 7, 14)),
|
|
static::createEvent('Assomption de Marie', '15 août', true, static::createDate($year, 8, 15)),
|
|
static::createEvent('Toussaint', '1er novembre', true, static::createDate($year, 11, 1)),
|
|
static::createEvent('Armistice 1918', '11 novembre', true, static::createDate($year, 11, 11)),
|
|
static::createEvent('Noël', '25 décembre', true, static::createDate($year, 12, 25)),
|
|
];
|
|
}
|
|
/**
|
|
* Create variable public holidays
|
|
*
|
|
* @param int $year The year
|
|
*
|
|
* @return Event[] The list of events
|
|
*/
|
|
private function createVariablePublicHolidays (int $year): array {
|
|
$paquesDate = static::calculatePaques($year);
|
|
return [
|
|
static::createEvent('Pâques', 'Lundi de Pâques', true, $paquesDate),
|
|
static::createEvent('Ascension', 'Jeudi de l\'Ascension', true, $paquesDate->add(new DateInterval('P39D'))),
|
|
static::createEvent('Pentecôte', 'Lundi de Pentecôte', true, $paquesDate->add(new DateInterval('P50D'))),
|
|
];
|
|
}
|
|
/**
|
|
* Create name days
|
|
*
|
|
* @param int $year The year
|
|
*
|
|
* @return Event[] The list of events
|
|
*/
|
|
private function createNameDays (int $year): array {
|
|
return [
|
|
static::createEvent('Fête des mères', 'Fête des mères', false, static::calculateMotherDay($year)),
|
|
static::createEvent('Fête des pères', 'Fête des pères', false, static::calculateFatherDay($year)),
|
|
static::createEvent('Fête des grand-mères', 'Fête des grand-mères', false, static::calculateGrandMotherDay($year)),
|
|
static::createEvent('Fête des grand-pères', 'Fête des grand-pères', false, static::calculateGrandFatherDay($year)),
|
|
];
|
|
}
|
|
/**
|
|
* Create weeks numbers
|
|
*
|
|
* @param int $year The year
|
|
*
|
|
* @return Event[] The list of events
|
|
*/
|
|
private function createWeeksNumbers (int $year): array {
|
|
$lastDay = static::createDate($year, 12, 31);
|
|
|
|
$currentDay = static::createDate($year, 1, 1);
|
|
$currentDayOfWeek = (int)$currentDay->format('N');
|
|
if ($currentDayOfWeek > 1) {
|
|
try {
|
|
$currentDay = $currentDay->add(new DateInterval('P' . (8 - $currentDayOfWeek) . 'D'));
|
|
}
|
|
catch (Exception) {
|
|
}
|
|
}
|
|
|
|
$events = [];
|
|
while ($currentDay < $lastDay) {
|
|
$events[] = static::createEvent(
|
|
'Semaine ' . (count($events) + 1),
|
|
'Semaine ' . (count($events) + 1),
|
|
false,
|
|
$currentDay
|
|
);
|
|
$currentDay = $currentDay->add(new DateInterval('P7D'));
|
|
}
|
|
return $events;
|
|
}
|
|
|
|
/**
|
|
* Calculate the date of "Pâques"
|
|
*
|
|
* @param int $year The year
|
|
*
|
|
* @return DateTimeImmutable The of "Pâques"
|
|
*/
|
|
private static function calculatePaques (int $year): DateTimeImmutable {
|
|
//https://fr.wikipedia.org/wiki/Calcul_de_la_date_de_P%C3%A2ques
|
|
$metonCycle = $year % 19;
|
|
$yearCentury = intdiv($year, 100);
|
|
$yearRank = $year % 100;
|
|
$bissextileCentury = intdiv($yearCentury, 4);
|
|
$bissextileOffset = $yearCentury % 4;
|
|
$proemptoseCycle = intdiv(($yearCentury + 8), 25);
|
|
$proemptose = intdiv($yearCentury - $proemptoseCycle + 1, 3);
|
|
$epacte = (19 * $metonCycle + $yearCentury - $bissextileCentury - $proemptose + 15) % 30;
|
|
$bissextileRankCentury = intdiv($yearRank, 4);
|
|
$bissextileRankOffset = $yearRank % 4;
|
|
$dominicalLetter = (2 * $bissextileOffset + 2 * $bissextileRankCentury - $epacte - $bissextileRankOffset + 32) % 7;
|
|
$correction = intdiv($metonCycle + 11 * $epacte + 22 * $dominicalLetter, 451);
|
|
$paquesNumber = $epacte + $dominicalLetter - 7 * $correction + 114;
|
|
|
|
return static::createDate($year, intdiv($paquesNumber, 31), ($paquesNumber % 31) + 1);
|
|
}
|
|
/**
|
|
* Calculate the mother day
|
|
*
|
|
* @param int $year The year
|
|
*
|
|
* @return DateTimeImmutable The date
|
|
*/
|
|
private static function calculateMotherDay (int $year): DateTimeImmutable {
|
|
$paquesDate = static::calculatePaques($year);
|
|
|
|
$motherDay = static::createDate($year, 5, 31);
|
|
$motherDayOfWeek = (int)$motherDay->format('N');
|
|
if ($motherDayOfWeek < 7) {
|
|
try {
|
|
$motherDay = $motherDay->sub(new DateInterval('P' . $motherDayOfWeek . 'D'));
|
|
}
|
|
catch (Exception) {
|
|
}
|
|
}
|
|
if ($motherDay === $paquesDate) {
|
|
$motherDay = $motherDay->add(new DateInterval('P7D'));
|
|
}
|
|
|
|
return $motherDay;
|
|
}
|
|
/**
|
|
* Calculate the father day
|
|
*
|
|
* @param int $year The year
|
|
*
|
|
* @return DateTimeImmutable The date
|
|
*/
|
|
private static function calculateFatherDay (int $year): DateTimeImmutable {
|
|
$fatherDay = static::createDate($year, 6, 1);
|
|
try {
|
|
$fatherDay = $fatherDay->sub(new DateInterval('P' . (7 - (int)$fatherDay->format('N')) . 'D'));
|
|
}
|
|
catch (Exception) {
|
|
}
|
|
return $fatherDay->add(new DateInterval('P21D'));
|
|
}
|
|
/**
|
|
* Calculate the grandmother day
|
|
*
|
|
* @param int $year The year
|
|
*
|
|
* @return DateTimeImmutable The date
|
|
*/
|
|
private static function calculateGrandMotherDay (int $year): DateTimeImmutable {
|
|
$grandMotherDay = static::createDate($year, 3, 1);
|
|
try {
|
|
$grandMotherDay = $grandMotherDay->sub(new DateInterval('P' . (7 - (int)$grandMotherDay->format('N')) . 'D'));
|
|
}
|
|
catch (Exception) {
|
|
}
|
|
return $grandMotherDay;
|
|
}
|
|
/**
|
|
* Calculate the grandfather day
|
|
*
|
|
* @param int $year The year
|
|
*
|
|
* @return DateTimeImmutable The date
|
|
*/
|
|
private static function calculateGrandFatherDay (int $year): DateTimeImmutable {
|
|
$grandFatherDay = static::createDate($year, 10, 1);
|
|
try {
|
|
$grandFatherDay = $grandFatherDay->sub(new DateInterval('P' . (7 - (int)$grandFatherDay->format('N')) . 'D'));
|
|
}
|
|
catch (Exception) {
|
|
}
|
|
return $grandFatherDay;
|
|
}
|
|
|
|
/**
|
|
* Create an event
|
|
*
|
|
* @param string $summary The event's summary
|
|
* @param string $description The event's description
|
|
* @param bool $publicHoliday Is the event a public holiday ?
|
|
* @param DateTimeInterface $date The event's date (whole day)
|
|
*
|
|
* @return Event The event
|
|
*/
|
|
private static function createEvent (string $summary, string $description, bool $publicHoliday, DateTimeInterface $date): Event {
|
|
return (new Event(new UniqueIdentifier(hash('sha256', $date->format('Y-m-d')))))
|
|
->setSummary($summary)
|
|
->setDescription($description . ' ' . $date->format('Y') . ($publicHoliday ? ' - Férié' : ''))
|
|
->setOccurrence(new SingleDay(new Date($date)));
|
|
}
|
|
|
|
/**
|
|
* Create a date (string)
|
|
*
|
|
* @param int $year The year
|
|
* @param int $month The month of the year
|
|
* @param int $day The day of the month
|
|
*
|
|
* @return string The date with "Y-m-d" format
|
|
*/
|
|
private static function createDateString (int $year, int $month, int $day): string {
|
|
return MultiByte::str_pad($year, 4, '0', STR_PAD_LEFT)
|
|
. '-' . MultiByte::str_pad($month, 2, '0', STR_PAD_LEFT)
|
|
. '-' . MultiByte::str_pad($day, 2, '0', STR_PAD_LEFT);
|
|
}
|
|
/**
|
|
* Create a date ({@see DateTime})
|
|
*
|
|
* @param int $year The year
|
|
* @param int $month The month of the year
|
|
* @param int $day The day of the month
|
|
*
|
|
* @return DateTimeImmutable The date
|
|
*/
|
|
private static function createDate (int $year, int $month, int $day): DateTimeImmutable {
|
|
return static::createDateFromDateString(static::createDateString($year, $month, $day));
|
|
}
|
|
/**
|
|
* Create a date ({@see DateTimeImmutable}) from a date string
|
|
*
|
|
* @param string $dateString The date string ("Y-m-d" format)
|
|
*
|
|
* @return DateTimeImmutable The date
|
|
*/
|
|
private static function createDateFromDateString (string $dateString): DateTimeImmutable {
|
|
try {
|
|
return new DateTimeImmutable($dateString);
|
|
}
|
|
catch (Exception) {
|
|
return new DateTimeImmutable('now');
|
|
}
|
|
}
|
|
} |