summaryrefslogtreecommitdiff
path: root/app/Date
diff options
context:
space:
mode:
authorGreg Roach <fisharebest@webtrees.net>2018-10-09 09:34:23 +0100
committerGreg Roach <fisharebest@webtrees.net>2018-10-09 10:28:12 +0100
commit4a83f5d742e43c37a641392fe50a3db201bffb30 (patch)
treeeafa95c5c20ef96de50ee707cfb483e6d0c28280 /app/Date
parent60c2f26d25c230dabfa31502bfa6d713019e9ce5 (diff)
downloadwebtrees-4a83f5d742e43c37a641392fe50a3db201bffb30.tar.gz
webtrees-4a83f5d742e43c37a641392fe50a3db201bffb30.tar.bz2
webtrees-4a83f5d742e43c37a641392fe50a3db201bffb30.zip
Calendar dates are abstract
Diffstat (limited to 'app/Date')
-rw-r--r--app/Date/AbstractCalendarDate.php (renamed from app/Date/CalendarDate.php)416
-rw-r--r--app/Date/AbstractGregorianJulianDate.php219
-rw-r--r--app/Date/FrenchDate.php13
-rw-r--r--app/Date/GregorianDate.php9
-rw-r--r--app/Date/HijriDate.php9
-rw-r--r--app/Date/JalaliDate.php9
-rw-r--r--app/Date/JewishDate.php21
-rw-r--r--app/Date/JulianDate.php26
-rw-r--r--app/Date/RomanDate.php9
9 files changed, 413 insertions, 318 deletions
diff --git a/app/Date/CalendarDate.php b/app/Date/AbstractCalendarDate.php
index 5014023d96..a72db1b906 100644
--- a/app/Date/CalendarDate.php
+++ b/app/Date/AbstractCalendarDate.php
@@ -34,36 +34,25 @@ use Fisharebest\Webtrees\I18N;
* midnight, solar midnight, sunset, sunrise, etc.), we convert on the basis of
* midday.
*/
-class CalendarDate
+class AbstractCalendarDate
{
- // Convert GEDCOM month names to month numbers
- const MONTH_ABBREVIATIONS = [
- '' => 0,
- 'JAN' => 1,
- 'FEB' => 2,
- 'MAR' => 3,
- 'APR' => 4,
- 'MAY' => 5,
- 'JUN' => 6,
- 'JUL' => 7,
- 'AUG' => 8,
- 'SEP' => 9,
- 'OCT' => 10,
- 'NOV' => 11,
- 'DEC' => 12,
- ];
+ // GEDCOM calendar escape
+ const ESCAPE = '@#DUNKNOWN@';
+
+ // Convert GEDCOM month names to month numbers.
+ const MONTH_ABBREVIATIONS = [];
/** @var CalendarInterface The calendar system used to represent this date */
protected $calendar;
/** @var int Year number */
- public $y;
+ public $year;
/** @var int Month number */
- public $m;
+ public $month;
/** @var int Day number */
- public $d;
+ public $day;
/** @var int Earliest Julian day number (start of month/year for imprecise dates) */
private $minimum_julian_day;
@@ -77,7 +66,7 @@ class CalendarDate
* day/month/year strings from a GEDCOM date
* another CalendarDate object
*
- * @param array|int|CalendarDate $date
+ * @param array|int|AbstractCalendarDate $date
*/
protected function __construct($date)
{
@@ -85,25 +74,25 @@ class CalendarDate
if (is_int($date)) {
$this->minimum_julian_day = $date;
$this->maximum_julian_day = $date;
- list($this->y, $this->m, $this->d) = $this->calendar->jdToYmd($date);
+ list($this->year, $this->month, $this->day) = $this->calendar->jdToYmd($date);
return;
}
// Construct from an array (of three gedcom-style strings: "1900", "FEB", "4")
if (is_array($date)) {
- $this->d = (int) $date[2];
+ $this->day = (int) $date[2];
if (array_key_exists($date[1], static::MONTH_ABBREVIATIONS)) {
- $this->m = static::MONTH_ABBREVIATIONS[$date[1]];
+ $this->month = static::MONTH_ABBREVIATIONS[$date[1]];
} else {
- $this->m = 0;
- $this->d = 0;
+ $this->month = 0;
+ $this->day = 0;
}
- $this->y = $this->extractYear($date[0]);
+ $this->year = $this->extractYear($date[0]);
// Our simple lookup table above does not take into account Adar and leap-years.
- if ($this->m === 6 && $this->calendar instanceof JewishCalendar && !$this->calendar->isLeapYear($this->y)) {
- $this->m = 7;
+ if ($this->month === 6 && $this->calendar instanceof JewishCalendar && !$this->calendar->isLeapYear($this->year)) {
+ $this->month = 7;
}
$this->setJdFromYmd();
@@ -117,269 +106,128 @@ class CalendarDate
// Construct from an equivalent xxxxDate object
if (get_class($this) == get_class($date)) {
- $this->y = $date->y;
- $this->m = $date->m;
- $this->d = $date->d;
+ $this->year = $date->year;
+ $this->month = $date->month;
+ $this->day = $date->day;
return;
}
// Not all dates can be converted
if (!$this->inValidRange()) {
- $this->y = 0;
- $this->m = 0;
- $this->d = 0;
+ $this->year = 0;
+ $this->month = 0;
+ $this->day = 0;
return;
}
// ...else construct an inequivalent xxxxDate object
- if ($date->y == 0) {
+ if ($date->year == 0) {
// Incomplete date - convert on basis of anniversary in current year
$today = $date->calendar->jdToYmd(unixtojd());
- $jd = $date->calendar->ymdToJd($today[0], $date->m, $date->d == 0 ? $today[2] : $date->d);
+ $jd = $date->calendar->ymdToJd($today[0], $date->month, $date->day == 0 ? $today[2] : $date->day);
} else {
// Complete date
$jd = intdiv($date->maximum_julian_day + $date->minimum_julian_day, 2);
}
- list($this->y, $this->m, $this->d) = $this->calendar->jdToYmd($jd);
+ list($this->year, $this->month, $this->day) = $this->calendar->jdToYmd($jd);
// New date has same precision as original date
- if ($date->y == 0) {
- $this->y = 0;
+ if ($date->year == 0) {
+ $this->year = 0;
}
- if ($date->m == 0) {
- $this->m = 0;
+ if ($date->month == 0) {
+ $this->month = 0;
}
- if ($date->d == 0) {
- $this->d = 0;
+ if ($date->day == 0) {
+ $this->day = 0;
}
$this->setJdFromYmd();
}
/**
- * @return int
+ * @return CalendarInterface
*/
- public function maximumJulianDay(): int
+ public function calendar(): CalendarInterface
{
- return $this->maximum_julian_day;
+ return $this->calendar();
}
/**
* @return int
*/
- public function minimumJulianDay(): int
+ public function maximumJulianDay(): int
{
- return $this->minimum_julian_day;
+ return $this->maximum_julian_day;
}
/**
- * Is the current year a leap year?
- *
- * @return bool
+ * @return int
*/
- public function isLeapYear(): bool
+ public function year(): int
{
- return $this->calendar->isLeapYear($this->y);
+ return $this->year;
}
/**
- * Set the object’s Julian day number from a potentially incomplete year/month/day
- *
- * @return void
+ * @return int
*/
- public function setJdFromYmd()
+ public function month(): int
{
- if ($this->y == 0) {
- $this->minimum_julian_day = 0;
- $this->maximum_julian_day = 0;
- } elseif ($this->m == 0) {
- $this->minimum_julian_day = $this->calendar->ymdToJd($this->y, 1, 1);
- $this->maximum_julian_day = $this->calendar->ymdToJd($this->nextYear($this->y), 1, 1) - 1;
- } elseif ($this->d == 0) {
- list($ny, $nm) = $this->nextMonth();
- $this->minimum_julian_day = $this->calendar->ymdToJd($this->y, $this->m, 1);
- $this->maximum_julian_day = $this->calendar->ymdToJd($ny, $nm, 1) - 1;
- } else {
- $this->minimum_julian_day = $this->calendar->ymdToJd($this->y, $this->m, $this->d);
- $this->maximum_julian_day = $this->minimum_julian_day;
- }
+ return $this->month;
}
/**
- * Full month name in nominative case.
- *
- * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars.
- *
- * @param int $month_number
- * @param bool $leap_year Some calendars use leap months
- *
- * @return string
+ * @return int
*/
- protected function monthNameNominativeCase(int $month_number, bool $leap_year): string
+ public function day(): int
{
- static $translated_month_names;
-
- if ($translated_month_names === null) {
- $translated_month_names = [
- 0 => '',
- 1 => I18N::translateContext('NOMINATIVE', 'January'),
- 2 => I18N::translateContext('NOMINATIVE', 'February'),
- 3 => I18N::translateContext('NOMINATIVE', 'March'),
- 4 => I18N::translateContext('NOMINATIVE', 'April'),
- 5 => I18N::translateContext('NOMINATIVE', 'May'),
- 6 => I18N::translateContext('NOMINATIVE', 'June'),
- 7 => I18N::translateContext('NOMINATIVE', 'July'),
- 8 => I18N::translateContext('NOMINATIVE', 'August'),
- 9 => I18N::translateContext('NOMINATIVE', 'September'),
- 10 => I18N::translateContext('NOMINATIVE', 'October'),
- 11 => I18N::translateContext('NOMINATIVE', 'November'),
- 12 => I18N::translateContext('NOMINATIVE', 'December'),
- ];
- }
-
- return $translated_month_names[$month_number];
+ return $this->day;
}
/**
- * Full month name in genitive case.
- *
- * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars.
- *
- * @param int $month_number
- * @param bool $leap_year Some calendars use leap months
- *
- * @return string
- */
- protected function monthNameGenitiveCase(int $month_number, bool $leap_year): string
- {
- static $translated_month_names;
-
- if ($translated_month_names === null) {
- $translated_month_names = [
- 0 => '',
- 1 => I18N::translateContext('GENITIVE', 'January'),
- 2 => I18N::translateContext('GENITIVE', 'February'),
- 3 => I18N::translateContext('GENITIVE', 'March'),
- 4 => I18N::translateContext('GENITIVE', 'April'),
- 5 => I18N::translateContext('GENITIVE', 'May'),
- 6 => I18N::translateContext('GENITIVE', 'June'),
- 7 => I18N::translateContext('GENITIVE', 'July'),
- 8 => I18N::translateContext('GENITIVE', 'August'),
- 9 => I18N::translateContext('GENITIVE', 'September'),
- 10 => I18N::translateContext('GENITIVE', 'October'),
- 11 => I18N::translateContext('GENITIVE', 'November'),
- 12 => I18N::translateContext('GENITIVE', 'December'),
- ];
- }
-
- return $translated_month_names[$month_number];
- }
-
- /**
- * Full month name in locative case.
- *
- * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars.
- *
- * @param int $month_number
- * @param bool $leap_year Some calendars use leap months
- *
- * @return string
+ * @return int
*/
- protected function monthNameLocativeCase(int $month_number, bool $leap_year): string
+ public function minimumJulianDay(): int
{
- static $translated_month_names;
-
- if ($translated_month_names === null) {
- $translated_month_names = [
- 0 => '',
- 1 => I18N::translateContext('LOCATIVE', 'January'),
- 2 => I18N::translateContext('LOCATIVE', 'February'),
- 3 => I18N::translateContext('LOCATIVE', 'March'),
- 4 => I18N::translateContext('LOCATIVE', 'April'),
- 5 => I18N::translateContext('LOCATIVE', 'May'),
- 6 => I18N::translateContext('LOCATIVE', 'June'),
- 7 => I18N::translateContext('LOCATIVE', 'July'),
- 8 => I18N::translateContext('LOCATIVE', 'August'),
- 9 => I18N::translateContext('LOCATIVE', 'September'),
- 10 => I18N::translateContext('LOCATIVE', 'October'),
- 11 => I18N::translateContext('LOCATIVE', 'November'),
- 12 => I18N::translateContext('LOCATIVE', 'December'),
- ];
- }
-
- return $translated_month_names[$month_number];
+ return $this->minimum_julian_day;
}
/**
- * Full month name in instrumental case.
- *
- * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars.
- *
- * @param int $month_number
- * @param bool $leap_year Some calendars use leap months
+ * Is the current year a leap year?
*
- * @return string
+ * @return bool
*/
- protected function monthNameInstrumentalCase(int $month_number, bool $leap_year): string
+ public function isLeapYear(): bool
{
- static $translated_month_names;
-
- if ($translated_month_names === null) {
- $translated_month_names = [
- 0 => '',
- 1 => I18N::translateContext('INSTRUMENTAL', 'January'),
- 2 => I18N::translateContext('INSTRUMENTAL', 'February'),
- 3 => I18N::translateContext('INSTRUMENTAL', 'March'),
- 4 => I18N::translateContext('INSTRUMENTAL', 'April'),
- 5 => I18N::translateContext('INSTRUMENTAL', 'May'),
- 6 => I18N::translateContext('INSTRUMENTAL', 'June'),
- 7 => I18N::translateContext('INSTRUMENTAL', 'July'),
- 8 => I18N::translateContext('INSTRUMENTAL', 'August'),
- 9 => I18N::translateContext('INSTRUMENTAL', 'September'),
- 10 => I18N::translateContext('INSTRUMENTAL', 'October'),
- 11 => I18N::translateContext('INSTRUMENTAL', 'November'),
- 12 => I18N::translateContext('INSTRUMENTAL', 'December'),
- ];
- }
-
- return $translated_month_names[$month_number];
+ return $this->calendar->isLeapYear($this->year);
}
/**
- * Abbreviated month name
- *
- * @param int $month_number
- * @param bool $leap_year Some calendars use leap months
+ * Set the object’s Julian day number from a potentially incomplete year/month/day
*
- * @return string
+ * @return void
*/
- protected function monthNameAbbreviated(int $month_number, bool $leap_year): string
+ public function setJdFromYmd()
{
- static $translated_month_names;
-
- if ($translated_month_names === null) {
- $translated_month_names = [
- 0 => '',
- 1 => I18N::translateContext('Abbreviation for January', 'Jan'),
- 2 => I18N::translateContext('Abbreviation for February', 'Feb'),
- 3 => I18N::translateContext('Abbreviation for March', 'Mar'),
- 4 => I18N::translateContext('Abbreviation for April', 'Apr'),
- 5 => I18N::translateContext('Abbreviation for May', 'May'),
- 6 => I18N::translateContext('Abbreviation for June', 'Jun'),
- 7 => I18N::translateContext('Abbreviation for July', 'Jul'),
- 8 => I18N::translateContext('Abbreviation for August', 'Aug'),
- 9 => I18N::translateContext('Abbreviation for September', 'Sep'),
- 10 => I18N::translateContext('Abbreviation for October', 'Oct'),
- 11 => I18N::translateContext('Abbreviation for November', 'Nov'),
- 12 => I18N::translateContext('Abbreviation for December', 'Dec'),
- ];
+ if ($this->year == 0) {
+ $this->minimum_julian_day = 0;
+ $this->maximum_julian_day = 0;
+ } elseif ($this->month == 0) {
+ $this->minimum_julian_day = $this->calendar->ymdToJd($this->year, 1, 1);
+ $this->maximum_julian_day = $this->calendar->ymdToJd($this->nextYear($this->year), 1, 1) - 1;
+ } elseif ($this->day == 0) {
+ list($ny, $nm) = $this->nextMonth();
+ $this->minimum_julian_day = $this->calendar->ymdToJd($this->year, $this->month, 1);
+ $this->maximum_julian_day = $this->calendar->ymdToJd($ny, $nm, 1) - 1;
+ } else {
+ $this->minimum_julian_day = $this->calendar->ymdToJd($this->year, $this->month, $this->day);
+ $this->maximum_julian_day = $this->minimum_julian_day;
}
-
- return $translated_month_names[$month_number];
}
/**
- * Full day of th eweek
+ * Full day of the week
*
* @param int $day_number
*
@@ -464,12 +312,12 @@ class CalendarDate
/**
* Compare two dates, for sorting
*
- * @param CalendarDate $d1
- * @param CalendarDate $d2
+ * @param AbstractCalendarDate $d1
+ * @param AbstractCalendarDate $d2
*
* @return int
*/
- public static function compare(CalendarDate $d1, CalendarDate $d2): int
+ public static function compare(AbstractCalendarDate $d1, AbstractCalendarDate $d2): int
{
if ($d1->maximum_julian_day < $d2->minimum_julian_day) {
return -1;
@@ -484,19 +332,18 @@ class CalendarDate
/**
* Calculate the years/months/days between this date and another date.
- *
* Results assume you add the days first, then the months.
* 4 February -> 3 July is 27 days (3 March) and 4 months.
* It is not 4 months (4 June) and 29 days.
*
- * @param CalendarDate $date
+ * @param AbstractCalendarDate $date
*
* @return int[] Age in years/months/days
*/
- public function ageDifference(CalendarDate $date): array
+ public function ageDifference(AbstractCalendarDate $date): array
{
// Incomplete dates
- if ($this->y === 0 || $date->y === 0) {
+ if ($this->year === 0 || $date->year === 0) {
return [-1, -1, -1];
}
@@ -536,7 +383,7 @@ class CalendarDate
*/
public function getAge(int $jd): int
{
- if ($this->y == 0 || $jd == 0) {
+ if ($this->year == 0 || $jd == 0) {
return 0;
}
if ($this->minimum_julian_day < $jd && $this->maximum_julian_day > $jd) {
@@ -546,14 +393,13 @@ class CalendarDate
return 0;
}
list($y, $m, $d) = $this->calendar->jdToYmd($jd);
- $dy = $y - $this->y;
- $dm = $m - max($this->m, 1);
- $dd = $d - max($this->d, 1);
+ $dy = $y - $this->year;
+ $dm = $m - max($this->month, 1);
+ $dd = $d - max($this->day, 1);
if ($dd < 0) {
$dm--;
}
if ($dm < 0) {
- $dm += $this->calendar->monthsInYear();
$dy--;
}
@@ -571,7 +417,7 @@ class CalendarDate
*/
public function getAgeFull(int $jd): string
{
- if ($this->y == 0 || $jd == 0) {
+ if ($this->year == 0 || $jd == 0) {
return '';
}
if ($this->minimum_julian_day < $jd && $this->maximum_julian_day > $jd) {
@@ -584,9 +430,9 @@ class CalendarDate
return '<i class="icon-warning"></i>';
}
list($y, $m, $d) = $this->calendar->jdToYmd($jd);
- $dy = $y - $this->y;
- $dm = $m - max($this->m, 1);
- $dd = $d - max($this->d, 1);
+ $dy = $y - $this->year;
+ $dm = $m - max($this->month, 1);
+ $dd = $d - max($this->day, 1);
if ($dd < 0) {
$dm--;
}
@@ -613,9 +459,9 @@ class CalendarDate
*
* @param string $calendar
*
- * @return CalendarDate
+ * @return AbstractCalendarDate
*/
- public function convertToCalendar(string $calendar): CalendarDate
+ public function convertToCalendar(string $calendar): AbstractCalendarDate
{
switch ($calendar) {
case 'gregorian':
@@ -663,7 +509,7 @@ class CalendarDate
public function daysInMonth(): int
{
try {
- return $this->calendar->daysInMonth($this->y, $this->m);
+ return $this->calendar->daysInMonth($this->year, $this->month);
} catch (\InvalidArgumentException $ex) {
DebugBar::addThrowable($ex);
@@ -694,11 +540,11 @@ class CalendarDate
public function format(string $format, string $qualifier = ''): string
{
// Don’t show exact details for inexact dates
- if (!$this->d) {
+ if (!$this->day) {
// The comma is for US "M D, Y" dates
$format = preg_replace('/%[djlDNSwz][,]?/', '', $format);
}
- if (!$this->m) {
+ if (!$this->month) {
$format = str_replace([
'%F',
'%m',
@@ -707,7 +553,7 @@ class CalendarDate
'%t',
], '', $format);
}
- if (!$this->y) {
+ if (!$this->year) {
$format = str_replace([
'%t',
'%L',
@@ -717,10 +563,10 @@ class CalendarDate
], '', $format);
}
// If we’ve trimmed the format, also trim the punctuation
- if (!$this->d || !$this->m || !$this->y) {
+ if (!$this->day || !$this->month || !$this->year) {
$format = trim($format, ',. ;/-');
}
- if ($this->d && preg_match('/%[djlDNSwz]/', $format)) {
+ if ($this->day && preg_match('/%[djlDNSwz]/', $format)) {
// If we have a day-number *and* we are being asked to display it, then genitive
$case = 'GENITIVE';
} else {
@@ -798,7 +644,7 @@ class CalendarDate
break;
// These 4 extensions are useful for re-formatting gedcom dates.
case '%@':
- $format = str_replace($match, $this->calendar->gedcomCalendarEscape(), $format);
+ $format = str_replace($match, $this->formatGedcomCalendarEscape(), $format);
break;
case '%A':
$format = str_replace($match, $this->formatGedcomDay(), $format);
@@ -822,11 +668,11 @@ class CalendarDate
*/
protected function formatDayZeros(): string
{
- if ($this->d > 9) {
- return I18N::digits($this->d);
+ if ($this->day > 9) {
+ return I18N::digits($this->day);
}
- return I18N::digits('0' . $this->d);
+ return I18N::digits('0' . $this->day);
}
/**
@@ -836,7 +682,7 @@ class CalendarDate
*/
protected function formatDay(): string
{
- return I18N::digits($this->d);
+ return I18N::digits($this->day);
}
/**
@@ -886,7 +732,7 @@ class CalendarDate
*/
protected function formatDayOfYear(): string
{
- return I18N::digits($this->minimum_julian_day - $this->calendar->ymdToJd($this->y, 1, 1));
+ return I18N::digits($this->minimum_julian_day - $this->calendar->ymdToJd($this->year, 1, 1));
}
/**
@@ -896,7 +742,7 @@ class CalendarDate
*/
protected function formatMonth(): string
{
- return I18N::digits($this->m);
+ return I18N::digits($this->month);
}
/**
@@ -906,11 +752,11 @@ class CalendarDate
*/
protected function formatMonthZeros(): string
{
- if ($this->m > 9) {
- return I18N::digits($this->m);
+ if ($this->month > 9) {
+ return I18N::digits($this->month);
}
- return I18N::digits('0' . $this->m);
+ return I18N::digits('0' . $this->month);
}
/**
@@ -924,13 +770,13 @@ class CalendarDate
{
switch ($case) {
case 'GENITIVE':
- return $this->monthNameGenitiveCase($this->m, $this->isLeapYear());
+ return $this->monthNameGenitiveCase($this->month, $this->isLeapYear());
case 'NOMINATIVE':
- return $this->monthNameNominativeCase($this->m, $this->isLeapYear());
+ return $this->monthNameNominativeCase($this->month, $this->isLeapYear());
case 'LOCATIVE':
- return $this->monthNameLocativeCase($this->m, $this->isLeapYear());
+ return $this->monthNameLocativeCase($this->month, $this->isLeapYear());
case 'INSTRUMENTAL':
- return $this->monthNameInstrumentalCase($this->m, $this->isLeapYear());
+ return $this->monthNameInstrumentalCase($this->month, $this->isLeapYear());
default:
throw new \InvalidArgumentException($case);
}
@@ -943,7 +789,7 @@ class CalendarDate
*/
protected function formatShortMonth(): string
{
- return $this->monthNameAbbreviated($this->m, $this->isLeapYear());
+ return $this->monthNameAbbreviated($this->month, $this->isLeapYear());
}
/**
@@ -965,11 +811,11 @@ class CalendarDate
*/
protected function formatGedcomDay(): string
{
- if ($this->d == 0) {
+ if ($this->day == 0) {
return '';
}
- return sprintf('%02d', $this->d);
+ return sprintf('%02d', $this->day);
}
/**
@@ -980,11 +826,11 @@ class CalendarDate
protected function formatGedcomMonth(): string
{
// Our simple lookup table doesn't work correctly for Adar on leap years
- if ($this->m == 7 && $this->calendar instanceof JewishCalendar && !$this->calendar->isLeapYear($this->y)) {
+ if ($this->month == 7 && $this->calendar instanceof JewishCalendar && !$this->calendar->isLeapYear($this->year)) {
return 'ADR';
}
- return array_search($this->m, static::MONTH_ABBREVIATIONS);
+ return array_search($this->month, static::MONTH_ABBREVIATIONS);
}
/**
@@ -994,11 +840,21 @@ class CalendarDate
*/
protected function formatGedcomYear(): string
{
- if ($this->y == 0) {
+ if ($this->year == 0) {
return '';
}
- return sprintf('%04d', $this->y);
+ return sprintf('%04d', $this->year);
+ }
+
+ /**
+ * Generate the %@ format for a calendar escape.
+ *
+ * @return string
+ */
+ protected function formatGedcomCalendarEscape(): string
+ {
+ return static::ESCAPE;
}
/**
@@ -1008,7 +864,7 @@ class CalendarDate
*/
protected function formatLongYear(): string
{
- return I18N::digits($this->y);
+ return I18N::digits($this->year);
}
/**
@@ -1019,8 +875,8 @@ class CalendarDate
protected function nextMonth(): array
{
return [
- $this->m === $this->calendar->monthsInYear() ? $this->nextYear($this->y) : $this->y,
- ($this->m % $this->calendar->monthsInYear()) + 1,
+ $this->month === $this->calendar->monthsInYear() ? $this->nextYear($this->year) : $this->year,
+ ($this->month % $this->calendar->monthsInYear()) + 1,
];
}
@@ -1037,15 +893,15 @@ class CalendarDate
/**
* Convert to today’s date.
*
- * @return CalendarDate
+ * @return AbstractCalendarDate
*/
- public function today(): CalendarDate
+ public function today(): AbstractCalendarDate
{
- $tmp = clone $this;
- $ymd = $tmp->todayYmd();
- $tmp->y = $ymd[0];
- $tmp->m = $ymd[1];
- $tmp->d = $ymd[2];
+ $tmp = clone $this;
+ $ymd = $tmp->todayYmd();
+ $tmp->year = $ymd[0];
+ $tmp->month = $ymd[1];
+ $tmp->day = $ymd[2];
$tmp->setJdFromYmd();
return $tmp;
@@ -1060,10 +916,10 @@ class CalendarDate
*/
public function calendarUrl(string $date_format): string
{
- if (strpbrk($date_format, 'dDj') && $this->d) {
+ if (strpbrk($date_format, 'dDj') && $this->day) {
// If the format includes a day, and the date also includes a day, then use the day view
$view = 'day';
- } elseif (strpbrk($date_format, 'FMmn') && $this->m) {
+ } elseif (strpbrk($date_format, 'FMmn') && $this->month) {
// If the format includes a month, and the date also includes a month, then use the month view
$view = 'month';
} else {
diff --git a/app/Date/AbstractGregorianJulianDate.php b/app/Date/AbstractGregorianJulianDate.php
new file mode 100644
index 0000000000..efcf7244aa
--- /dev/null
+++ b/app/Date/AbstractGregorianJulianDate.php
@@ -0,0 +1,219 @@
+<?php
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2018 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Date;
+
+use Fisharebest\ExtCalendar\CalendarInterface;
+use Fisharebest\ExtCalendar\JewishCalendar;
+use Fisharebest\Webtrees\DebugBar;
+use Fisharebest\Webtrees\I18N;
+
+/**
+ * Common definitions for Gregorian and Julian dates.
+ */
+abstract class AbstractGregorianJulianDate extends AbstractCalendarDate
+{
+ // Convert GEDCOM month names to month numbers
+ const MONTH_ABBREVIATIONS = [
+ '' => 0,
+ 'JAN' => 1,
+ 'FEB' => 2,
+ 'MAR' => 3,
+ 'APR' => 4,
+ 'MAY' => 5,
+ 'JUN' => 6,
+ 'JUL' => 7,
+ 'AUG' => 8,
+ 'SEP' => 9,
+ 'OCT' => 10,
+ 'NOV' => 11,
+ 'DEC' => 12,
+ ];
+
+ /**
+ * Full month name in nominative case.
+ *
+ * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars.
+ *
+ * @param int $month_number
+ * @param bool $leap_year Some calendars use leap months
+ *
+ * @return string
+ */
+ protected function monthNameNominativeCase(int $month_number, bool $leap_year): string
+ {
+ static $translated_month_names;
+
+ if ($translated_month_names === null) {
+ $translated_month_names = [
+ 0 => '',
+ 1 => I18N::translateContext('NOMINATIVE', 'January'),
+ 2 => I18N::translateContext('NOMINATIVE', 'February'),
+ 3 => I18N::translateContext('NOMINATIVE', 'March'),
+ 4 => I18N::translateContext('NOMINATIVE', 'April'),
+ 5 => I18N::translateContext('NOMINATIVE', 'May'),
+ 6 => I18N::translateContext('NOMINATIVE', 'June'),
+ 7 => I18N::translateContext('NOMINATIVE', 'July'),
+ 8 => I18N::translateContext('NOMINATIVE', 'August'),
+ 9 => I18N::translateContext('NOMINATIVE', 'September'),
+ 10 => I18N::translateContext('NOMINATIVE', 'October'),
+ 11 => I18N::translateContext('NOMINATIVE', 'November'),
+ 12 => I18N::translateContext('NOMINATIVE', 'December'),
+ ];
+ }
+
+ return $translated_month_names[$month_number];
+ }
+
+ /**
+ * Full month name in genitive case.
+ *
+ * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars.
+ *
+ * @param int $month_number
+ * @param bool $leap_year Some calendars use leap months
+ *
+ * @return string
+ */
+ protected function monthNameGenitiveCase(int $month_number, bool $leap_year): string
+ {
+ static $translated_month_names;
+
+ if ($translated_month_names === null) {
+ $translated_month_names = [
+ 0 => '',
+ 1 => I18N::translateContext('GENITIVE', 'January'),
+ 2 => I18N::translateContext('GENITIVE', 'February'),
+ 3 => I18N::translateContext('GENITIVE', 'March'),
+ 4 => I18N::translateContext('GENITIVE', 'April'),
+ 5 => I18N::translateContext('GENITIVE', 'May'),
+ 6 => I18N::translateContext('GENITIVE', 'June'),
+ 7 => I18N::translateContext('GENITIVE', 'July'),
+ 8 => I18N::translateContext('GENITIVE', 'August'),
+ 9 => I18N::translateContext('GENITIVE', 'September'),
+ 10 => I18N::translateContext('GENITIVE', 'October'),
+ 11 => I18N::translateContext('GENITIVE', 'November'),
+ 12 => I18N::translateContext('GENITIVE', 'December'),
+ ];
+ }
+
+ return $translated_month_names[$month_number];
+ }
+
+ /**
+ * Full month name in locative case.
+ *
+ * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars.
+ *
+ * @param int $month_number
+ * @param bool $leap_year Some calendars use leap months
+ *
+ * @return string
+ */
+ protected function monthNameLocativeCase(int $month_number, bool $leap_year): string
+ {
+ static $translated_month_names;
+
+ if ($translated_month_names === null) {
+ $translated_month_names = [
+ 0 => '',
+ 1 => I18N::translateContext('LOCATIVE', 'January'),
+ 2 => I18N::translateContext('LOCATIVE', 'February'),
+ 3 => I18N::translateContext('LOCATIVE', 'March'),
+ 4 => I18N::translateContext('LOCATIVE', 'April'),
+ 5 => I18N::translateContext('LOCATIVE', 'May'),
+ 6 => I18N::translateContext('LOCATIVE', 'June'),
+ 7 => I18N::translateContext('LOCATIVE', 'July'),
+ 8 => I18N::translateContext('LOCATIVE', 'August'),
+ 9 => I18N::translateContext('LOCATIVE', 'September'),
+ 10 => I18N::translateContext('LOCATIVE', 'October'),
+ 11 => I18N::translateContext('LOCATIVE', 'November'),
+ 12 => I18N::translateContext('LOCATIVE', 'December'),
+ ];
+ }
+
+ return $translated_month_names[$month_number];
+ }
+
+ /**
+ * Full month name in instrumental case.
+ *
+ * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars.
+ *
+ * @param int $month_number
+ * @param bool $leap_year Some calendars use leap months
+ *
+ * @return string
+ */
+ protected function monthNameInstrumentalCase(int $month_number, bool $leap_year): string
+ {
+ static $translated_month_names;
+
+ if ($translated_month_names === null) {
+ $translated_month_names = [
+ 0 => '',
+ 1 => I18N::translateContext('INSTRUMENTAL', 'January'),
+ 2 => I18N::translateContext('INSTRUMENTAL', 'February'),
+ 3 => I18N::translateContext('INSTRUMENTAL', 'March'),
+ 4 => I18N::translateContext('INSTRUMENTAL', 'April'),
+ 5 => I18N::translateContext('INSTRUMENTAL', 'May'),
+ 6 => I18N::translateContext('INSTRUMENTAL', 'June'),
+ 7 => I18N::translateContext('INSTRUMENTAL', 'July'),
+ 8 => I18N::translateContext('INSTRUMENTAL', 'August'),
+ 9 => I18N::translateContext('INSTRUMENTAL', 'September'),
+ 10 => I18N::translateContext('INSTRUMENTAL', 'October'),
+ 11 => I18N::translateContext('INSTRUMENTAL', 'November'),
+ 12 => I18N::translateContext('INSTRUMENTAL', 'December'),
+ ];
+ }
+
+ return $translated_month_names[$month_number];
+ }
+
+ /**
+ * Abbreviated month name
+ *
+ * @param int $month_number
+ * @param bool $leap_year Some calendars use leap months
+ *
+ * @return string
+ */
+ protected function monthNameAbbreviated(int $month_number, bool $leap_year): string
+ {
+ static $translated_month_names;
+
+ if ($translated_month_names === null) {
+ $translated_month_names = [
+ 0 => '',
+ 1 => I18N::translateContext('Abbreviation for January', 'Jan'),
+ 2 => I18N::translateContext('Abbreviation for February', 'Feb'),
+ 3 => I18N::translateContext('Abbreviation for March', 'Mar'),
+ 4 => I18N::translateContext('Abbreviation for April', 'Apr'),
+ 5 => I18N::translateContext('Abbreviation for May', 'May'),
+ 6 => I18N::translateContext('Abbreviation for June', 'Jun'),
+ 7 => I18N::translateContext('Abbreviation for July', 'Jul'),
+ 8 => I18N::translateContext('Abbreviation for August', 'Aug'),
+ 9 => I18N::translateContext('Abbreviation for September', 'Sep'),
+ 10 => I18N::translateContext('Abbreviation for October', 'Oct'),
+ 11 => I18N::translateContext('Abbreviation for November', 'Nov'),
+ 12 => I18N::translateContext('Abbreviation for December', 'Dec'),
+ ];
+ }
+
+ return $translated_month_names[$month_number];
+ }
+}
diff --git a/app/Date/FrenchDate.php b/app/Date/FrenchDate.php
index fc668d8704..c5caae5193 100644
--- a/app/Date/FrenchDate.php
+++ b/app/Date/FrenchDate.php
@@ -22,10 +22,13 @@ use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Services\RomanNumeralsService;
/**
- * Definitions for the French Republican calendar
+ * Definitions for French Republican dates.
*/
-class FrenchDate extends CalendarDate
+class FrenchDate extends AbstractCalendarDate
{
+ // GEDCOM calendar escape
+ const ESCAPE = '@#DFRENCH R@';
+
// Convert GEDCOM month names to month numbers
const MONTH_ABBREVIATIONS = [
'' => 0,
@@ -53,7 +56,7 @@ class FrenchDate extends CalendarDate
* day/month/year strings from a GEDCOM date
* another CalendarDate object
*
- * @param array|int|CalendarDate $date
+ * @param array|int|AbstractCalendarDate $date
*/
public function __construct($date)
{
@@ -266,7 +269,7 @@ class FrenchDate extends CalendarDate
}
/**
- * Full day of th eweek
+ * Full day of the week
*
* @param int $day_number
*
@@ -323,6 +326,6 @@ class FrenchDate extends CalendarDate
*/
protected function formatLongYear(): string
{
- return $this->roman_numerals_service->numberToRomanNumerals($this->y);
+ return $this->roman_numerals_service->numberToRomanNumerals($this->year);
}
}
diff --git a/app/Date/GregorianDate.php b/app/Date/GregorianDate.php
index ca405608b4..9a8d1fa053 100644
--- a/app/Date/GregorianDate.php
+++ b/app/Date/GregorianDate.php
@@ -20,17 +20,20 @@ namespace Fisharebest\Webtrees\Date;
use Fisharebest\ExtCalendar\GregorianCalendar;
/**
- * Definitions for the Gregorian calendar
+ * Definitions for Gregorian dates.
*/
-class GregorianDate extends CalendarDate
+class GregorianDate extends AbstractGregorianJulianDate
{
+ // GEDCOM calendar escape
+ const ESCAPE = '@#DGREGORIAN@';
+
/**
* Create a date from either:
* a Julian day number
* day/month/year strings from a GEDCOM date
* another CalendarDate object
*
- * @param array|int|CalendarDate $date
+ * @param array|int|AbstractCalendarDate $date
*/
public function __construct($date)
{
diff --git a/app/Date/HijriDate.php b/app/Date/HijriDate.php
index b228b55aac..20088e169d 100644
--- a/app/Date/HijriDate.php
+++ b/app/Date/HijriDate.php
@@ -21,13 +21,16 @@ use Fisharebest\ExtCalendar\ArabicCalendar;
use Fisharebest\Webtrees\I18N;
/**
- * Definitions for the Hijri calendar.
+ * Definitions for Hijri dates.
*
* Note that these are "theoretical" dates.
* "True" dates are based on local lunar observations, and can be a +/- one day.
*/
-class HijriDate extends CalendarDate
+class HijriDate extends AbstractCalendarDate
{
+ // GEDCOM calendar escape
+ const ESCAPE = '@#DHIJRI@';
+
// Convert GEDCOM month names to month numbers
const MONTH_ABBREVIATIONS = [
'' => 0,
@@ -51,7 +54,7 @@ class HijriDate extends CalendarDate
* day/month/year strings from a GEDCOM date
* another CalendarDate object
*
- * @param array|int|CalendarDate $date
+ * @param array|int|AbstractCalendarDate $date
*/
public function __construct($date)
{
diff --git a/app/Date/JalaliDate.php b/app/Date/JalaliDate.php
index d482e59cc9..187218800b 100644
--- a/app/Date/JalaliDate.php
+++ b/app/Date/JalaliDate.php
@@ -21,10 +21,13 @@ use Fisharebest\ExtCalendar\PersianCalendar;
use Fisharebest\Webtrees\I18N;
/**
- * Definitions for the Jalali calendar
+ * Definitions for Jalali dates.
*/
-class JalaliDate extends CalendarDate
+class JalaliDate extends AbstractCalendarDate
{
+ // GEDCOM calendar escape
+ const ESCAPE = '@#DJALALI@';
+
// Convert GEDCOM month names to month numbers
const MONTH_ABBREVIATIONS = [
'' => 0,
@@ -48,7 +51,7 @@ class JalaliDate extends CalendarDate
* day/month/year strings from a GEDCOM date
* another CalendarDate object
*
- * @param array|int|CalendarDate $date
+ * @param array|int|AbstractCalendarDate $date
*/
public function __construct($date)
{
diff --git a/app/Date/JewishDate.php b/app/Date/JewishDate.php
index 478154f8a2..8a44efbdf8 100644
--- a/app/Date/JewishDate.php
+++ b/app/Date/JewishDate.php
@@ -23,8 +23,11 @@ use Fisharebest\Webtrees\I18N;
/**
* Definitions for the Jewish calendar
*/
-class JewishDate extends CalendarDate
+class JewishDate extends AbstractCalendarDate
{
+ // GEDCOM calendar escape
+ const ESCAPE = '@#DHEBREW@';
+
// Convert GEDCOM month names to month numbers
const MONTH_ABBREVIATIONS = [
'' => 0,
@@ -49,7 +52,7 @@ class JewishDate extends CalendarDate
* day/month/year strings from a GEDCOM date
* another CalendarDate object
*
- * @param array|int|CalendarDate $date
+ * @param array|int|AbstractCalendarDate $date
*/
public function __construct($date)
{
@@ -65,7 +68,7 @@ class JewishDate extends CalendarDate
protected function formatDay(): string
{
if (WT_LOCALE === 'he' || WT_LOCALE === 'yi') {
- return (new JewishCalendar())->numberToHebrewNumerals($this->d, true);
+ return (new JewishCalendar())->numberToHebrewNumerals($this->day, true);
}
return parent::formatDay();
@@ -82,7 +85,7 @@ class JewishDate extends CalendarDate
protected function formatShortYear(): string
{
if (WT_LOCALE === 'he' || WT_LOCALE === 'yi') {
- return (new JewishCalendar())->numberToHebrewNumerals($this->y, false);
+ return (new JewishCalendar())->numberToHebrewNumerals($this->year, false);
}
return parent::formatLongYear();
@@ -96,7 +99,7 @@ class JewishDate extends CalendarDate
protected function formatLongYear(): string
{
if (WT_LOCALE === 'he' || WT_LOCALE === 'yi') {
- return (new JewishCalendar())->numberToHebrewNumerals($this->y, true);
+ return (new JewishCalendar())->numberToHebrewNumerals($this->year, true);
}
return parent::formatLongYear();
@@ -334,16 +337,16 @@ class JewishDate extends CalendarDate
*/
protected function nextMonth(): array
{
- if ($this->m == 6 && !$this->isLeapYear()) {
+ if ($this->month == 6 && !$this->isLeapYear()) {
return [
- $this->y,
+ $this->year,
8,
];
}
return [
- $this->y + ($this->m == 13 ? 1 : 0),
- ($this->m % 13) + 1,
+ $this->year + ($this->month == 13 ? 1 : 0),
+ ($this->month % 13) + 1,
];
}
}
diff --git a/app/Date/JulianDate.php b/app/Date/JulianDate.php
index 91d1eeb752..9d49a98baa 100644
--- a/app/Date/JulianDate.php
+++ b/app/Date/JulianDate.php
@@ -21,11 +21,13 @@ use Fisharebest\ExtCalendar\JulianCalendar;
use Fisharebest\Webtrees\I18N;
/**
- * Definitions for the Julian Proleptic calendar
- * (Proleptic means we extend it backwards, prior to its introduction in 46BC)
+ * Definitions for proleptic Julian dates.
*/
-class JulianDate extends CalendarDate
+class JulianDate extends AbstractGregorianJulianDate
{
+ // GEDCOM calendar escape
+ const ESCAPE = '@#DJULIAN@';
+
/** @var bool True for dates recorded in new-style/old-style format, e.g. 2 FEB 1743/44 */
private $new_old_style = false;
@@ -35,7 +37,7 @@ class JulianDate extends CalendarDate
* day/month/year strings from a GEDCOM date
* another CalendarDate object
*
- * @param array|int|CalendarDate $date
+ * @param array|int|AbstractCalendarDate $date
*/
public function __construct($date)
{
@@ -89,17 +91,17 @@ class JulianDate extends CalendarDate
*/
protected function formatLongYear(): string
{
- if ($this->y < 0) {
+ if ($this->year < 0) {
return /* I18N: BCE=Before the Common Era, for Julian years < 0. See http://en.wikipedia.org/wiki/Common_Era */
- I18N::translate('%s&nbsp;BCE', I18N::digits(-$this->y));
+ I18N::translate('%s&nbsp;BCE', I18N::digits(-$this->year));
}
if ($this->new_old_style) {
- return I18N::translate('%s&nbsp;CE', I18N::digits(sprintf('%d/%02d', $this->y - 1, $this->y % 100)));
+ return I18N::translate('%s&nbsp;CE', I18N::digits(sprintf('%d/%02d', $this->year - 1, $this->year % 100)));
}
/* I18N: CE=Common Era, for Julian years > 0. See http://en.wikipedia.org/wiki/Common_Era */
- return I18N::translate('%s&nbsp;CE', I18N::digits($this->y));
+ return I18N::translate('%s&nbsp;CE', I18N::digits($this->year));
}
/**
@@ -109,14 +111,14 @@ class JulianDate extends CalendarDate
*/
protected function formatGedcomYear(): string
{
- if ($this->y < 0) {
- return sprintf('%04d B.C.', -$this->y);
+ if ($this->year < 0) {
+ return sprintf('%04d B.C.', -$this->year);
}
if ($this->new_old_style) {
- return sprintf('%04d/%02d', $this->y - 1, $this->y % 100);
+ return sprintf('%04d/%02d', $this->year - 1, $this->year % 100);
}
- return sprintf('%04d', $this->y);
+ return sprintf('%04d', $this->year);
}
}
diff --git a/app/Date/RomanDate.php b/app/Date/RomanDate.php
index 6be538a7b6..39f05b3415 100644
--- a/app/Date/RomanDate.php
+++ b/app/Date/RomanDate.php
@@ -18,7 +18,7 @@ declare(strict_types=1);
namespace Fisharebest\Webtrees\Date;
/**
- * Definitions for the Roman calendar
+ * Definitions for Roman dtes.
*
* The 5.5.1 gedcom spec mentions this calendar, but gives no details of
* how it is to be represented.... This class is just a place holder so that
@@ -26,6 +26,9 @@ namespace Fisharebest\Webtrees\Date;
*/
class RomanDate extends JulianDate
{
+ // GEDCOM calendar escape
+ const ESCAPE = '@#DROMAN@';
+
/**
* Generate the %E format for a date.
*
@@ -33,7 +36,7 @@ class RomanDate extends JulianDate
*/
protected function formatGedcomYear(): string
{
- return sprintf('%04dAUC', $this->y);
+ return sprintf('%04dAUC', $this->year);
}
/**
@@ -43,6 +46,6 @@ class RomanDate extends JulianDate
*/
protected function formatLongYear(): string
{
- return $this->y . 'AUC';
+ return $this->year . 'AUC';
}
}