14a83f5d7SGreg Roach<?php 23976b470SGreg Roach 34a83f5d7SGreg Roach/** 44a83f5d7SGreg Roach * webtrees: online genealogy 589f7189bSGreg Roach * Copyright (C) 2021 webtrees development team 64a83f5d7SGreg Roach * This program is free software: you can redistribute it and/or modify 74a83f5d7SGreg Roach * it under the terms of the GNU General Public License as published by 84a83f5d7SGreg Roach * the Free Software Foundation, either version 3 of the License, or 94a83f5d7SGreg Roach * (at your option) any later version. 104a83f5d7SGreg Roach * This program is distributed in the hope that it will be useful, 114a83f5d7SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 124a83f5d7SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 134a83f5d7SGreg Roach * GNU General Public License for more details. 144a83f5d7SGreg Roach * You should have received a copy of the GNU General Public License 1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 164a83f5d7SGreg Roach */ 17fcfa147eSGreg Roach 184a83f5d7SGreg Roachdeclare(strict_types=1); 194a83f5d7SGreg Roach 204a83f5d7SGreg Roachnamespace Fisharebest\Webtrees\Date; 214a83f5d7SGreg Roach 224a83f5d7SGreg Roachuse Fisharebest\ExtCalendar\CalendarInterface; 234a83f5d7SGreg Roachuse Fisharebest\ExtCalendar\JewishCalendar; 244459dc9aSGreg Roachuse Fisharebest\Webtrees\Carbon; 25b00cb080SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\CalendarPage; 264a83f5d7SGreg Roachuse Fisharebest\Webtrees\I18N; 2749d5f1d7SGreg Roachuse Fisharebest\Webtrees\Tree; 2891495569SGreg Roachuse InvalidArgumentException; 294a83f5d7SGreg Roach 301934fe30SGreg Roachuse function array_key_exists; 311934fe30SGreg Roachuse function array_search; 321934fe30SGreg Roachuse function get_class; 331934fe30SGreg Roachuse function intdiv; 341934fe30SGreg Roachuse function is_array; 351934fe30SGreg Roachuse function is_int; 361934fe30SGreg Roachuse function preg_match; 371934fe30SGreg Roachuse function route; 381934fe30SGreg Roachuse function sprintf; 3952eb92f9SGreg Roachuse function str_contains; 401934fe30SGreg Roachuse function strpbrk; 41dec352c1SGreg Roachuse function strtr; 421934fe30SGreg Roachuse function trim; 43054771e9SGreg Roach 444a83f5d7SGreg Roach/** 454a83f5d7SGreg Roach * Classes for Gedcom Date/Calendar functionality. 464a83f5d7SGreg Roach * 474a83f5d7SGreg Roach * CalendarDate is a base class for classes such as GregorianDate, etc. 484a83f5d7SGreg Roach * + All supported calendars have non-zero days/months/years. 494a83f5d7SGreg Roach * + We store dates as both Y/M/D and Julian Days. 504a83f5d7SGreg Roach * + For imprecise dates such as "JAN 2000" we store the start/end julian day. 514a83f5d7SGreg Roach * 524a83f5d7SGreg Roach * NOTE: Since different calendars start their days at different times, (civil 534a83f5d7SGreg Roach * midnight, solar midnight, sunset, sunrise, etc.), we convert on the basis of 544a83f5d7SGreg Roach * midday. 554a83f5d7SGreg Roach */ 567bb2eb25SGreg Roachabstract class AbstractCalendarDate 574a83f5d7SGreg Roach{ 584a83f5d7SGreg Roach // GEDCOM calendar escape 5916d6367aSGreg Roach public const ESCAPE = '@#DUNKNOWN@'; 604a83f5d7SGreg Roach 614a83f5d7SGreg Roach // Convert GEDCOM month names to month numbers. 62*d8809d62SGreg Roach protected const MONTH_TO_NUMBER = []; 63*d8809d62SGreg Roach protected const NUMBER_TO_MONTH = []; 644a83f5d7SGreg Roach 6533c746f1SGreg Roach protected CalendarInterface $calendar; 664a83f5d7SGreg Roach 6733c746f1SGreg Roach public int $year; 684a83f5d7SGreg Roach 6933c746f1SGreg Roach public int $month; 704a83f5d7SGreg Roach 7133c746f1SGreg Roach public int $day; 724a83f5d7SGreg Roach 7333c746f1SGreg Roach private int $minimum_julian_day; 744a83f5d7SGreg Roach 7533c746f1SGreg Roach private int $maximum_julian_day; 764a83f5d7SGreg Roach 774a83f5d7SGreg Roach /** 784a83f5d7SGreg Roach * Create a date from either: 794a83f5d7SGreg Roach * a Julian day number 804a83f5d7SGreg Roach * day/month/year strings from a GEDCOM date 814a83f5d7SGreg Roach * another CalendarDate object 824a83f5d7SGreg Roach * 83f4c767fdSGreg Roach * @param array<string>|int|AbstractCalendarDate $date 844a83f5d7SGreg Roach */ 854a83f5d7SGreg Roach protected function __construct($date) 864a83f5d7SGreg Roach { 874a83f5d7SGreg Roach // Construct from an integer (a julian day number) 884a83f5d7SGreg Roach if (is_int($date)) { 894a83f5d7SGreg Roach $this->minimum_julian_day = $date; 904a83f5d7SGreg Roach $this->maximum_julian_day = $date; 9165e02381SGreg Roach [$this->year, $this->month, $this->day] = $this->calendar->jdToYmd($date); 924a83f5d7SGreg Roach 934a83f5d7SGreg Roach return; 944a83f5d7SGreg Roach } 954a83f5d7SGreg Roach 964a83f5d7SGreg Roach // Construct from an array (of three gedcom-style strings: "1900", "FEB", "4") 974a83f5d7SGreg Roach if (is_array($date)) { 984a83f5d7SGreg Roach $this->day = (int) $date[2]; 99*d8809d62SGreg Roach $this->month = static::MONTH_TO_NUMBER[$date[1]] ?? 0; 100*d8809d62SGreg Roach 101*d8809d62SGreg Roach if ($this->month === 0) { 1024a83f5d7SGreg Roach $this->day = 0; 1034a83f5d7SGreg Roach } 104*d8809d62SGreg Roach 1054a83f5d7SGreg Roach $this->year = $this->extractYear($date[0]); 1064a83f5d7SGreg Roach 1074a83f5d7SGreg Roach // Our simple lookup table above does not take into account Adar and leap-years. 1084a83f5d7SGreg Roach if ($this->month === 6 && $this->calendar instanceof JewishCalendar && !$this->calendar->isLeapYear($this->year)) { 1094a83f5d7SGreg Roach $this->month = 7; 1104a83f5d7SGreg Roach } 1114a83f5d7SGreg Roach 1124a83f5d7SGreg Roach $this->setJdFromYmd(); 1134a83f5d7SGreg Roach 1144a83f5d7SGreg Roach return; 1154a83f5d7SGreg Roach } 1164a83f5d7SGreg Roach 117fceda430SGreg Roach // Construct from a CalendarDate 1184a83f5d7SGreg Roach $this->minimum_julian_day = $date->minimum_julian_day; 1194a83f5d7SGreg Roach $this->maximum_julian_day = $date->maximum_julian_day; 1204a83f5d7SGreg Roach 1214a83f5d7SGreg Roach // Construct from an equivalent xxxxDate object 122e364afe4SGreg Roach if (get_class($this) === get_class($date)) { 1234a83f5d7SGreg Roach $this->year = $date->year; 1244a83f5d7SGreg Roach $this->month = $date->month; 1254a83f5d7SGreg Roach $this->day = $date->day; 1264a83f5d7SGreg Roach 1274a83f5d7SGreg Roach return; 1284a83f5d7SGreg Roach } 1294a83f5d7SGreg Roach 1304a83f5d7SGreg Roach // Not all dates can be converted 1314a83f5d7SGreg Roach if (!$this->inValidRange()) { 1324a83f5d7SGreg Roach $this->year = 0; 1334a83f5d7SGreg Roach $this->month = 0; 1344a83f5d7SGreg Roach $this->day = 0; 1354a83f5d7SGreg Roach 1364a83f5d7SGreg Roach return; 1374a83f5d7SGreg Roach } 1384a83f5d7SGreg Roach 1394a83f5d7SGreg Roach // ...else construct an inequivalent xxxxDate object 140e364afe4SGreg Roach if ($date->year === 0) { 1414a83f5d7SGreg Roach // Incomplete date - convert on basis of anniversary in current year 1424459dc9aSGreg Roach $today = $date->calendar->jdToYmd(Carbon::now()->julianDay()); 143e364afe4SGreg Roach $jd = $date->calendar->ymdToJd($today[0], $date->month, $date->day === 0 ? $today[2] : $date->day); 1444a83f5d7SGreg Roach } else { 1454a83f5d7SGreg Roach // Complete date 1464a83f5d7SGreg Roach $jd = intdiv($date->maximum_julian_day + $date->minimum_julian_day, 2); 1474a83f5d7SGreg Roach } 14865e02381SGreg Roach [$this->year, $this->month, $this->day] = $this->calendar->jdToYmd($jd); 1494a83f5d7SGreg Roach // New date has same precision as original date 150e364afe4SGreg Roach if ($date->year === 0) { 1514a83f5d7SGreg Roach $this->year = 0; 1524a83f5d7SGreg Roach } 153e364afe4SGreg Roach if ($date->month === 0) { 1544a83f5d7SGreg Roach $this->month = 0; 1554a83f5d7SGreg Roach } 156e364afe4SGreg Roach if ($date->day === 0) { 1574a83f5d7SGreg Roach $this->day = 0; 1584a83f5d7SGreg Roach } 1594a83f5d7SGreg Roach $this->setJdFromYmd(); 1604a83f5d7SGreg Roach } 1614a83f5d7SGreg Roach 1624a83f5d7SGreg Roach /** 1634a83f5d7SGreg Roach * @return int 1644a83f5d7SGreg Roach */ 1654a83f5d7SGreg Roach public function maximumJulianDay(): int 1664a83f5d7SGreg Roach { 1674a83f5d7SGreg Roach return $this->maximum_julian_day; 1684a83f5d7SGreg Roach } 1694a83f5d7SGreg Roach 1704a83f5d7SGreg Roach /** 1714a83f5d7SGreg Roach * @return int 1724a83f5d7SGreg Roach */ 1734a83f5d7SGreg Roach public function year(): int 1744a83f5d7SGreg Roach { 1754a83f5d7SGreg Roach return $this->year; 1764a83f5d7SGreg Roach } 1774a83f5d7SGreg Roach 1784a83f5d7SGreg Roach /** 1794a83f5d7SGreg Roach * @return int 1804a83f5d7SGreg Roach */ 1814a83f5d7SGreg Roach public function month(): int 1824a83f5d7SGreg Roach { 1834a83f5d7SGreg Roach return $this->month; 1844a83f5d7SGreg Roach } 1854a83f5d7SGreg Roach 1864a83f5d7SGreg Roach /** 1874a83f5d7SGreg Roach * @return int 1884a83f5d7SGreg Roach */ 1894a83f5d7SGreg Roach public function day(): int 1904a83f5d7SGreg Roach { 1914a83f5d7SGreg Roach return $this->day; 1924a83f5d7SGreg Roach } 1934a83f5d7SGreg Roach 1944a83f5d7SGreg Roach /** 1954a83f5d7SGreg Roach * @return int 1964a83f5d7SGreg Roach */ 1974a83f5d7SGreg Roach public function minimumJulianDay(): int 1984a83f5d7SGreg Roach { 1994a83f5d7SGreg Roach return $this->minimum_julian_day; 2004a83f5d7SGreg Roach } 2014a83f5d7SGreg Roach 2024a83f5d7SGreg Roach /** 2034a83f5d7SGreg Roach * Is the current year a leap year? 2044a83f5d7SGreg Roach * 2054a83f5d7SGreg Roach * @return bool 2064a83f5d7SGreg Roach */ 2074a83f5d7SGreg Roach public function isLeapYear(): bool 2084a83f5d7SGreg Roach { 2094a83f5d7SGreg Roach return $this->calendar->isLeapYear($this->year); 2104a83f5d7SGreg Roach } 2114a83f5d7SGreg Roach 2124a83f5d7SGreg Roach /** 2134a83f5d7SGreg Roach * Set the object’s Julian day number from a potentially incomplete year/month/day 2144a83f5d7SGreg Roach * 2154a83f5d7SGreg Roach * @return void 2164a83f5d7SGreg Roach */ 217e364afe4SGreg Roach public function setJdFromYmd(): void 2184a83f5d7SGreg Roach { 219e364afe4SGreg Roach if ($this->year === 0) { 2204a83f5d7SGreg Roach $this->minimum_julian_day = 0; 2214a83f5d7SGreg Roach $this->maximum_julian_day = 0; 222e364afe4SGreg Roach } elseif ($this->month === 0) { 2234a83f5d7SGreg Roach $this->minimum_julian_day = $this->calendar->ymdToJd($this->year, 1, 1); 2244a83f5d7SGreg Roach $this->maximum_julian_day = $this->calendar->ymdToJd($this->nextYear($this->year), 1, 1) - 1; 225e364afe4SGreg Roach } elseif ($this->day === 0) { 22665e02381SGreg Roach [$ny, $nm] = $this->nextMonth(); 2274a83f5d7SGreg Roach $this->minimum_julian_day = $this->calendar->ymdToJd($this->year, $this->month, 1); 2284a83f5d7SGreg Roach $this->maximum_julian_day = $this->calendar->ymdToJd($ny, $nm, 1) - 1; 2294a83f5d7SGreg Roach } else { 2304a83f5d7SGreg Roach $this->minimum_julian_day = $this->calendar->ymdToJd($this->year, $this->month, $this->day); 2314a83f5d7SGreg Roach $this->maximum_julian_day = $this->minimum_julian_day; 2324a83f5d7SGreg Roach } 2334a83f5d7SGreg Roach } 2344a83f5d7SGreg Roach 2354a83f5d7SGreg Roach /** 2364a83f5d7SGreg Roach * Full day of the week 2374a83f5d7SGreg Roach * 2384a83f5d7SGreg Roach * @param int $day_number 2394a83f5d7SGreg Roach * 2404a83f5d7SGreg Roach * @return string 2414a83f5d7SGreg Roach */ 2424a83f5d7SGreg Roach public function dayNames(int $day_number): string 2434a83f5d7SGreg Roach { 2444a83f5d7SGreg Roach static $translated_day_names; 2454a83f5d7SGreg Roach 2464a83f5d7SGreg Roach if ($translated_day_names === null) { 2474a83f5d7SGreg Roach $translated_day_names = [ 2484a83f5d7SGreg Roach 0 => I18N::translate('Monday'), 2494a83f5d7SGreg Roach 1 => I18N::translate('Tuesday'), 2504a83f5d7SGreg Roach 2 => I18N::translate('Wednesday'), 2514a83f5d7SGreg Roach 3 => I18N::translate('Thursday'), 2524a83f5d7SGreg Roach 4 => I18N::translate('Friday'), 2534a83f5d7SGreg Roach 5 => I18N::translate('Saturday'), 2544a83f5d7SGreg Roach 6 => I18N::translate('Sunday'), 2554a83f5d7SGreg Roach ]; 2564a83f5d7SGreg Roach } 2574a83f5d7SGreg Roach 2584a83f5d7SGreg Roach return $translated_day_names[$day_number]; 2594a83f5d7SGreg Roach } 2604a83f5d7SGreg Roach 2614a83f5d7SGreg Roach /** 2624a83f5d7SGreg Roach * Abbreviated day of the week 2634a83f5d7SGreg Roach * 2644a83f5d7SGreg Roach * @param int $day_number 2654a83f5d7SGreg Roach * 2664a83f5d7SGreg Roach * @return string 2674a83f5d7SGreg Roach */ 2684a83f5d7SGreg Roach protected function dayNamesAbbreviated(int $day_number): string 2694a83f5d7SGreg Roach { 2704a83f5d7SGreg Roach static $translated_day_names; 2714a83f5d7SGreg Roach 2724a83f5d7SGreg Roach if ($translated_day_names === null) { 2734a83f5d7SGreg Roach $translated_day_names = [ 2744a83f5d7SGreg Roach /* I18N: abbreviation for Monday */ 2754a83f5d7SGreg Roach 0 => I18N::translate('Mon'), 2764a83f5d7SGreg Roach /* I18N: abbreviation for Tuesday */ 2774a83f5d7SGreg Roach 1 => I18N::translate('Tue'), 2784a83f5d7SGreg Roach /* I18N: abbreviation for Wednesday */ 2794a83f5d7SGreg Roach 2 => I18N::translate('Wed'), 2804a83f5d7SGreg Roach /* I18N: abbreviation for Thursday */ 2814a83f5d7SGreg Roach 3 => I18N::translate('Thu'), 2824a83f5d7SGreg Roach /* I18N: abbreviation for Friday */ 2834a83f5d7SGreg Roach 4 => I18N::translate('Fri'), 2844a83f5d7SGreg Roach /* I18N: abbreviation for Saturday */ 2854a83f5d7SGreg Roach 5 => I18N::translate('Sat'), 2864a83f5d7SGreg Roach /* I18N: abbreviation for Sunday */ 2874a83f5d7SGreg Roach 6 => I18N::translate('Sun'), 2884a83f5d7SGreg Roach ]; 2894a83f5d7SGreg Roach } 2904a83f5d7SGreg Roach 2914a83f5d7SGreg Roach return $translated_day_names[$day_number]; 2924a83f5d7SGreg Roach } 2934a83f5d7SGreg Roach 2944a83f5d7SGreg Roach /** 2954a83f5d7SGreg Roach * Most years are 1 more than the previous, but not always (e.g. 1BC->1AD) 2964a83f5d7SGreg Roach * 2974a83f5d7SGreg Roach * @param int $year 2984a83f5d7SGreg Roach * 2994a83f5d7SGreg Roach * @return int 3004a83f5d7SGreg Roach */ 3014a83f5d7SGreg Roach protected function nextYear(int $year): int 3024a83f5d7SGreg Roach { 3034a83f5d7SGreg Roach return $year + 1; 3044a83f5d7SGreg Roach } 3054a83f5d7SGreg Roach 3064a83f5d7SGreg Roach /** 3074a83f5d7SGreg Roach * Calendars that use suffixes, etc. (e.g. “B.C.”) or OS/NS notation should redefine this. 3084a83f5d7SGreg Roach * 3094a83f5d7SGreg Roach * @param string $year 3104a83f5d7SGreg Roach * 3114a83f5d7SGreg Roach * @return int 3124a83f5d7SGreg Roach */ 3134a83f5d7SGreg Roach protected function extractYear(string $year): int 3144a83f5d7SGreg Roach { 3154a83f5d7SGreg Roach return (int) $year; 3164a83f5d7SGreg Roach } 3174a83f5d7SGreg Roach 3184a83f5d7SGreg Roach /** 3194a83f5d7SGreg Roach * Compare two dates, for sorting 3204a83f5d7SGreg Roach * 3214a83f5d7SGreg Roach * @param AbstractCalendarDate $d1 3224a83f5d7SGreg Roach * @param AbstractCalendarDate $d2 3234a83f5d7SGreg Roach * 3244a83f5d7SGreg Roach * @return int 3254a83f5d7SGreg Roach */ 3264a83f5d7SGreg Roach public static function compare(AbstractCalendarDate $d1, AbstractCalendarDate $d2): int 3274a83f5d7SGreg Roach { 3284a83f5d7SGreg Roach if ($d1->maximum_julian_day < $d2->minimum_julian_day) { 3294a83f5d7SGreg Roach return -1; 3304a83f5d7SGreg Roach } 3314a83f5d7SGreg Roach 3324a83f5d7SGreg Roach if ($d2->maximum_julian_day < $d1->minimum_julian_day) { 3334a83f5d7SGreg Roach return 1; 3344a83f5d7SGreg Roach } 3354a83f5d7SGreg Roach 3364a83f5d7SGreg Roach return 0; 3374a83f5d7SGreg Roach } 3384a83f5d7SGreg Roach 3394a83f5d7SGreg Roach /** 3404a83f5d7SGreg Roach * Calculate the years/months/days between this date and another date. 3414a83f5d7SGreg Roach * Results assume you add the days first, then the months. 3424a83f5d7SGreg Roach * 4 February -> 3 July is 27 days (3 March) and 4 months. 3434a83f5d7SGreg Roach * It is not 4 months (4 June) and 29 days. 3444a83f5d7SGreg Roach * 3454a83f5d7SGreg Roach * @param AbstractCalendarDate $date 3464a83f5d7SGreg Roach * 34709482a55SGreg Roach * @return array<int> Age in years/months/days 3484a83f5d7SGreg Roach */ 3494a83f5d7SGreg Roach public function ageDifference(AbstractCalendarDate $date): array 3504a83f5d7SGreg Roach { 3514a83f5d7SGreg Roach // Incomplete dates 3524a83f5d7SGreg Roach if ($this->year === 0 || $date->year === 0) { 3534a83f5d7SGreg Roach return [-1, -1, -1]; 3544a83f5d7SGreg Roach } 3554a83f5d7SGreg Roach 3564a83f5d7SGreg Roach // Overlapping dates 3574a83f5d7SGreg Roach if (self::compare($this, $date) === 0) { 3584a83f5d7SGreg Roach return [0, 0, 0]; 3594a83f5d7SGreg Roach } 3604a83f5d7SGreg Roach 3614a83f5d7SGreg Roach // Perform all calculations using the calendar of the first date 36265e02381SGreg Roach [$year1, $month1, $day1] = $this->calendar->jdToYmd($this->minimum_julian_day); 36365e02381SGreg Roach [$year2, $month2, $day2] = $this->calendar->jdToYmd($date->minimum_julian_day); 3644a83f5d7SGreg Roach 3654a83f5d7SGreg Roach $years = $year2 - $year1; 3664a83f5d7SGreg Roach $months = $month2 - $month1; 3674a83f5d7SGreg Roach $days = $day2 - $day1; 3684a83f5d7SGreg Roach 3694a83f5d7SGreg Roach if ($days < 0) { 3704a83f5d7SGreg Roach $days += $this->calendar->daysInMonth($year1, $month1); 3714a83f5d7SGreg Roach $months--; 3724a83f5d7SGreg Roach } 3734a83f5d7SGreg Roach 3744a83f5d7SGreg Roach if ($months < 0) { 3754a83f5d7SGreg Roach $months += $this->calendar->monthsInYear($year2); 3764a83f5d7SGreg Roach $years--; 3774a83f5d7SGreg Roach } 3784a83f5d7SGreg Roach 3794a83f5d7SGreg Roach return [$years, $months, $days]; 3804a83f5d7SGreg Roach } 3814a83f5d7SGreg Roach 3824a83f5d7SGreg Roach /** 3834a83f5d7SGreg Roach * Convert a date from one calendar to another. 3844a83f5d7SGreg Roach * 3854a83f5d7SGreg Roach * @param string $calendar 3864a83f5d7SGreg Roach * 3874a83f5d7SGreg Roach * @return AbstractCalendarDate 3884a83f5d7SGreg Roach */ 3894a83f5d7SGreg Roach public function convertToCalendar(string $calendar): AbstractCalendarDate 3904a83f5d7SGreg Roach { 3914a83f5d7SGreg Roach switch ($calendar) { 3924a83f5d7SGreg Roach case 'gregorian': 3934a83f5d7SGreg Roach return new GregorianDate($this); 3944a83f5d7SGreg Roach case 'julian': 3954a83f5d7SGreg Roach return new JulianDate($this); 3964a83f5d7SGreg Roach case 'jewish': 3974a83f5d7SGreg Roach return new JewishDate($this); 3984a83f5d7SGreg Roach case 'french': 3994a83f5d7SGreg Roach return new FrenchDate($this); 4004a83f5d7SGreg Roach case 'hijri': 4014a83f5d7SGreg Roach return new HijriDate($this); 4024a83f5d7SGreg Roach case 'jalali': 4034a83f5d7SGreg Roach return new JalaliDate($this); 4044a83f5d7SGreg Roach default: 4054a83f5d7SGreg Roach return $this; 4064a83f5d7SGreg Roach } 4074a83f5d7SGreg Roach } 4084a83f5d7SGreg Roach 4094a83f5d7SGreg Roach /** 4104a83f5d7SGreg Roach * Is this date within the valid range of the calendar 4114a83f5d7SGreg Roach * 4124a83f5d7SGreg Roach * @return bool 4134a83f5d7SGreg Roach */ 4144a83f5d7SGreg Roach public function inValidRange(): bool 4154a83f5d7SGreg Roach { 4164a83f5d7SGreg Roach return $this->minimum_julian_day >= $this->calendar->jdStart() && $this->maximum_julian_day <= $this->calendar->jdEnd(); 4174a83f5d7SGreg Roach } 4184a83f5d7SGreg Roach 4194a83f5d7SGreg Roach /** 4204a83f5d7SGreg Roach * How many months in a year 4214a83f5d7SGreg Roach * 4224a83f5d7SGreg Roach * @return int 4234a83f5d7SGreg Roach */ 4244a83f5d7SGreg Roach public function monthsInYear(): int 4254a83f5d7SGreg Roach { 4264a83f5d7SGreg Roach return $this->calendar->monthsInYear(); 4274a83f5d7SGreg Roach } 4284a83f5d7SGreg Roach 4294a83f5d7SGreg Roach /** 4304a83f5d7SGreg Roach * How many days in the current month 4314a83f5d7SGreg Roach * 4324a83f5d7SGreg Roach * @return int 4334a83f5d7SGreg Roach */ 4344a83f5d7SGreg Roach public function daysInMonth(): int 4354a83f5d7SGreg Roach { 4364a83f5d7SGreg Roach try { 4374a83f5d7SGreg Roach return $this->calendar->daysInMonth($this->year, $this->month); 43891495569SGreg Roach } catch (InvalidArgumentException $ex) { 4394a83f5d7SGreg Roach // calendar.php calls this with "DD MMM" dates, for which we cannot calculate 4404a83f5d7SGreg Roach // the length of a month. Should we validate this before calling this function? 4414a83f5d7SGreg Roach return 0; 4424a83f5d7SGreg Roach } 4434a83f5d7SGreg Roach } 4444a83f5d7SGreg Roach 4454a83f5d7SGreg Roach /** 4464a83f5d7SGreg Roach * How many days in the current week 4474a83f5d7SGreg Roach * 4484a83f5d7SGreg Roach * @return int 4494a83f5d7SGreg Roach */ 4504a83f5d7SGreg Roach public function daysInWeek(): int 4514a83f5d7SGreg Roach { 4524a83f5d7SGreg Roach return $this->calendar->daysInWeek(); 4534a83f5d7SGreg Roach } 4544a83f5d7SGreg Roach 4554a83f5d7SGreg Roach /** 4564a83f5d7SGreg Roach * Format a date, using similar codes to the PHP date() function. 4574a83f5d7SGreg Roach * 458ad3143ccSGreg Roach * @param string $format See https://php.net/date 4594a83f5d7SGreg Roach * @param string $qualifier GEDCOM qualifier, so we can choose the right case for the month name. 4604a83f5d7SGreg Roach * 4614a83f5d7SGreg Roach * @return string 4624a83f5d7SGreg Roach */ 4634a83f5d7SGreg Roach public function format(string $format, string $qualifier = ''): string 4644a83f5d7SGreg Roach { 46552eb92f9SGreg Roach // Dates can include additional punctuation and symbols. e.g. 46652eb92f9SGreg Roach // %F %j, %Y 46752eb92f9SGreg Roach // %Y. %F %d. 46852eb92f9SGreg Roach // %Y年 %n月 %j日 46952eb92f9SGreg Roach // %j. %F %Y 47052eb92f9SGreg Roach // Don’t show exact details or unnecessary punctuation for inexact dates. 47152eb92f9SGreg Roach if ($this->day === 0) { 47252eb92f9SGreg Roach $format = strtr($format, ['%d' => '', '%j日' => '', '%j,' => '', '%j' => '', '%l' => '', '%D' => '', '%N' => '', '%S' => '', '%w' => '', '%z' => '']); 47352eb92f9SGreg Roach } 47452eb92f9SGreg Roach if ($this->month === 0) { 47552eb92f9SGreg Roach $format = strtr($format, ['%F' => '', '%m' => '', '%M' => '', '年 %n月' => '', '%n' => '', '%t' => '']); 47652eb92f9SGreg Roach } 47752eb92f9SGreg Roach if ($this->year === 0) { 47852eb92f9SGreg Roach $format = strtr($format, ['%t' => '', '%L' => '', '%G' => '', '%y' => '', '%Y年' => '', '%Y' => '']); 47952eb92f9SGreg Roach } 48052eb92f9SGreg Roach $format = trim($format, ',. /-'); 4811934fe30SGreg Roach 48252eb92f9SGreg Roach if ($this->day !== 0 && preg_match('/%[djlDNSwz]/', $format)) { 4834a83f5d7SGreg Roach // If we have a day-number *and* we are being asked to display it, then genitive 4844a83f5d7SGreg Roach $case = 'GENITIVE'; 4854a83f5d7SGreg Roach } else { 4864a83f5d7SGreg Roach switch ($qualifier) { 4874a83f5d7SGreg Roach case 'TO': 4884a83f5d7SGreg Roach case 'ABT': 4894a83f5d7SGreg Roach case 'FROM': 4904a83f5d7SGreg Roach $case = 'GENITIVE'; 4914a83f5d7SGreg Roach break; 4924a83f5d7SGreg Roach case 'AFT': 4934a83f5d7SGreg Roach $case = 'LOCATIVE'; 4944a83f5d7SGreg Roach break; 4954a83f5d7SGreg Roach case 'BEF': 4964a83f5d7SGreg Roach case 'BET': 4974a83f5d7SGreg Roach case 'AND': 4984a83f5d7SGreg Roach $case = 'INSTRUMENTAL'; 4994a83f5d7SGreg Roach break; 5004a83f5d7SGreg Roach case '': 5014a83f5d7SGreg Roach case 'INT': 5024a83f5d7SGreg Roach case 'EST': 5034a83f5d7SGreg Roach case 'CAL': 5044a83f5d7SGreg Roach default: // There shouldn't be any other options... 5054a83f5d7SGreg Roach $case = 'NOMINATIVE'; 5064a83f5d7SGreg Roach break; 5074a83f5d7SGreg Roach } 5084a83f5d7SGreg Roach } 50952eb92f9SGreg Roach // Build up the formatted date, character at a time 51052eb92f9SGreg Roach if (str_contains($format, '%d')) { 51152eb92f9SGreg Roach $format = strtr($format, ['%d' => $this->formatDayZeros()]); 51252eb92f9SGreg Roach } 51352eb92f9SGreg Roach if (str_contains($format, '%j')) { 51452eb92f9SGreg Roach $format = strtr($format, ['%j' => $this->formatDay()]); 51552eb92f9SGreg Roach } 51652eb92f9SGreg Roach if (str_contains($format, '%l')) { 51752eb92f9SGreg Roach $format = strtr($format, ['%l' => $this->formatLongWeekday()]); 51852eb92f9SGreg Roach } 51952eb92f9SGreg Roach if (str_contains($format, '%D')) { 52052eb92f9SGreg Roach $format = strtr($format, ['%D' => $this->formatShortWeekday()]); 52152eb92f9SGreg Roach } 52252eb92f9SGreg Roach if (str_contains($format, '%N')) { 52352eb92f9SGreg Roach $format = strtr($format, ['%N' => $this->formatIsoWeekday()]); 52452eb92f9SGreg Roach } 52552eb92f9SGreg Roach if (str_contains($format, '%w')) { 52652eb92f9SGreg Roach $format = strtr($format, ['%w' => $this->formatNumericWeekday()]); 52752eb92f9SGreg Roach } 52852eb92f9SGreg Roach if (str_contains($format, '%z')) { 52952eb92f9SGreg Roach $format = strtr($format, ['%z' => $this->formatDayOfYear()]); 53052eb92f9SGreg Roach } 53152eb92f9SGreg Roach if (str_contains($format, '%F')) { 53252eb92f9SGreg Roach $format = strtr($format, ['%F' => $this->formatLongMonth($case)]); 53352eb92f9SGreg Roach } 53452eb92f9SGreg Roach if (str_contains($format, '%m')) { 53552eb92f9SGreg Roach $format = strtr($format, ['%m' => $this->formatMonthZeros()]); 53652eb92f9SGreg Roach } 53752eb92f9SGreg Roach if (str_contains($format, '%M')) { 53852eb92f9SGreg Roach $format = strtr($format, ['%M' => $this->formatShortMonth()]); 53952eb92f9SGreg Roach } 54052eb92f9SGreg Roach if (str_contains($format, '%n')) { 541b2d30019SGreg Roach $format = strtr($format, ['%n' => $this->formatMonth()]); 54252eb92f9SGreg Roach } 54352eb92f9SGreg Roach if (str_contains($format, '%t')) { 544b2d30019SGreg Roach $format = strtr($format, ['%t' => (string) $this->daysInMonth()]); 54552eb92f9SGreg Roach } 54652eb92f9SGreg Roach if (str_contains($format, '%L')) { 54752eb92f9SGreg Roach $format = strtr($format, ['%L' => $this->isLeapYear() ? '1' : '0']); 54852eb92f9SGreg Roach } 54952eb92f9SGreg Roach if (str_contains($format, '%Y')) { 55052eb92f9SGreg Roach $format = strtr($format, ['%Y' => $this->formatLongYear()]); 55152eb92f9SGreg Roach } 55252eb92f9SGreg Roach if (str_contains($format, '%y')) { 55352eb92f9SGreg Roach $format = strtr($format, ['%y' => $this->formatShortYear()]); 55452eb92f9SGreg Roach } 555dec352c1SGreg Roach // These 4 extensions are useful for re-formatting gedcom dates. 55652eb92f9SGreg Roach if (str_contains($format, '%@')) { 55752eb92f9SGreg Roach $format = strtr($format, ['%@' => $this->formatGedcomCalendarEscape()]); 55852eb92f9SGreg Roach } 55952eb92f9SGreg Roach if (str_contains($format, '%A')) { 56052eb92f9SGreg Roach $format = strtr($format, ['%A' => $this->formatGedcomDay()]); 56152eb92f9SGreg Roach } 56252eb92f9SGreg Roach if (str_contains($format, '%O')) { 56352eb92f9SGreg Roach $format = strtr($format, ['%O' => $this->formatGedcomMonth()]); 56452eb92f9SGreg Roach } 56552eb92f9SGreg Roach if (str_contains($format, '%E')) { 56652eb92f9SGreg Roach $format = strtr($format, ['%E' => $this->formatGedcomYear()]); 56752eb92f9SGreg Roach } 56852eb92f9SGreg Roach 56952eb92f9SGreg Roach return $format; 5704a83f5d7SGreg Roach } 5714a83f5d7SGreg Roach 5724a83f5d7SGreg Roach /** 5734a83f5d7SGreg Roach * Generate the %d format for a date. 5744a83f5d7SGreg Roach * 5754a83f5d7SGreg Roach * @return string 5764a83f5d7SGreg Roach */ 5774a83f5d7SGreg Roach protected function formatDayZeros(): string 5784a83f5d7SGreg Roach { 5794a83f5d7SGreg Roach if ($this->day > 9) { 5804a83f5d7SGreg Roach return I18N::digits($this->day); 5814a83f5d7SGreg Roach } 5824a83f5d7SGreg Roach 5834a83f5d7SGreg Roach return I18N::digits('0' . $this->day); 5844a83f5d7SGreg Roach } 5854a83f5d7SGreg Roach 5864a83f5d7SGreg Roach /** 5874a83f5d7SGreg Roach * Generate the %j format for a date. 5884a83f5d7SGreg Roach * 5894a83f5d7SGreg Roach * @return string 5904a83f5d7SGreg Roach */ 5914a83f5d7SGreg Roach protected function formatDay(): string 5924a83f5d7SGreg Roach { 5934a83f5d7SGreg Roach return I18N::digits($this->day); 5944a83f5d7SGreg Roach } 5954a83f5d7SGreg Roach 5964a83f5d7SGreg Roach /** 5974a83f5d7SGreg Roach * Generate the %l format for a date. 5984a83f5d7SGreg Roach * 5994a83f5d7SGreg Roach * @return string 6004a83f5d7SGreg Roach */ 6014a83f5d7SGreg Roach protected function formatLongWeekday(): string 6024a83f5d7SGreg Roach { 6034a83f5d7SGreg Roach return $this->dayNames($this->minimum_julian_day % $this->calendar->daysInWeek()); 6044a83f5d7SGreg Roach } 6054a83f5d7SGreg Roach 6064a83f5d7SGreg Roach /** 6074a83f5d7SGreg Roach * Generate the %D format for a date. 6084a83f5d7SGreg Roach * 6094a83f5d7SGreg Roach * @return string 6104a83f5d7SGreg Roach */ 6114a83f5d7SGreg Roach protected function formatShortWeekday(): string 6124a83f5d7SGreg Roach { 6134a83f5d7SGreg Roach return $this->dayNamesAbbreviated($this->minimum_julian_day % $this->calendar->daysInWeek()); 6144a83f5d7SGreg Roach } 6154a83f5d7SGreg Roach 6164a83f5d7SGreg Roach /** 6174a83f5d7SGreg Roach * Generate the %N format for a date. 6184a83f5d7SGreg Roach * 6194a83f5d7SGreg Roach * @return string 6204a83f5d7SGreg Roach */ 6214a83f5d7SGreg Roach protected function formatIsoWeekday(): string 6224a83f5d7SGreg Roach { 6234a83f5d7SGreg Roach return I18N::digits($this->minimum_julian_day % 7 + 1); 6244a83f5d7SGreg Roach } 6254a83f5d7SGreg Roach 6264a83f5d7SGreg Roach /** 6274a83f5d7SGreg Roach * Generate the %w format for a date. 6284a83f5d7SGreg Roach * 6294a83f5d7SGreg Roach * @return string 6304a83f5d7SGreg Roach */ 6314a83f5d7SGreg Roach protected function formatNumericWeekday(): string 6324a83f5d7SGreg Roach { 6334a83f5d7SGreg Roach return I18N::digits(($this->minimum_julian_day + 1) % $this->calendar->daysInWeek()); 6344a83f5d7SGreg Roach } 6354a83f5d7SGreg Roach 6364a83f5d7SGreg Roach /** 6374a83f5d7SGreg Roach * Generate the %z format for a date. 6384a83f5d7SGreg Roach * 6394a83f5d7SGreg Roach * @return string 6404a83f5d7SGreg Roach */ 6414a83f5d7SGreg Roach protected function formatDayOfYear(): string 6424a83f5d7SGreg Roach { 6434a83f5d7SGreg Roach return I18N::digits($this->minimum_julian_day - $this->calendar->ymdToJd($this->year, 1, 1)); 6444a83f5d7SGreg Roach } 6454a83f5d7SGreg Roach 6464a83f5d7SGreg Roach /** 6474a83f5d7SGreg Roach * Generate the %n format for a date. 6484a83f5d7SGreg Roach * 6494a83f5d7SGreg Roach * @return string 6504a83f5d7SGreg Roach */ 6514a83f5d7SGreg Roach protected function formatMonth(): string 6524a83f5d7SGreg Roach { 6534a83f5d7SGreg Roach return I18N::digits($this->month); 6544a83f5d7SGreg Roach } 6554a83f5d7SGreg Roach 6564a83f5d7SGreg Roach /** 6574a83f5d7SGreg Roach * Generate the %m format for a date. 6584a83f5d7SGreg Roach * 6594a83f5d7SGreg Roach * @return string 6604a83f5d7SGreg Roach */ 6614a83f5d7SGreg Roach protected function formatMonthZeros(): string 6624a83f5d7SGreg Roach { 6634a83f5d7SGreg Roach if ($this->month > 9) { 6644a83f5d7SGreg Roach return I18N::digits($this->month); 6654a83f5d7SGreg Roach } 6664a83f5d7SGreg Roach 6674a83f5d7SGreg Roach return I18N::digits('0' . $this->month); 6684a83f5d7SGreg Roach } 6694a83f5d7SGreg Roach 6704a83f5d7SGreg Roach /** 6714a83f5d7SGreg Roach * Generate the %F format for a date. 6724a83f5d7SGreg Roach * 6734a83f5d7SGreg Roach * @param string $case Which grammatical case shall we use 6744a83f5d7SGreg Roach * 6754a83f5d7SGreg Roach * @return string 6764a83f5d7SGreg Roach */ 67773d58381SGreg Roach protected function formatLongMonth(string $case = 'NOMINATIVE'): string 6784a83f5d7SGreg Roach { 6794a83f5d7SGreg Roach switch ($case) { 6804a83f5d7SGreg Roach case 'GENITIVE': 6814a83f5d7SGreg Roach return $this->monthNameGenitiveCase($this->month, $this->isLeapYear()); 6824a83f5d7SGreg Roach case 'NOMINATIVE': 6834a83f5d7SGreg Roach return $this->monthNameNominativeCase($this->month, $this->isLeapYear()); 6844a83f5d7SGreg Roach case 'LOCATIVE': 6854a83f5d7SGreg Roach return $this->monthNameLocativeCase($this->month, $this->isLeapYear()); 6864a83f5d7SGreg Roach case 'INSTRUMENTAL': 6874a83f5d7SGreg Roach return $this->monthNameInstrumentalCase($this->month, $this->isLeapYear()); 6884a83f5d7SGreg Roach default: 68991495569SGreg Roach throw new InvalidArgumentException($case); 6904a83f5d7SGreg Roach } 6914a83f5d7SGreg Roach } 6924a83f5d7SGreg Roach 6934a83f5d7SGreg Roach /** 6947bb2eb25SGreg Roach * Full month name in genitive case. 6957bb2eb25SGreg Roach * 6967bb2eb25SGreg Roach * @param int $month 6977bb2eb25SGreg Roach * @param bool $leap_year Some calendars use leap months 6987bb2eb25SGreg Roach * 6997bb2eb25SGreg Roach * @return string 7007bb2eb25SGreg Roach */ 7017bb2eb25SGreg Roach abstract protected function monthNameGenitiveCase(int $month, bool $leap_year): string; 7027bb2eb25SGreg Roach 7037bb2eb25SGreg Roach /** 7047bb2eb25SGreg Roach * Full month name in nominative case. 7057bb2eb25SGreg Roach * 7067bb2eb25SGreg Roach * @param int $month 7077bb2eb25SGreg Roach * @param bool $leap_year Some calendars use leap months 7087bb2eb25SGreg Roach * 7097bb2eb25SGreg Roach * @return string 7107bb2eb25SGreg Roach */ 7117bb2eb25SGreg Roach abstract protected function monthNameNominativeCase(int $month, bool $leap_year): string; 7127bb2eb25SGreg Roach 7137bb2eb25SGreg Roach /** 7147bb2eb25SGreg Roach * Full month name in locative case. 7157bb2eb25SGreg Roach * 7167bb2eb25SGreg Roach * @param int $month 7177bb2eb25SGreg Roach * @param bool $leap_year Some calendars use leap months 7187bb2eb25SGreg Roach * 7197bb2eb25SGreg Roach * @return string 7207bb2eb25SGreg Roach */ 7217bb2eb25SGreg Roach abstract protected function monthNameLocativeCase(int $month, bool $leap_year): string; 7227bb2eb25SGreg Roach 7237bb2eb25SGreg Roach /** 7247bb2eb25SGreg Roach * Full month name in instrumental case. 7257bb2eb25SGreg Roach * 7267bb2eb25SGreg Roach * @param int $month 7277bb2eb25SGreg Roach * @param bool $leap_year Some calendars use leap months 7287bb2eb25SGreg Roach * 7297bb2eb25SGreg Roach * @return string 7307bb2eb25SGreg Roach */ 7317bb2eb25SGreg Roach abstract protected function monthNameInstrumentalCase(int $month, bool $leap_year): string; 7327bb2eb25SGreg Roach 7337bb2eb25SGreg Roach /** 7347bb2eb25SGreg Roach * Abbreviated month name 7357bb2eb25SGreg Roach * 7367bb2eb25SGreg Roach * @param int $month 7377bb2eb25SGreg Roach * @param bool $leap_year Some calendars use leap months 7387bb2eb25SGreg Roach * 7397bb2eb25SGreg Roach * @return string 7407bb2eb25SGreg Roach */ 7417bb2eb25SGreg Roach abstract protected function monthNameAbbreviated(int $month, bool $leap_year): string; 7427bb2eb25SGreg Roach 7437bb2eb25SGreg Roach /** 7444a83f5d7SGreg Roach * Generate the %M format for a date. 7454a83f5d7SGreg Roach * 7464a83f5d7SGreg Roach * @return string 7474a83f5d7SGreg Roach */ 7484a83f5d7SGreg Roach protected function formatShortMonth(): string 7494a83f5d7SGreg Roach { 7504a83f5d7SGreg Roach return $this->monthNameAbbreviated($this->month, $this->isLeapYear()); 7514a83f5d7SGreg Roach } 7524a83f5d7SGreg Roach 7534a83f5d7SGreg Roach /** 7544a83f5d7SGreg Roach * Generate the %y format for a date. 7554a83f5d7SGreg Roach * NOTE Short year is NOT a 2-digit year. It is for calendars such as hebrew 7564a83f5d7SGreg Roach * which have a 3-digit form of 4-digit years. 7574a83f5d7SGreg Roach * 7584a83f5d7SGreg Roach * @return string 7594a83f5d7SGreg Roach */ 7604a83f5d7SGreg Roach protected function formatShortYear(): string 7614a83f5d7SGreg Roach { 7624a83f5d7SGreg Roach return $this->formatLongYear(); 7634a83f5d7SGreg Roach } 7644a83f5d7SGreg Roach 7654a83f5d7SGreg Roach /** 7664a83f5d7SGreg Roach * Generate the %A format for a date. 7674a83f5d7SGreg Roach * 7684a83f5d7SGreg Roach * @return string 7694a83f5d7SGreg Roach */ 7704a83f5d7SGreg Roach protected function formatGedcomDay(): string 7714a83f5d7SGreg Roach { 772e364afe4SGreg Roach if ($this->day === 0) { 7734a83f5d7SGreg Roach return ''; 7744a83f5d7SGreg Roach } 7754a83f5d7SGreg Roach 7764a83f5d7SGreg Roach return sprintf('%02d', $this->day); 7774a83f5d7SGreg Roach } 7784a83f5d7SGreg Roach 7794a83f5d7SGreg Roach /** 7804a83f5d7SGreg Roach * Generate the %O format for a date. 7814a83f5d7SGreg Roach * 7824a83f5d7SGreg Roach * @return string 7834a83f5d7SGreg Roach */ 7844a83f5d7SGreg Roach protected function formatGedcomMonth(): string 7854a83f5d7SGreg Roach { 7864a83f5d7SGreg Roach // Our simple lookup table doesn't work correctly for Adar on leap years 787e364afe4SGreg Roach if ($this->month === 7 && $this->calendar instanceof JewishCalendar && !$this->calendar->isLeapYear($this->year)) { 7884a83f5d7SGreg Roach return 'ADR'; 7894a83f5d7SGreg Roach } 7904a83f5d7SGreg Roach 791*d8809d62SGreg Roach return static::NUMBER_TO_MONTH[$this->month] ?? ''; 7924a83f5d7SGreg Roach } 7934a83f5d7SGreg Roach 7944a83f5d7SGreg Roach /** 7954a83f5d7SGreg Roach * Generate the %E format for a date. 7964a83f5d7SGreg Roach * 7974a83f5d7SGreg Roach * @return string 7984a83f5d7SGreg Roach */ 7994a83f5d7SGreg Roach protected function formatGedcomYear(): string 8004a83f5d7SGreg Roach { 801e364afe4SGreg Roach if ($this->year === 0) { 8024a83f5d7SGreg Roach return ''; 8034a83f5d7SGreg Roach } 8044a83f5d7SGreg Roach 8054a83f5d7SGreg Roach return sprintf('%04d', $this->year); 8064a83f5d7SGreg Roach } 8074a83f5d7SGreg Roach 8084a83f5d7SGreg Roach /** 8094a83f5d7SGreg Roach * Generate the %@ format for a calendar escape. 8104a83f5d7SGreg Roach * 8114a83f5d7SGreg Roach * @return string 8124a83f5d7SGreg Roach */ 8134a83f5d7SGreg Roach protected function formatGedcomCalendarEscape(): string 8144a83f5d7SGreg Roach { 8154a83f5d7SGreg Roach return static::ESCAPE; 8164a83f5d7SGreg Roach } 8174a83f5d7SGreg Roach 8184a83f5d7SGreg Roach /** 8194a83f5d7SGreg Roach * Generate the %Y format for a date. 8204a83f5d7SGreg Roach * 8214a83f5d7SGreg Roach * @return string 8224a83f5d7SGreg Roach */ 8234a83f5d7SGreg Roach protected function formatLongYear(): string 8244a83f5d7SGreg Roach { 8254a83f5d7SGreg Roach return I18N::digits($this->year); 8264a83f5d7SGreg Roach } 8274a83f5d7SGreg Roach 8284a83f5d7SGreg Roach /** 8294a83f5d7SGreg Roach * Which months follows this one? Calendars with leap-months should provide their own implementation. 8304a83f5d7SGreg Roach * 83109482a55SGreg Roach * @return array<int> 8324a83f5d7SGreg Roach */ 8334a83f5d7SGreg Roach protected function nextMonth(): array 8344a83f5d7SGreg Roach { 8354a83f5d7SGreg Roach return [ 8364a83f5d7SGreg Roach $this->month === $this->calendar->monthsInYear() ? $this->nextYear($this->year) : $this->year, 8372cebb4b4SGreg Roach $this->month % $this->calendar->monthsInYear() + 1, 8384a83f5d7SGreg Roach ]; 8394a83f5d7SGreg Roach } 8404a83f5d7SGreg Roach 8414a83f5d7SGreg Roach /** 8424a83f5d7SGreg Roach * Get today’s date in the current calendar. 8434a83f5d7SGreg Roach * 84409482a55SGreg Roach * @return array<int> 8454a83f5d7SGreg Roach */ 8464a83f5d7SGreg Roach public function todayYmd(): array 8474a83f5d7SGreg Roach { 8484459dc9aSGreg Roach return $this->calendar->jdToYmd(Carbon::now()->julianDay()); 8494a83f5d7SGreg Roach } 8504a83f5d7SGreg Roach 8514a83f5d7SGreg Roach /** 8524a83f5d7SGreg Roach * Convert to today’s date. 8534a83f5d7SGreg Roach * 8544a83f5d7SGreg Roach * @return AbstractCalendarDate 8554a83f5d7SGreg Roach */ 8564a83f5d7SGreg Roach public function today(): AbstractCalendarDate 8574a83f5d7SGreg Roach { 8584a83f5d7SGreg Roach $tmp = clone $this; 8594a83f5d7SGreg Roach $ymd = $tmp->todayYmd(); 8604a83f5d7SGreg Roach $tmp->year = $ymd[0]; 8614a83f5d7SGreg Roach $tmp->month = $ymd[1]; 8624a83f5d7SGreg Roach $tmp->day = $ymd[2]; 8634a83f5d7SGreg Roach $tmp->setJdFromYmd(); 8644a83f5d7SGreg Roach 8654a83f5d7SGreg Roach return $tmp; 8664a83f5d7SGreg Roach } 8674a83f5d7SGreg Roach 8684a83f5d7SGreg Roach /** 8694a83f5d7SGreg Roach * Create a URL that links this date to the WT calendar 8704a83f5d7SGreg Roach * 8714a83f5d7SGreg Roach * @param string $date_format 87249d5f1d7SGreg Roach * @param Tree $tree 8734a83f5d7SGreg Roach * 8744a83f5d7SGreg Roach * @return string 8754a83f5d7SGreg Roach */ 87649d5f1d7SGreg Roach public function calendarUrl(string $date_format, Tree $tree): string 8774a83f5d7SGreg Roach { 878e364afe4SGreg Roach if ($this->day !== 0 && strpbrk($date_format, 'dDj')) { 8794a83f5d7SGreg Roach // If the format includes a day, and the date also includes a day, then use the day view 8804a83f5d7SGreg Roach $view = 'day'; 881e364afe4SGreg Roach } elseif ($this->month !== 0 && strpbrk($date_format, 'FMmn')) { 8824a83f5d7SGreg Roach // If the format includes a month, and the date also includes a month, then use the month view 8834a83f5d7SGreg Roach $view = 'month'; 8844a83f5d7SGreg Roach } else { 8854a83f5d7SGreg Roach // Use the year view 8864a83f5d7SGreg Roach $view = 'year'; 8874a83f5d7SGreg Roach } 8884a83f5d7SGreg Roach 889b00cb080SGreg Roach return route(CalendarPage::class, [ 8904a83f5d7SGreg Roach 'cal' => $this->calendar->gedcomCalendarEscape(), 8914a83f5d7SGreg Roach 'year' => $this->formatGedcomYear(), 8924a83f5d7SGreg Roach 'month' => $this->formatGedcomMonth(), 8934a83f5d7SGreg Roach 'day' => $this->formatGedcomDay(), 8944a83f5d7SGreg Roach 'view' => $view, 895d72b284aSGreg Roach 'tree' => $tree->name(), 8964a83f5d7SGreg Roach ]); 8974a83f5d7SGreg Roach } 8984a83f5d7SGreg Roach} 899