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*(?[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'); } } }