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.

361 lines
13 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
*
* All times are UTC
*
* @return void
*/
#[NoReturn] public function proceed (): void {
try {
//region Extract arguments
//region YearStart
$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 = $_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 = $_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['false'];
//endregion
//endregion
//region Create the calendar
$calendar = new Calendar();
$calendar->addTimeZone(TimeZone::createFromPhpDateTimeZone(new DateTimeZone(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);
header('Content-Type: text/calendar; charset=UTF-8', true, 200);
header('Content-Length: ' . mb_strlen($icalContent));
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 $e->getMessage();
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->sub(new DateInterval('P' . (8 - $currentDayOfWeek) . 'D'));
}
catch (Exception) {
}
}
$events = [];
while ($currentDay < $lastDay) {
$events[] = static::createEvent(
'Semaine ' . (count($events) + 1),
'Semaine n° ' . (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 . ($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');
}
}
}