@@ -32,80 +32,80 @@ discard block |
||
32 | 32 | * midday. |
33 | 33 | */ |
34 | 34 | class CalendarDate { |
35 | - /** @var int[] Convert GEDCOM month names to month numbers */ |
|
36 | - public static $MONTH_ABBREV = array('' => 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); |
|
37 | - |
|
38 | - /** @var string[] Convert numbers to/from roman numerals */ |
|
39 | - protected static $roman_numerals = array(1000 => 'M', 900 => 'CM', 500 => 'D', 400 => 'CD', 100 => 'C', 90 => 'XC', 50 => 'L', 40 => 'XL', 10 => 'X', 9 => 'IX', 5 => 'V', 4 => 'IV', 1 => 'I'); |
|
40 | - |
|
41 | - /** @var CalendarInterface The calendar system used to represent this date */ |
|
42 | - protected $calendar; |
|
43 | - |
|
44 | - /** @var int Year number */ |
|
45 | - public $y; |
|
46 | - |
|
47 | - /** @var int Month number */ |
|
48 | - public $m; |
|
49 | - |
|
50 | - /** @var int Day number */ |
|
51 | - public $d; |
|
52 | - |
|
53 | - /** @var int Earliest Julian day number (start of month/year for imprecise dates) */ |
|
54 | - public $minJD; |
|
55 | - |
|
56 | - /** @var int Latest Julian day number (end of month/year for imprecise dates) */ |
|
57 | - public $maxJD; |
|
58 | - |
|
59 | - /** |
|
60 | - * Create a date from either: |
|
61 | - * a Julian day number |
|
62 | - * day/month/year strings from a GEDCOM date |
|
63 | - * another CalendarDate object |
|
64 | - * |
|
65 | - * @param array|int|CalendarDate $date |
|
66 | - */ |
|
67 | - public function __construct($date) { |
|
68 | - // Construct from an integer (a julian day number) |
|
69 | - if (is_integer($date)) { |
|
70 | - $this->minJD = $date; |
|
71 | - $this->maxJD = $date; |
|
72 | - list($this->y, $this->m, $this->d) = $this->calendar->jdToYmd($date); |
|
73 | - |
|
74 | - return; |
|
75 | - } |
|
76 | - |
|
77 | - // Construct from an array (of three gedcom-style strings: "1900", "FEB", "4") |
|
78 | - if (is_array($date)) { |
|
79 | - $this->d = (int) $date[2]; |
|
80 | - if (array_key_exists($date[1], static::$MONTH_ABBREV)) { |
|
81 | - $this->m = static::$MONTH_ABBREV[$date[1]]; |
|
82 | - } else { |
|
83 | - $this->m = 0; |
|
84 | - $this->d = 0; |
|
85 | - } |
|
86 | - $this->y = $this->extractYear($date[0]); |
|
87 | - |
|
88 | - // Our simple lookup table above does not take into account Adar and leap-years. |
|
89 | - if ($this->m === 6 && $this->calendar instanceof JewishCalendar && !$this->calendar->isLeapYear($this->y)) { |
|
90 | - $this->m = 7; |
|
91 | - } |
|
92 | - |
|
93 | - $this->setJdFromYmd(); |
|
94 | - |
|
95 | - return; |
|
96 | - } |
|
35 | + /** @var int[] Convert GEDCOM month names to month numbers */ |
|
36 | + public static $MONTH_ABBREV = array('' => 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); |
|
37 | + |
|
38 | + /** @var string[] Convert numbers to/from roman numerals */ |
|
39 | + protected static $roman_numerals = array(1000 => 'M', 900 => 'CM', 500 => 'D', 400 => 'CD', 100 => 'C', 90 => 'XC', 50 => 'L', 40 => 'XL', 10 => 'X', 9 => 'IX', 5 => 'V', 4 => 'IV', 1 => 'I'); |
|
40 | + |
|
41 | + /** @var CalendarInterface The calendar system used to represent this date */ |
|
42 | + protected $calendar; |
|
43 | + |
|
44 | + /** @var int Year number */ |
|
45 | + public $y; |
|
46 | + |
|
47 | + /** @var int Month number */ |
|
48 | + public $m; |
|
49 | + |
|
50 | + /** @var int Day number */ |
|
51 | + public $d; |
|
52 | + |
|
53 | + /** @var int Earliest Julian day number (start of month/year for imprecise dates) */ |
|
54 | + public $minJD; |
|
55 | + |
|
56 | + /** @var int Latest Julian day number (end of month/year for imprecise dates) */ |
|
57 | + public $maxJD; |
|
58 | + |
|
59 | + /** |
|
60 | + * Create a date from either: |
|
61 | + * a Julian day number |
|
62 | + * day/month/year strings from a GEDCOM date |
|
63 | + * another CalendarDate object |
|
64 | + * |
|
65 | + * @param array|int|CalendarDate $date |
|
66 | + */ |
|
67 | + public function __construct($date) { |
|
68 | + // Construct from an integer (a julian day number) |
|
69 | + if (is_integer($date)) { |
|
70 | + $this->minJD = $date; |
|
71 | + $this->maxJD = $date; |
|
72 | + list($this->y, $this->m, $this->d) = $this->calendar->jdToYmd($date); |
|
73 | + |
|
74 | + return; |
|
75 | + } |
|
76 | + |
|
77 | + // Construct from an array (of three gedcom-style strings: "1900", "FEB", "4") |
|
78 | + if (is_array($date)) { |
|
79 | + $this->d = (int) $date[2]; |
|
80 | + if (array_key_exists($date[1], static::$MONTH_ABBREV)) { |
|
81 | + $this->m = static::$MONTH_ABBREV[$date[1]]; |
|
82 | + } else { |
|
83 | + $this->m = 0; |
|
84 | + $this->d = 0; |
|
85 | + } |
|
86 | + $this->y = $this->extractYear($date[0]); |
|
87 | + |
|
88 | + // Our simple lookup table above does not take into account Adar and leap-years. |
|
89 | + if ($this->m === 6 && $this->calendar instanceof JewishCalendar && !$this->calendar->isLeapYear($this->y)) { |
|
90 | + $this->m = 7; |
|
91 | + } |
|
92 | + |
|
93 | + $this->setJdFromYmd(); |
|
94 | + |
|
95 | + return; |
|
96 | + } |
|
97 | 97 | |
98 | 98 | $this->minJD = $date->minJD; |
99 | 99 | $this->maxJD = $date->maxJD; |
100 | 100 | |
101 | - // Construct from an equivalent xxxxDate object |
|
102 | - if (get_class($this) == get_class($date)) { |
|
103 | - $this->y = $date->y; |
|
104 | - $this->m = $date->m; |
|
105 | - $this->d = $date->d; |
|
101 | + // Construct from an equivalent xxxxDate object |
|
102 | + if (get_class($this) == get_class($date)) { |
|
103 | + $this->y = $date->y; |
|
104 | + $this->m = $date->m; |
|
105 | + $this->d = $date->d; |
|
106 | 106 | |
107 | - return; |
|
108 | - } |
|
107 | + return; |
|
108 | + } |
|
109 | 109 | |
110 | 110 | // Not all dates can be converted |
111 | 111 | if (!$this->inValidRange()) { |
@@ -117,838 +117,838 @@ discard block |
||
117 | 117 | } |
118 | 118 | |
119 | 119 | // ...else construct an inequivalent xxxxDate object |
120 | - if ($date->y == 0) { |
|
121 | - // Incomplete date - convert on basis of anniversary in current year |
|
122 | - $today = $date->calendar->jdToYmd(unixtojd()); |
|
123 | - $jd = $date->calendar->ymdToJd($today[0], $date->m, $date->d == 0 ? $today[2] : $date->d); |
|
124 | - } else { |
|
125 | - // Complete date |
|
126 | - $jd = (int) (($date->maxJD + $date->minJD) / 2); |
|
127 | - } |
|
128 | - list($this->y, $this->m, $this->d) = $this->calendar->jdToYmd($jd); |
|
129 | - // New date has same precision as original date |
|
130 | - if ($date->y == 0) { |
|
131 | - $this->y = 0; |
|
132 | - } |
|
133 | - if ($date->m == 0) { |
|
134 | - $this->m = 0; |
|
135 | - } |
|
136 | - if ($date->d == 0) { |
|
137 | - $this->d = 0; |
|
138 | - } |
|
139 | - $this->setJdFromYmd(); |
|
140 | - } |
|
141 | - |
|
142 | - /** |
|
143 | - * Is the current year a leap year? |
|
144 | - * |
|
145 | - * @return bool |
|
146 | - */ |
|
147 | - public function isLeapYear() { |
|
148 | - return $this->calendar->isLeapYear($this->y); |
|
149 | - } |
|
150 | - |
|
151 | - /** |
|
152 | - * Set the object’s Julian day number from a potentially incomplete year/month/day |
|
153 | - */ |
|
154 | - public function setJdFromYmd() { |
|
155 | - if ($this->y == 0) { |
|
156 | - $this->minJD = 0; |
|
157 | - $this->maxJD = 0; |
|
158 | - } elseif ($this->m == 0) { |
|
159 | - $this->minJD = $this->calendar->ymdToJd($this->y, 1, 1); |
|
160 | - $this->maxJD = $this->calendar->ymdToJd($this->nextYear($this->y), 1, 1) - 1; |
|
161 | - } elseif ($this->d == 0) { |
|
162 | - list($ny, $nm) = $this->nextMonth(); |
|
163 | - $this->minJD = $this->calendar->ymdToJd($this->y, $this->m, 1); |
|
164 | - $this->maxJD = $this->calendar->ymdToJd($ny, $nm, 1) - 1; |
|
165 | - } else { |
|
166 | - $this->minJD = $this->calendar->ymdToJd($this->y, $this->m, $this->d); |
|
167 | - $this->maxJD = $this->minJD; |
|
168 | - } |
|
169 | - } |
|
170 | - |
|
171 | - /** |
|
172 | - * Full month name in nominative case. |
|
173 | - * |
|
174 | - * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars. |
|
175 | - * |
|
176 | - * @param int $month_number |
|
177 | - * @param bool $leap_year Some calendars use leap months |
|
178 | - * |
|
179 | - * @return string |
|
180 | - */ |
|
181 | - public static function monthNameNominativeCase($month_number, $leap_year) { |
|
182 | - static $translated_month_names; |
|
183 | - |
|
184 | - if ($translated_month_names === null) { |
|
185 | - $translated_month_names = array( |
|
186 | - 0 => '', |
|
187 | - 1 => I18N::translateContext('NOMINATIVE', 'January'), |
|
188 | - 2 => I18N::translateContext('NOMINATIVE', 'February'), |
|
189 | - 3 => I18N::translateContext('NOMINATIVE', 'March'), |
|
190 | - 4 => I18N::translateContext('NOMINATIVE', 'April'), |
|
191 | - 5 => I18N::translateContext('NOMINATIVE', 'May'), |
|
192 | - 6 => I18N::translateContext('NOMINATIVE', 'June'), |
|
193 | - 7 => I18N::translateContext('NOMINATIVE', 'July'), |
|
194 | - 8 => I18N::translateContext('NOMINATIVE', 'August'), |
|
195 | - 9 => I18N::translateContext('NOMINATIVE', 'September'), |
|
196 | - 10 => I18N::translateContext('NOMINATIVE', 'October'), |
|
197 | - 11 => I18N::translateContext('NOMINATIVE', 'November'), |
|
198 | - 12 => I18N::translateContext('NOMINATIVE', 'December'), |
|
199 | - ); |
|
200 | - } |
|
201 | - |
|
202 | - return $translated_month_names[$month_number]; |
|
203 | - } |
|
204 | - |
|
205 | - /** |
|
206 | - * Full month name in genitive case. |
|
207 | - * |
|
208 | - * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars. |
|
209 | - * |
|
210 | - * @param int $month_number |
|
211 | - * @param bool $leap_year Some calendars use leap months |
|
212 | - * |
|
213 | - * @return string |
|
214 | - */ |
|
215 | - protected function monthNameGenitiveCase($month_number, $leap_year) { |
|
216 | - static $translated_month_names; |
|
217 | - |
|
218 | - if ($translated_month_names === null) { |
|
219 | - $translated_month_names = array( |
|
220 | - 0 => '', |
|
221 | - 1 => I18N::translateContext('GENITIVE', 'January'), |
|
222 | - 2 => I18N::translateContext('GENITIVE', 'February'), |
|
223 | - 3 => I18N::translateContext('GENITIVE', 'March'), |
|
224 | - 4 => I18N::translateContext('GENITIVE', 'April'), |
|
225 | - 5 => I18N::translateContext('GENITIVE', 'May'), |
|
226 | - 6 => I18N::translateContext('GENITIVE', 'June'), |
|
227 | - 7 => I18N::translateContext('GENITIVE', 'July'), |
|
228 | - 8 => I18N::translateContext('GENITIVE', 'August'), |
|
229 | - 9 => I18N::translateContext('GENITIVE', 'September'), |
|
230 | - 10 => I18N::translateContext('GENITIVE', 'October'), |
|
231 | - 11 => I18N::translateContext('GENITIVE', 'November'), |
|
232 | - 12 => I18N::translateContext('GENITIVE', 'December'), |
|
233 | - ); |
|
234 | - } |
|
235 | - |
|
236 | - return $translated_month_names[$month_number]; |
|
237 | - } |
|
238 | - |
|
239 | - /** |
|
240 | - * Full month name in locative case. |
|
241 | - * |
|
242 | - * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars. |
|
243 | - * |
|
244 | - * @param int $month_number |
|
245 | - * @param bool $leap_year Some calendars use leap months |
|
246 | - * |
|
247 | - * @return string |
|
248 | - */ |
|
249 | - protected function monthNameLocativeCase($month_number, $leap_year) { |
|
250 | - static $translated_month_names; |
|
251 | - |
|
252 | - if ($translated_month_names === null) { |
|
253 | - $translated_month_names = array( |
|
254 | - 0 => '', |
|
255 | - 1 => I18N::translateContext('LOCATIVE', 'January'), |
|
256 | - 2 => I18N::translateContext('LOCATIVE', 'February'), |
|
257 | - 3 => I18N::translateContext('LOCATIVE', 'March'), |
|
258 | - 4 => I18N::translateContext('LOCATIVE', 'April'), |
|
259 | - 5 => I18N::translateContext('LOCATIVE', 'May'), |
|
260 | - 6 => I18N::translateContext('LOCATIVE', 'June'), |
|
261 | - 7 => I18N::translateContext('LOCATIVE', 'July'), |
|
262 | - 8 => I18N::translateContext('LOCATIVE', 'August'), |
|
263 | - 9 => I18N::translateContext('LOCATIVE', 'September'), |
|
264 | - 10 => I18N::translateContext('LOCATIVE', 'October'), |
|
265 | - 11 => I18N::translateContext('LOCATIVE', 'November'), |
|
266 | - 12 => I18N::translateContext('LOCATIVE', 'December'), |
|
267 | - ); |
|
268 | - } |
|
269 | - |
|
270 | - return $translated_month_names[$month_number]; |
|
271 | - } |
|
272 | - |
|
273 | - /** |
|
274 | - * Full month name in instrumental case. |
|
275 | - * |
|
276 | - * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars. |
|
277 | - * |
|
278 | - * @param int $month_number |
|
279 | - * @param bool $leap_year Some calendars use leap months |
|
280 | - * |
|
281 | - * @return string |
|
282 | - */ |
|
283 | - protected function monthNameInstrumentalCase($month_number, $leap_year) { |
|
284 | - static $translated_month_names; |
|
285 | - |
|
286 | - if ($translated_month_names === null) { |
|
287 | - $translated_month_names = array( |
|
288 | - 0 => '', |
|
289 | - 1 => I18N::translateContext('INSTRUMENTAL', 'January'), |
|
290 | - 2 => I18N::translateContext('INSTRUMENTAL', 'February'), |
|
291 | - 3 => I18N::translateContext('INSTRUMENTAL', 'March'), |
|
292 | - 4 => I18N::translateContext('INSTRUMENTAL', 'April'), |
|
293 | - 5 => I18N::translateContext('INSTRUMENTAL', 'May'), |
|
294 | - 6 => I18N::translateContext('INSTRUMENTAL', 'June'), |
|
295 | - 7 => I18N::translateContext('INSTRUMENTAL', 'July'), |
|
296 | - 8 => I18N::translateContext('INSTRUMENTAL', 'August'), |
|
297 | - 9 => I18N::translateContext('INSTRUMENTAL', 'September'), |
|
298 | - 10 => I18N::translateContext('INSTRUMENTAL', 'October'), |
|
299 | - 11 => I18N::translateContext('INSTRUMENTAL', 'November'), |
|
300 | - 12 => I18N::translateContext('INSTRUMENTAL', 'December'), |
|
301 | - ); |
|
302 | - } |
|
303 | - |
|
304 | - return $translated_month_names[$month_number]; |
|
305 | - } |
|
306 | - |
|
307 | - /** |
|
308 | - * Abbreviated month name |
|
309 | - * |
|
310 | - * @param int $month_number |
|
311 | - * @param bool $leap_year Some calendars use leap months |
|
312 | - * |
|
313 | - * @return string |
|
314 | - */ |
|
315 | - protected function monthNameAbbreviated($month_number, $leap_year) { |
|
316 | - static $translated_month_names; |
|
317 | - |
|
318 | - if ($translated_month_names === null) { |
|
319 | - $translated_month_names = array( |
|
320 | - 0 => '', |
|
321 | - 1 => I18N::translateContext('Abbreviation for January', 'Jan'), |
|
322 | - 2 => I18N::translateContext('Abbreviation for February', 'Feb'), |
|
323 | - 3 => I18N::translateContext('Abbreviation for March', 'Mar'), |
|
324 | - 4 => I18N::translateContext('Abbreviation for April', 'Apr'), |
|
325 | - 5 => I18N::translateContext('Abbreviation for May', 'May'), |
|
326 | - 6 => I18N::translateContext('Abbreviation for June', 'Jun'), |
|
327 | - 7 => I18N::translateContext('Abbreviation for July', 'Jul'), |
|
328 | - 8 => I18N::translateContext('Abbreviation for August', 'Aug'), |
|
329 | - 9 => I18N::translateContext('Abbreviation for September', 'Sep'), |
|
330 | - 10 => I18N::translateContext('Abbreviation for October', 'Oct'), |
|
331 | - 11 => I18N::translateContext('Abbreviation for November', 'Nov'), |
|
332 | - 12 => I18N::translateContext('Abbreviation for December', 'Dec'), |
|
333 | - ); |
|
334 | - } |
|
335 | - |
|
336 | - return $translated_month_names[$month_number]; |
|
337 | - } |
|
338 | - |
|
339 | - /** |
|
340 | - * Full day of th eweek |
|
341 | - * |
|
342 | - * @param int $day_number |
|
343 | - * |
|
344 | - * @return string |
|
345 | - */ |
|
346 | - public function dayNames($day_number) { |
|
347 | - static $translated_day_names; |
|
348 | - |
|
349 | - if ($translated_day_names === null) { |
|
350 | - $translated_day_names = array( |
|
351 | - 0 => I18N::translate('Monday'), |
|
352 | - 1 => I18N::translate('Tuesday'), |
|
353 | - 2 => I18N::translate('Wednesday'), |
|
354 | - 3 => I18N::translate('Thursday'), |
|
355 | - 4 => I18N::translate('Friday'), |
|
356 | - 5 => I18N::translate('Saturday'), |
|
357 | - 6 => I18N::translate('Sunday'), |
|
358 | - ); |
|
359 | - } |
|
360 | - |
|
361 | - return $translated_day_names[$day_number]; |
|
362 | - } |
|
363 | - |
|
364 | - /** |
|
365 | - * Abbreviated day of the week |
|
366 | - * |
|
367 | - * @param int $day_number |
|
368 | - * |
|
369 | - * @return string |
|
370 | - */ |
|
371 | - protected function dayNamesAbbreviated($day_number) { |
|
372 | - static $translated_day_names; |
|
373 | - |
|
374 | - if ($translated_day_names === null) { |
|
375 | - $translated_day_names = array( |
|
376 | - 0 => /* I18N: abbreviation for Monday */ I18N::translate('Mon'), |
|
377 | - 1 => /* I18N: abbreviation for Tuesday */ I18N::translate('Tue'), |
|
378 | - 2 => /* I18N: abbreviation for Wednesday */ I18N::translate('Wed'), |
|
379 | - 3 => /* I18N: abbreviation for Thursday */ I18N::translate('Thu'), |
|
380 | - 4 => /* I18N: abbreviation for Friday */ I18N::translate('Fri'), |
|
381 | - 5 => /* I18N: abbreviation for Saturday */ I18N::translate('Sat'), |
|
382 | - 6 => /* I18N: abbreviation for Sunday */ I18N::translate('Sun'), |
|
383 | - ); |
|
384 | - } |
|
385 | - |
|
386 | - return $translated_day_names[$day_number]; |
|
387 | - } |
|
388 | - |
|
389 | - /** |
|
390 | - * Most years are 1 more than the previous, but not always (e.g. 1BC->1AD) |
|
391 | - * |
|
392 | - * @param int $year |
|
393 | - * |
|
394 | - * @return int |
|
395 | - */ |
|
396 | - protected function nextYear($year) { |
|
397 | - return $year + 1; |
|
398 | - } |
|
399 | - |
|
400 | - /** |
|
401 | - * Calendars that use suffixes, etc. (e.g. “B.C.”) or OS/NS notation should redefine this. |
|
402 | - * |
|
403 | - * @param string $year |
|
404 | - * |
|
405 | - * @return int |
|
406 | - */ |
|
407 | - protected function extractYear($year) { |
|
408 | - return (int) $year; |
|
409 | - } |
|
410 | - |
|
411 | - /** |
|
412 | - * Compare two dates, for sorting |
|
413 | - * |
|
414 | - * @param CalendarDate $d1 |
|
415 | - * @param CalendarDate $d2 |
|
416 | - * |
|
417 | - * @return int |
|
418 | - */ |
|
419 | - public static function compare(CalendarDate $d1, CalendarDate $d2) { |
|
420 | - if ($d1->maxJD < $d2->minJD) { |
|
421 | - return -1; |
|
422 | - } elseif ($d2->maxJD < $d1->minJD) { |
|
423 | - return 1; |
|
424 | - } else { |
|
425 | - return 0; |
|
426 | - } |
|
427 | - } |
|
428 | - |
|
429 | - /** |
|
430 | - * How long between an event and a given julian day |
|
431 | - * Return result as either a number of years or |
|
432 | - * a gedcom-style age string. |
|
433 | - * |
|
434 | - * @todo JewishDate needs to redefine this to cope with leap months |
|
435 | - * |
|
436 | - * @param bool $full true=gedcom style, false=just years |
|
437 | - * @param int $jd date for calculation |
|
438 | - * @param bool $warn_on_negative show a warning triangle for negative ages |
|
439 | - * |
|
440 | - * @return string |
|
441 | - */ |
|
442 | - public function getAge($full, $jd, $warn_on_negative = true) { |
|
443 | - if ($this->y == 0 || $jd == 0) { |
|
444 | - return $full ? '' : '0'; |
|
445 | - } |
|
446 | - if ($this->minJD < $jd && $this->maxJD > $jd) { |
|
447 | - return $full ? '' : '0'; |
|
448 | - } |
|
449 | - if ($this->minJD == $jd) { |
|
450 | - return $full ? '' : '0'; |
|
451 | - } |
|
452 | - if ($warn_on_negative && $jd < $this->minJD) { |
|
453 | - return '<i class="icon-warning"></i>'; |
|
454 | - } |
|
455 | - list($y, $m, $d) = $this->calendar->jdToYmd($jd); |
|
456 | - $dy = $y - $this->y; |
|
457 | - $dm = $m - max($this->m, 1); |
|
458 | - $dd = $d - max($this->d, 1); |
|
459 | - if ($dd < 0) { |
|
460 | - $dm--; |
|
461 | - } |
|
462 | - if ($dm < 0) { |
|
463 | - $dm += $this->calendar->monthsInYear(); |
|
464 | - $dy--; |
|
465 | - } |
|
466 | - // Not a full age? Then just the years |
|
467 | - if (!$full) { |
|
468 | - return $dy; |
|
469 | - } |
|
470 | - // Age in years? |
|
471 | - if ($dy > 1) { |
|
472 | - return $dy . 'y'; |
|
473 | - } |
|
474 | - $dm += $dy * $this->calendar->monthsInYear(); |
|
475 | - // Age in months? |
|
476 | - if ($dm > 1) { |
|
477 | - return $dm . 'm'; |
|
478 | - } |
|
479 | - |
|
480 | - // Age in days? |
|
481 | - return ($jd - $this->minJD) . 'd'; |
|
482 | - } |
|
483 | - |
|
484 | - /** |
|
485 | - * Convert a date from one calendar to another. |
|
486 | - * |
|
487 | - * @param string $calendar |
|
488 | - * |
|
489 | - * @return CalendarDate |
|
490 | - */ |
|
491 | - public function convertToCalendar($calendar) { |
|
492 | - switch ($calendar) { |
|
493 | - case 'gregorian': |
|
494 | - return new GregorianDate($this); |
|
495 | - case 'julian': |
|
496 | - return new JulianDate($this); |
|
497 | - case 'jewish': |
|
498 | - return new JewishDate($this); |
|
499 | - case 'french': |
|
500 | - return new FrenchDate($this); |
|
501 | - case 'hijri': |
|
502 | - return new HijriDate($this); |
|
503 | - case 'jalali': |
|
504 | - return new JalaliDate($this); |
|
505 | - default: |
|
506 | - return $this; |
|
507 | - } |
|
508 | - } |
|
509 | - |
|
510 | - /** |
|
511 | - * Is this date within the valid range of the calendar |
|
512 | - * |
|
513 | - * @return bool |
|
514 | - */ |
|
515 | - public function inValidRange() { |
|
516 | - return $this->minJD >= $this->calendar->jdStart() && $this->maxJD <= $this->calendar->jdEnd(); |
|
517 | - } |
|
518 | - |
|
519 | - /** |
|
520 | - * How many months in a year |
|
521 | - * |
|
522 | - * @return int |
|
523 | - */ |
|
524 | - public function monthsInYear() { |
|
525 | - return $this->calendar->monthsInYear(); |
|
526 | - } |
|
527 | - |
|
528 | - /** |
|
529 | - * How many days in the current month |
|
530 | - * |
|
531 | - * @return int |
|
532 | - */ |
|
533 | - public function daysInMonth() { |
|
534 | - try { |
|
535 | - return $this->calendar->daysInMonth($this->y, $this->m); |
|
536 | - } catch (\InvalidArgumentException $ex) { |
|
537 | - // calendar.php calls this with "DD MMM" dates, for which we cannot calculate |
|
538 | - // the length of a month. Should we validate this before calling this function? |
|
539 | - return 0; |
|
540 | - } |
|
541 | - } |
|
542 | - |
|
543 | - /** |
|
544 | - * How many days in the current week |
|
545 | - * |
|
546 | - * @return int |
|
547 | - */ |
|
548 | - public function daysInWeek() { |
|
549 | - return $this->calendar->daysInWeek(); |
|
550 | - } |
|
551 | - |
|
552 | - /** |
|
553 | - * Format a date, using similar codes to the PHP date() function. |
|
554 | - * |
|
555 | - * @param string $format See http://php.net/date |
|
556 | - * @param string $qualifier GEDCOM qualifier, so we can choose the right case for the month name. |
|
557 | - * |
|
558 | - * @return string |
|
559 | - */ |
|
560 | - public function format($format, $qualifier = '') { |
|
561 | - // Don’t show exact details for inexact dates |
|
562 | - if (!$this->d) { |
|
563 | - // The comma is for US "M D, Y" dates |
|
564 | - $format = preg_replace('/%[djlDNSwz][,]?/', '', $format); |
|
565 | - } |
|
566 | - if (!$this->m) { |
|
567 | - $format = str_replace(array('%F', '%m', '%M', '%n', '%t'), '', $format); |
|
568 | - } |
|
569 | - if (!$this->y) { |
|
570 | - $format = str_replace(array('%t', '%L', '%G', '%y', '%Y'), '', $format); |
|
571 | - } |
|
572 | - // If we’ve trimmed the format, also trim the punctuation |
|
573 | - if (!$this->d || !$this->m || !$this->y) { |
|
574 | - $format = trim($format, ',. ;/-'); |
|
575 | - } |
|
576 | - if ($this->d && preg_match('/%[djlDNSwz]/', $format)) { |
|
577 | - // If we have a day-number *and* we are being asked to display it, then genitive |
|
578 | - $case = 'GENITIVE'; |
|
579 | - } else { |
|
580 | - switch ($qualifier) { |
|
581 | - case 'TO': |
|
582 | - case 'ABT': |
|
583 | - case 'FROM': |
|
584 | - $case = 'GENITIVE'; |
|
585 | - break; |
|
586 | - case 'AFT': |
|
587 | - $case = 'LOCATIVE'; |
|
588 | - break; |
|
589 | - case 'BEF': |
|
590 | - case 'BET': |
|
591 | - case 'AND': |
|
592 | - $case = 'INSTRUMENTAL'; |
|
593 | - break; |
|
594 | - case '': |
|
595 | - case 'INT': |
|
596 | - case 'EST': |
|
597 | - case 'CAL': |
|
598 | - default: // There shouldn't be any other options... |
|
599 | - $case = 'NOMINATIVE'; |
|
600 | - break; |
|
601 | - } |
|
602 | - } |
|
603 | - // Build up the formatted date, character at a time |
|
604 | - preg_match_all('/%[^%]/', $format, $matches); |
|
605 | - foreach ($matches[0] as $match) { |
|
606 | - switch ($match) { |
|
607 | - case '%d': |
|
608 | - $format = str_replace($match, $this->formatDayZeros(), $format); |
|
609 | - break; |
|
610 | - case '%j': |
|
611 | - $format = str_replace($match, $this->formatDay(), $format); |
|
612 | - break; |
|
613 | - case '%l': |
|
614 | - $format = str_replace($match, $this->formatLongWeekday(), $format); |
|
615 | - break; |
|
616 | - case '%D': |
|
617 | - $format = str_replace($match, $this->formatShortWeekday(), $format); |
|
618 | - break; |
|
619 | - case '%N': |
|
620 | - $format = str_replace($match, $this->formatIsoWeekday(), $format); |
|
621 | - break; |
|
622 | - case '%w': |
|
623 | - $format = str_replace($match, $this->formatNumericWeekday(), $format); |
|
624 | - break; |
|
625 | - case '%z': |
|
626 | - $format = str_replace($match, $this->formatDayOfYear(), $format); |
|
627 | - break; |
|
628 | - case '%F': |
|
629 | - $format = str_replace($match, $this->formatLongMonth($case), $format); |
|
630 | - break; |
|
631 | - case '%m': |
|
632 | - $format = str_replace($match, $this->formatMonthZeros(), $format); |
|
633 | - break; |
|
634 | - case '%M': |
|
635 | - $format = str_replace($match, $this->formatShortMonth(), $format); |
|
636 | - break; |
|
637 | - case '%n': |
|
638 | - $format = str_replace($match, $this->formatMonth(), $format); |
|
639 | - break; |
|
640 | - case '%t': |
|
641 | - $format = str_replace($match, $this->daysInMonth(), $format); |
|
642 | - break; |
|
643 | - case '%L': |
|
644 | - $format = str_replace($match, (int) $this->isLeapYear(), $format); |
|
645 | - break; |
|
646 | - case '%Y': |
|
647 | - $format = str_replace($match, $this->formatLongYear(), $format); |
|
648 | - break; |
|
649 | - case '%y': |
|
650 | - $format = str_replace($match, $this->formatShortYear(), $format); |
|
651 | - break; |
|
652 | - // These 4 extensions are useful for re-formatting gedcom dates. |
|
653 | - case '%@': |
|
654 | - $format = str_replace($match, $this->calendar->gedcomCalendarEscape(), $format); |
|
655 | - break; |
|
656 | - case '%A': |
|
657 | - $format = str_replace($match, $this->formatGedcomDay(), $format); |
|
658 | - break; |
|
659 | - case '%O': |
|
660 | - $format = str_replace($match, $this->formatGedcomMonth(), $format); |
|
661 | - break; |
|
662 | - case '%E': |
|
663 | - $format = str_replace($match, $this->formatGedcomYear(), $format); |
|
664 | - break; |
|
665 | - } |
|
666 | - } |
|
667 | - |
|
668 | - return $format; |
|
669 | - } |
|
670 | - |
|
671 | - /** |
|
672 | - * Generate the %d format for a date. |
|
673 | - * |
|
674 | - * @return string |
|
675 | - */ |
|
676 | - protected function formatDayZeros() { |
|
677 | - if ($this->d > 9) { |
|
678 | - return I18N::digits($this->d); |
|
679 | - } else { |
|
680 | - return I18N::digits('0' . $this->d); |
|
681 | - } |
|
682 | - } |
|
683 | - |
|
684 | - /** |
|
685 | - * Generate the %j format for a date. |
|
686 | - * |
|
687 | - * @return string |
|
688 | - */ |
|
689 | - protected function formatDay() { |
|
690 | - return I18N::digits($this->d); |
|
691 | - } |
|
692 | - |
|
693 | - /** |
|
694 | - * Generate the %l format for a date. |
|
695 | - * |
|
696 | - * @return string |
|
697 | - */ |
|
698 | - protected function formatLongWeekday() { |
|
699 | - return $this->dayNames($this->minJD % $this->calendar->daysInWeek()); |
|
700 | - } |
|
701 | - |
|
702 | - /** |
|
703 | - * Generate the %D format for a date. |
|
704 | - * |
|
705 | - * @return string |
|
706 | - */ |
|
707 | - protected function formatShortWeekday() { |
|
708 | - return $this->dayNamesAbbreviated($this->minJD % $this->calendar->daysInWeek()); |
|
709 | - } |
|
710 | - |
|
711 | - /** |
|
712 | - * Generate the %N format for a date. |
|
713 | - * |
|
714 | - * @return string |
|
715 | - */ |
|
716 | - protected function formatIsoWeekday() { |
|
717 | - return I18N::digits($this->minJD % 7 + 1); |
|
718 | - } |
|
719 | - |
|
720 | - /** |
|
721 | - * Generate the %w format for a date. |
|
722 | - * |
|
723 | - * @return string |
|
724 | - */ |
|
725 | - protected function formatNumericWeekday() { |
|
726 | - return I18N::digits(($this->minJD + 1) % $this->calendar->daysInWeek()); |
|
727 | - } |
|
728 | - |
|
729 | - /** |
|
730 | - * Generate the %z format for a date. |
|
731 | - * |
|
732 | - * @return string |
|
733 | - */ |
|
734 | - protected function formatDayOfYear() { |
|
735 | - return I18N::digits($this->minJD - $this->calendar->ymdToJd($this->y, 1, 1)); |
|
736 | - } |
|
737 | - |
|
738 | - /** |
|
739 | - * Generate the %n format for a date. |
|
740 | - * |
|
741 | - * @return string |
|
742 | - */ |
|
743 | - protected function formatMonth() { |
|
744 | - return I18N::digits($this->m); |
|
745 | - } |
|
746 | - |
|
747 | - /** |
|
748 | - * Generate the %m format for a date. |
|
749 | - * |
|
750 | - * @return string |
|
751 | - */ |
|
752 | - protected function formatMonthZeros() { |
|
753 | - if ($this->m > 9) { |
|
754 | - return I18N::digits($this->m); |
|
755 | - } else { |
|
756 | - return I18N::digits('0' . $this->m); |
|
757 | - } |
|
758 | - } |
|
759 | - |
|
760 | - /** |
|
761 | - * Generate the %F format for a date. |
|
762 | - * |
|
763 | - * @param string $case Which grammatical case shall we use |
|
764 | - * |
|
765 | - * @return string |
|
766 | - */ |
|
767 | - protected function formatLongMonth($case = 'NOMINATIVE') { |
|
768 | - switch ($case) { |
|
769 | - case 'GENITIVE': |
|
770 | - return $this->monthNameGenitiveCase($this->m, $this->isLeapYear()); |
|
771 | - case 'NOMINATIVE': |
|
772 | - return $this->monthNameNominativeCase($this->m, $this->isLeapYear()); |
|
773 | - case 'LOCATIVE': |
|
774 | - return $this->monthNameLocativeCase($this->m, $this->isLeapYear()); |
|
775 | - case 'INSTRUMENTAL': |
|
776 | - return $this->monthNameInstrumentalCase($this->m, $this->isLeapYear()); |
|
777 | - default: |
|
778 | - throw new \InvalidArgumentException($case); |
|
779 | - } |
|
780 | - } |
|
781 | - |
|
782 | - /** |
|
783 | - * Generate the %M format for a date. |
|
784 | - * |
|
785 | - * @return string |
|
786 | - */ |
|
787 | - protected function formatShortMonth() { |
|
788 | - return $this->monthNameAbbreviated($this->m, $this->isLeapYear()); |
|
789 | - } |
|
790 | - |
|
791 | - /** |
|
792 | - * Generate the %y format for a date. |
|
793 | - * |
|
794 | - * NOTE Short year is NOT a 2-digit year. It is for calendars such as hebrew |
|
795 | - * which have a 3-digit form of 4-digit years. |
|
796 | - * |
|
797 | - * @return string |
|
798 | - */ |
|
799 | - protected function formatShortYear() { |
|
800 | - return $this->formatLongYear(); |
|
801 | - } |
|
802 | - |
|
803 | - /** |
|
804 | - * Generate the %A format for a date. |
|
805 | - * |
|
806 | - * @return string |
|
807 | - */ |
|
808 | - protected function formatGedcomDay() { |
|
809 | - if ($this->d == 0) { |
|
810 | - return ''; |
|
811 | - } else { |
|
812 | - return sprintf('%02d', $this->d); |
|
813 | - } |
|
814 | - } |
|
815 | - |
|
816 | - /** |
|
817 | - * Generate the %O format for a date. |
|
818 | - * |
|
819 | - * @return string |
|
820 | - */ |
|
821 | - protected function formatGedcomMonth() { |
|
822 | - // Our simple lookup table doesn't work correctly for Adar on leap years |
|
823 | - if ($this->m == 7 && $this->calendar instanceof JewishCalendar && !$this->calendar->isLeapYear($this->y)) { |
|
824 | - return 'ADR'; |
|
825 | - } else { |
|
826 | - return array_search($this->m, static::$MONTH_ABBREV); |
|
827 | - } |
|
828 | - } |
|
829 | - |
|
830 | - /** |
|
831 | - * Generate the %E format for a date. |
|
832 | - * |
|
833 | - * @return string |
|
834 | - */ |
|
835 | - protected function formatGedcomYear() { |
|
836 | - if ($this->y == 0) { |
|
837 | - return ''; |
|
838 | - } else { |
|
839 | - return sprintf('%04d', $this->y); |
|
840 | - } |
|
841 | - } |
|
842 | - |
|
843 | - /** |
|
844 | - * Generate the %Y format for a date. |
|
845 | - * |
|
846 | - * @return string |
|
847 | - */ |
|
848 | - protected function formatLongYear() { |
|
849 | - return I18N::digits($this->y); |
|
850 | - } |
|
851 | - |
|
852 | - /** |
|
853 | - * Which months follows this one? Calendars with leap-months should provide their own implementation. |
|
854 | - * |
|
855 | - * @return int[] |
|
856 | - */ |
|
857 | - protected function nextMonth() { |
|
858 | - return array($this->m === $this->calendar->monthsInYear() ? $this->nextYear($this->y) : $this->y, ($this->m % $this->calendar->monthsInYear()) + 1); |
|
859 | - } |
|
860 | - |
|
861 | - /** |
|
862 | - * Convert a decimal number to roman numerals |
|
863 | - * |
|
864 | - * @param int $number |
|
865 | - * |
|
866 | - * @return string |
|
867 | - */ |
|
868 | - protected function numberToRomanNumerals($number) { |
|
869 | - if ($number < 1) { |
|
870 | - // Cannot convert zero/negative numbers |
|
871 | - return (string) $number; |
|
872 | - } |
|
873 | - $roman = ''; |
|
874 | - foreach (self::$roman_numerals as $key => $value) { |
|
875 | - while ($number >= $key) { |
|
876 | - $roman .= $value; |
|
877 | - $number -= $key; |
|
878 | - } |
|
879 | - } |
|
880 | - |
|
881 | - return $roman; |
|
882 | - } |
|
883 | - |
|
884 | - /** |
|
885 | - * Convert a roman numeral to decimal |
|
886 | - * |
|
887 | - * @param string $roman |
|
888 | - * |
|
889 | - * @return int |
|
890 | - */ |
|
891 | - protected function romanNumeralsToNumber($roman) { |
|
892 | - $num = 0; |
|
893 | - foreach (self::$roman_numerals as $key => $value) { |
|
894 | - if (strpos($roman, $value) === 0) { |
|
895 | - $num += $key; |
|
896 | - $roman = substr($roman, strlen($value)); |
|
897 | - } |
|
898 | - } |
|
899 | - |
|
900 | - return $num; |
|
901 | - } |
|
902 | - |
|
903 | - /** |
|
904 | - * Get today’s date in the current calendar. |
|
905 | - * |
|
906 | - * @return int[] |
|
907 | - */ |
|
908 | - public function todayYmd() { |
|
909 | - return $this->calendar->jdToYmd(unixtojd()); |
|
910 | - } |
|
911 | - |
|
912 | - /** |
|
913 | - * Convert to today’s date. |
|
914 | - * |
|
915 | - * @return CalendarDate |
|
916 | - */ |
|
917 | - public function today() { |
|
918 | - $tmp = clone $this; |
|
919 | - $ymd = $tmp->todayYmd(); |
|
920 | - $tmp->y = $ymd[0]; |
|
921 | - $tmp->m = $ymd[1]; |
|
922 | - $tmp->d = $ymd[2]; |
|
923 | - $tmp->setJdFromYmd(); |
|
924 | - |
|
925 | - return $tmp; |
|
926 | - } |
|
927 | - |
|
928 | - /** |
|
929 | - * Create a URL that links this date to the WT calendar |
|
930 | - * |
|
931 | - * @param string $date_format |
|
932 | - * |
|
933 | - * @return string |
|
934 | - */ |
|
935 | - public function calendarUrl($date_format) { |
|
936 | - if (strpbrk($date_format, 'dDj') && $this->d) { |
|
937 | - // If the format includes a day, and the date also includes a day, then use the day view |
|
938 | - $view = 'day'; |
|
939 | - } elseif (strpbrk($date_format, 'FMmn') && $this->m) { |
|
940 | - // If the format includes a month, and the date also includes a month, then use the month view |
|
941 | - $view = 'month'; |
|
942 | - } else { |
|
943 | - // Use the year view |
|
944 | - $view = 'year'; |
|
945 | - } |
|
946 | - |
|
947 | - return |
|
948 | - 'calendar.php?cal=' . rawurlencode($this->calendar->gedcomCalendarEscape()) . |
|
949 | - '&year=' . $this->formatGedcomYear() . |
|
950 | - '&month=' . $this->formatGedcomMonth() . |
|
951 | - '&day=' . $this->formatGedcomDay() . |
|
952 | - '&view=' . $view; |
|
953 | - } |
|
120 | + if ($date->y == 0) { |
|
121 | + // Incomplete date - convert on basis of anniversary in current year |
|
122 | + $today = $date->calendar->jdToYmd(unixtojd()); |
|
123 | + $jd = $date->calendar->ymdToJd($today[0], $date->m, $date->d == 0 ? $today[2] : $date->d); |
|
124 | + } else { |
|
125 | + // Complete date |
|
126 | + $jd = (int) (($date->maxJD + $date->minJD) / 2); |
|
127 | + } |
|
128 | + list($this->y, $this->m, $this->d) = $this->calendar->jdToYmd($jd); |
|
129 | + // New date has same precision as original date |
|
130 | + if ($date->y == 0) { |
|
131 | + $this->y = 0; |
|
132 | + } |
|
133 | + if ($date->m == 0) { |
|
134 | + $this->m = 0; |
|
135 | + } |
|
136 | + if ($date->d == 0) { |
|
137 | + $this->d = 0; |
|
138 | + } |
|
139 | + $this->setJdFromYmd(); |
|
140 | + } |
|
141 | + |
|
142 | + /** |
|
143 | + * Is the current year a leap year? |
|
144 | + * |
|
145 | + * @return bool |
|
146 | + */ |
|
147 | + public function isLeapYear() { |
|
148 | + return $this->calendar->isLeapYear($this->y); |
|
149 | + } |
|
150 | + |
|
151 | + /** |
|
152 | + * Set the object’s Julian day number from a potentially incomplete year/month/day |
|
153 | + */ |
|
154 | + public function setJdFromYmd() { |
|
155 | + if ($this->y == 0) { |
|
156 | + $this->minJD = 0; |
|
157 | + $this->maxJD = 0; |
|
158 | + } elseif ($this->m == 0) { |
|
159 | + $this->minJD = $this->calendar->ymdToJd($this->y, 1, 1); |
|
160 | + $this->maxJD = $this->calendar->ymdToJd($this->nextYear($this->y), 1, 1) - 1; |
|
161 | + } elseif ($this->d == 0) { |
|
162 | + list($ny, $nm) = $this->nextMonth(); |
|
163 | + $this->minJD = $this->calendar->ymdToJd($this->y, $this->m, 1); |
|
164 | + $this->maxJD = $this->calendar->ymdToJd($ny, $nm, 1) - 1; |
|
165 | + } else { |
|
166 | + $this->minJD = $this->calendar->ymdToJd($this->y, $this->m, $this->d); |
|
167 | + $this->maxJD = $this->minJD; |
|
168 | + } |
|
169 | + } |
|
170 | + |
|
171 | + /** |
|
172 | + * Full month name in nominative case. |
|
173 | + * |
|
174 | + * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars. |
|
175 | + * |
|
176 | + * @param int $month_number |
|
177 | + * @param bool $leap_year Some calendars use leap months |
|
178 | + * |
|
179 | + * @return string |
|
180 | + */ |
|
181 | + public static function monthNameNominativeCase($month_number, $leap_year) { |
|
182 | + static $translated_month_names; |
|
183 | + |
|
184 | + if ($translated_month_names === null) { |
|
185 | + $translated_month_names = array( |
|
186 | + 0 => '', |
|
187 | + 1 => I18N::translateContext('NOMINATIVE', 'January'), |
|
188 | + 2 => I18N::translateContext('NOMINATIVE', 'February'), |
|
189 | + 3 => I18N::translateContext('NOMINATIVE', 'March'), |
|
190 | + 4 => I18N::translateContext('NOMINATIVE', 'April'), |
|
191 | + 5 => I18N::translateContext('NOMINATIVE', 'May'), |
|
192 | + 6 => I18N::translateContext('NOMINATIVE', 'June'), |
|
193 | + 7 => I18N::translateContext('NOMINATIVE', 'July'), |
|
194 | + 8 => I18N::translateContext('NOMINATIVE', 'August'), |
|
195 | + 9 => I18N::translateContext('NOMINATIVE', 'September'), |
|
196 | + 10 => I18N::translateContext('NOMINATIVE', 'October'), |
|
197 | + 11 => I18N::translateContext('NOMINATIVE', 'November'), |
|
198 | + 12 => I18N::translateContext('NOMINATIVE', 'December'), |
|
199 | + ); |
|
200 | + } |
|
201 | + |
|
202 | + return $translated_month_names[$month_number]; |
|
203 | + } |
|
204 | + |
|
205 | + /** |
|
206 | + * Full month name in genitive case. |
|
207 | + * |
|
208 | + * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars. |
|
209 | + * |
|
210 | + * @param int $month_number |
|
211 | + * @param bool $leap_year Some calendars use leap months |
|
212 | + * |
|
213 | + * @return string |
|
214 | + */ |
|
215 | + protected function monthNameGenitiveCase($month_number, $leap_year) { |
|
216 | + static $translated_month_names; |
|
217 | + |
|
218 | + if ($translated_month_names === null) { |
|
219 | + $translated_month_names = array( |
|
220 | + 0 => '', |
|
221 | + 1 => I18N::translateContext('GENITIVE', 'January'), |
|
222 | + 2 => I18N::translateContext('GENITIVE', 'February'), |
|
223 | + 3 => I18N::translateContext('GENITIVE', 'March'), |
|
224 | + 4 => I18N::translateContext('GENITIVE', 'April'), |
|
225 | + 5 => I18N::translateContext('GENITIVE', 'May'), |
|
226 | + 6 => I18N::translateContext('GENITIVE', 'June'), |
|
227 | + 7 => I18N::translateContext('GENITIVE', 'July'), |
|
228 | + 8 => I18N::translateContext('GENITIVE', 'August'), |
|
229 | + 9 => I18N::translateContext('GENITIVE', 'September'), |
|
230 | + 10 => I18N::translateContext('GENITIVE', 'October'), |
|
231 | + 11 => I18N::translateContext('GENITIVE', 'November'), |
|
232 | + 12 => I18N::translateContext('GENITIVE', 'December'), |
|
233 | + ); |
|
234 | + } |
|
235 | + |
|
236 | + return $translated_month_names[$month_number]; |
|
237 | + } |
|
238 | + |
|
239 | + /** |
|
240 | + * Full month name in locative case. |
|
241 | + * |
|
242 | + * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars. |
|
243 | + * |
|
244 | + * @param int $month_number |
|
245 | + * @param bool $leap_year Some calendars use leap months |
|
246 | + * |
|
247 | + * @return string |
|
248 | + */ |
|
249 | + protected function monthNameLocativeCase($month_number, $leap_year) { |
|
250 | + static $translated_month_names; |
|
251 | + |
|
252 | + if ($translated_month_names === null) { |
|
253 | + $translated_month_names = array( |
|
254 | + 0 => '', |
|
255 | + 1 => I18N::translateContext('LOCATIVE', 'January'), |
|
256 | + 2 => I18N::translateContext('LOCATIVE', 'February'), |
|
257 | + 3 => I18N::translateContext('LOCATIVE', 'March'), |
|
258 | + 4 => I18N::translateContext('LOCATIVE', 'April'), |
|
259 | + 5 => I18N::translateContext('LOCATIVE', 'May'), |
|
260 | + 6 => I18N::translateContext('LOCATIVE', 'June'), |
|
261 | + 7 => I18N::translateContext('LOCATIVE', 'July'), |
|
262 | + 8 => I18N::translateContext('LOCATIVE', 'August'), |
|
263 | + 9 => I18N::translateContext('LOCATIVE', 'September'), |
|
264 | + 10 => I18N::translateContext('LOCATIVE', 'October'), |
|
265 | + 11 => I18N::translateContext('LOCATIVE', 'November'), |
|
266 | + 12 => I18N::translateContext('LOCATIVE', 'December'), |
|
267 | + ); |
|
268 | + } |
|
269 | + |
|
270 | + return $translated_month_names[$month_number]; |
|
271 | + } |
|
272 | + |
|
273 | + /** |
|
274 | + * Full month name in instrumental case. |
|
275 | + * |
|
276 | + * We put these in the base class, to save duplicating it in the Julian and Gregorian calendars. |
|
277 | + * |
|
278 | + * @param int $month_number |
|
279 | + * @param bool $leap_year Some calendars use leap months |
|
280 | + * |
|
281 | + * @return string |
|
282 | + */ |
|
283 | + protected function monthNameInstrumentalCase($month_number, $leap_year) { |
|
284 | + static $translated_month_names; |
|
285 | + |
|
286 | + if ($translated_month_names === null) { |
|
287 | + $translated_month_names = array( |
|
288 | + 0 => '', |
|
289 | + 1 => I18N::translateContext('INSTRUMENTAL', 'January'), |
|
290 | + 2 => I18N::translateContext('INSTRUMENTAL', 'February'), |
|
291 | + 3 => I18N::translateContext('INSTRUMENTAL', 'March'), |
|
292 | + 4 => I18N::translateContext('INSTRUMENTAL', 'April'), |
|
293 | + 5 => I18N::translateContext('INSTRUMENTAL', 'May'), |
|
294 | + 6 => I18N::translateContext('INSTRUMENTAL', 'June'), |
|
295 | + 7 => I18N::translateContext('INSTRUMENTAL', 'July'), |
|
296 | + 8 => I18N::translateContext('INSTRUMENTAL', 'August'), |
|
297 | + 9 => I18N::translateContext('INSTRUMENTAL', 'September'), |
|
298 | + 10 => I18N::translateContext('INSTRUMENTAL', 'October'), |
|
299 | + 11 => I18N::translateContext('INSTRUMENTAL', 'November'), |
|
300 | + 12 => I18N::translateContext('INSTRUMENTAL', 'December'), |
|
301 | + ); |
|
302 | + } |
|
303 | + |
|
304 | + return $translated_month_names[$month_number]; |
|
305 | + } |
|
306 | + |
|
307 | + /** |
|
308 | + * Abbreviated month name |
|
309 | + * |
|
310 | + * @param int $month_number |
|
311 | + * @param bool $leap_year Some calendars use leap months |
|
312 | + * |
|
313 | + * @return string |
|
314 | + */ |
|
315 | + protected function monthNameAbbreviated($month_number, $leap_year) { |
|
316 | + static $translated_month_names; |
|
317 | + |
|
318 | + if ($translated_month_names === null) { |
|
319 | + $translated_month_names = array( |
|
320 | + 0 => '', |
|
321 | + 1 => I18N::translateContext('Abbreviation for January', 'Jan'), |
|
322 | + 2 => I18N::translateContext('Abbreviation for February', 'Feb'), |
|
323 | + 3 => I18N::translateContext('Abbreviation for March', 'Mar'), |
|
324 | + 4 => I18N::translateContext('Abbreviation for April', 'Apr'), |
|
325 | + 5 => I18N::translateContext('Abbreviation for May', 'May'), |
|
326 | + 6 => I18N::translateContext('Abbreviation for June', 'Jun'), |
|
327 | + 7 => I18N::translateContext('Abbreviation for July', 'Jul'), |
|
328 | + 8 => I18N::translateContext('Abbreviation for August', 'Aug'), |
|
329 | + 9 => I18N::translateContext('Abbreviation for September', 'Sep'), |
|
330 | + 10 => I18N::translateContext('Abbreviation for October', 'Oct'), |
|
331 | + 11 => I18N::translateContext('Abbreviation for November', 'Nov'), |
|
332 | + 12 => I18N::translateContext('Abbreviation for December', 'Dec'), |
|
333 | + ); |
|
334 | + } |
|
335 | + |
|
336 | + return $translated_month_names[$month_number]; |
|
337 | + } |
|
338 | + |
|
339 | + /** |
|
340 | + * Full day of th eweek |
|
341 | + * |
|
342 | + * @param int $day_number |
|
343 | + * |
|
344 | + * @return string |
|
345 | + */ |
|
346 | + public function dayNames($day_number) { |
|
347 | + static $translated_day_names; |
|
348 | + |
|
349 | + if ($translated_day_names === null) { |
|
350 | + $translated_day_names = array( |
|
351 | + 0 => I18N::translate('Monday'), |
|
352 | + 1 => I18N::translate('Tuesday'), |
|
353 | + 2 => I18N::translate('Wednesday'), |
|
354 | + 3 => I18N::translate('Thursday'), |
|
355 | + 4 => I18N::translate('Friday'), |
|
356 | + 5 => I18N::translate('Saturday'), |
|
357 | + 6 => I18N::translate('Sunday'), |
|
358 | + ); |
|
359 | + } |
|
360 | + |
|
361 | + return $translated_day_names[$day_number]; |
|
362 | + } |
|
363 | + |
|
364 | + /** |
|
365 | + * Abbreviated day of the week |
|
366 | + * |
|
367 | + * @param int $day_number |
|
368 | + * |
|
369 | + * @return string |
|
370 | + */ |
|
371 | + protected function dayNamesAbbreviated($day_number) { |
|
372 | + static $translated_day_names; |
|
373 | + |
|
374 | + if ($translated_day_names === null) { |
|
375 | + $translated_day_names = array( |
|
376 | + 0 => /* I18N: abbreviation for Monday */ I18N::translate('Mon'), |
|
377 | + 1 => /* I18N: abbreviation for Tuesday */ I18N::translate('Tue'), |
|
378 | + 2 => /* I18N: abbreviation for Wednesday */ I18N::translate('Wed'), |
|
379 | + 3 => /* I18N: abbreviation for Thursday */ I18N::translate('Thu'), |
|
380 | + 4 => /* I18N: abbreviation for Friday */ I18N::translate('Fri'), |
|
381 | + 5 => /* I18N: abbreviation for Saturday */ I18N::translate('Sat'), |
|
382 | + 6 => /* I18N: abbreviation for Sunday */ I18N::translate('Sun'), |
|
383 | + ); |
|
384 | + } |
|
385 | + |
|
386 | + return $translated_day_names[$day_number]; |
|
387 | + } |
|
388 | + |
|
389 | + /** |
|
390 | + * Most years are 1 more than the previous, but not always (e.g. 1BC->1AD) |
|
391 | + * |
|
392 | + * @param int $year |
|
393 | + * |
|
394 | + * @return int |
|
395 | + */ |
|
396 | + protected function nextYear($year) { |
|
397 | + return $year + 1; |
|
398 | + } |
|
399 | + |
|
400 | + /** |
|
401 | + * Calendars that use suffixes, etc. (e.g. “B.C.”) or OS/NS notation should redefine this. |
|
402 | + * |
|
403 | + * @param string $year |
|
404 | + * |
|
405 | + * @return int |
|
406 | + */ |
|
407 | + protected function extractYear($year) { |
|
408 | + return (int) $year; |
|
409 | + } |
|
410 | + |
|
411 | + /** |
|
412 | + * Compare two dates, for sorting |
|
413 | + * |
|
414 | + * @param CalendarDate $d1 |
|
415 | + * @param CalendarDate $d2 |
|
416 | + * |
|
417 | + * @return int |
|
418 | + */ |
|
419 | + public static function compare(CalendarDate $d1, CalendarDate $d2) { |
|
420 | + if ($d1->maxJD < $d2->minJD) { |
|
421 | + return -1; |
|
422 | + } elseif ($d2->maxJD < $d1->minJD) { |
|
423 | + return 1; |
|
424 | + } else { |
|
425 | + return 0; |
|
426 | + } |
|
427 | + } |
|
428 | + |
|
429 | + /** |
|
430 | + * How long between an event and a given julian day |
|
431 | + * Return result as either a number of years or |
|
432 | + * a gedcom-style age string. |
|
433 | + * |
|
434 | + * @todo JewishDate needs to redefine this to cope with leap months |
|
435 | + * |
|
436 | + * @param bool $full true=gedcom style, false=just years |
|
437 | + * @param int $jd date for calculation |
|
438 | + * @param bool $warn_on_negative show a warning triangle for negative ages |
|
439 | + * |
|
440 | + * @return string |
|
441 | + */ |
|
442 | + public function getAge($full, $jd, $warn_on_negative = true) { |
|
443 | + if ($this->y == 0 || $jd == 0) { |
|
444 | + return $full ? '' : '0'; |
|
445 | + } |
|
446 | + if ($this->minJD < $jd && $this->maxJD > $jd) { |
|
447 | + return $full ? '' : '0'; |
|
448 | + } |
|
449 | + if ($this->minJD == $jd) { |
|
450 | + return $full ? '' : '0'; |
|
451 | + } |
|
452 | + if ($warn_on_negative && $jd < $this->minJD) { |
|
453 | + return '<i class="icon-warning"></i>'; |
|
454 | + } |
|
455 | + list($y, $m, $d) = $this->calendar->jdToYmd($jd); |
|
456 | + $dy = $y - $this->y; |
|
457 | + $dm = $m - max($this->m, 1); |
|
458 | + $dd = $d - max($this->d, 1); |
|
459 | + if ($dd < 0) { |
|
460 | + $dm--; |
|
461 | + } |
|
462 | + if ($dm < 0) { |
|
463 | + $dm += $this->calendar->monthsInYear(); |
|
464 | + $dy--; |
|
465 | + } |
|
466 | + // Not a full age? Then just the years |
|
467 | + if (!$full) { |
|
468 | + return $dy; |
|
469 | + } |
|
470 | + // Age in years? |
|
471 | + if ($dy > 1) { |
|
472 | + return $dy . 'y'; |
|
473 | + } |
|
474 | + $dm += $dy * $this->calendar->monthsInYear(); |
|
475 | + // Age in months? |
|
476 | + if ($dm > 1) { |
|
477 | + return $dm . 'm'; |
|
478 | + } |
|
479 | + |
|
480 | + // Age in days? |
|
481 | + return ($jd - $this->minJD) . 'd'; |
|
482 | + } |
|
483 | + |
|
484 | + /** |
|
485 | + * Convert a date from one calendar to another. |
|
486 | + * |
|
487 | + * @param string $calendar |
|
488 | + * |
|
489 | + * @return CalendarDate |
|
490 | + */ |
|
491 | + public function convertToCalendar($calendar) { |
|
492 | + switch ($calendar) { |
|
493 | + case 'gregorian': |
|
494 | + return new GregorianDate($this); |
|
495 | + case 'julian': |
|
496 | + return new JulianDate($this); |
|
497 | + case 'jewish': |
|
498 | + return new JewishDate($this); |
|
499 | + case 'french': |
|
500 | + return new FrenchDate($this); |
|
501 | + case 'hijri': |
|
502 | + return new HijriDate($this); |
|
503 | + case 'jalali': |
|
504 | + return new JalaliDate($this); |
|
505 | + default: |
|
506 | + return $this; |
|
507 | + } |
|
508 | + } |
|
509 | + |
|
510 | + /** |
|
511 | + * Is this date within the valid range of the calendar |
|
512 | + * |
|
513 | + * @return bool |
|
514 | + */ |
|
515 | + public function inValidRange() { |
|
516 | + return $this->minJD >= $this->calendar->jdStart() && $this->maxJD <= $this->calendar->jdEnd(); |
|
517 | + } |
|
518 | + |
|
519 | + /** |
|
520 | + * How many months in a year |
|
521 | + * |
|
522 | + * @return int |
|
523 | + */ |
|
524 | + public function monthsInYear() { |
|
525 | + return $this->calendar->monthsInYear(); |
|
526 | + } |
|
527 | + |
|
528 | + /** |
|
529 | + * How many days in the current month |
|
530 | + * |
|
531 | + * @return int |
|
532 | + */ |
|
533 | + public function daysInMonth() { |
|
534 | + try { |
|
535 | + return $this->calendar->daysInMonth($this->y, $this->m); |
|
536 | + } catch (\InvalidArgumentException $ex) { |
|
537 | + // calendar.php calls this with "DD MMM" dates, for which we cannot calculate |
|
538 | + // the length of a month. Should we validate this before calling this function? |
|
539 | + return 0; |
|
540 | + } |
|
541 | + } |
|
542 | + |
|
543 | + /** |
|
544 | + * How many days in the current week |
|
545 | + * |
|
546 | + * @return int |
|
547 | + */ |
|
548 | + public function daysInWeek() { |
|
549 | + return $this->calendar->daysInWeek(); |
|
550 | + } |
|
551 | + |
|
552 | + /** |
|
553 | + * Format a date, using similar codes to the PHP date() function. |
|
554 | + * |
|
555 | + * @param string $format See http://php.net/date |
|
556 | + * @param string $qualifier GEDCOM qualifier, so we can choose the right case for the month name. |
|
557 | + * |
|
558 | + * @return string |
|
559 | + */ |
|
560 | + public function format($format, $qualifier = '') { |
|
561 | + // Don’t show exact details for inexact dates |
|
562 | + if (!$this->d) { |
|
563 | + // The comma is for US "M D, Y" dates |
|
564 | + $format = preg_replace('/%[djlDNSwz][,]?/', '', $format); |
|
565 | + } |
|
566 | + if (!$this->m) { |
|
567 | + $format = str_replace(array('%F', '%m', '%M', '%n', '%t'), '', $format); |
|
568 | + } |
|
569 | + if (!$this->y) { |
|
570 | + $format = str_replace(array('%t', '%L', '%G', '%y', '%Y'), '', $format); |
|
571 | + } |
|
572 | + // If we’ve trimmed the format, also trim the punctuation |
|
573 | + if (!$this->d || !$this->m || !$this->y) { |
|
574 | + $format = trim($format, ',. ;/-'); |
|
575 | + } |
|
576 | + if ($this->d && preg_match('/%[djlDNSwz]/', $format)) { |
|
577 | + // If we have a day-number *and* we are being asked to display it, then genitive |
|
578 | + $case = 'GENITIVE'; |
|
579 | + } else { |
|
580 | + switch ($qualifier) { |
|
581 | + case 'TO': |
|
582 | + case 'ABT': |
|
583 | + case 'FROM': |
|
584 | + $case = 'GENITIVE'; |
|
585 | + break; |
|
586 | + case 'AFT': |
|
587 | + $case = 'LOCATIVE'; |
|
588 | + break; |
|
589 | + case 'BEF': |
|
590 | + case 'BET': |
|
591 | + case 'AND': |
|
592 | + $case = 'INSTRUMENTAL'; |
|
593 | + break; |
|
594 | + case '': |
|
595 | + case 'INT': |
|
596 | + case 'EST': |
|
597 | + case 'CAL': |
|
598 | + default: // There shouldn't be any other options... |
|
599 | + $case = 'NOMINATIVE'; |
|
600 | + break; |
|
601 | + } |
|
602 | + } |
|
603 | + // Build up the formatted date, character at a time |
|
604 | + preg_match_all('/%[^%]/', $format, $matches); |
|
605 | + foreach ($matches[0] as $match) { |
|
606 | + switch ($match) { |
|
607 | + case '%d': |
|
608 | + $format = str_replace($match, $this->formatDayZeros(), $format); |
|
609 | + break; |
|
610 | + case '%j': |
|
611 | + $format = str_replace($match, $this->formatDay(), $format); |
|
612 | + break; |
|
613 | + case '%l': |
|
614 | + $format = str_replace($match, $this->formatLongWeekday(), $format); |
|
615 | + break; |
|
616 | + case '%D': |
|
617 | + $format = str_replace($match, $this->formatShortWeekday(), $format); |
|
618 | + break; |
|
619 | + case '%N': |
|
620 | + $format = str_replace($match, $this->formatIsoWeekday(), $format); |
|
621 | + break; |
|
622 | + case '%w': |
|
623 | + $format = str_replace($match, $this->formatNumericWeekday(), $format); |
|
624 | + break; |
|
625 | + case '%z': |
|
626 | + $format = str_replace($match, $this->formatDayOfYear(), $format); |
|
627 | + break; |
|
628 | + case '%F': |
|
629 | + $format = str_replace($match, $this->formatLongMonth($case), $format); |
|
630 | + break; |
|
631 | + case '%m': |
|
632 | + $format = str_replace($match, $this->formatMonthZeros(), $format); |
|
633 | + break; |
|
634 | + case '%M': |
|
635 | + $format = str_replace($match, $this->formatShortMonth(), $format); |
|
636 | + break; |
|
637 | + case '%n': |
|
638 | + $format = str_replace($match, $this->formatMonth(), $format); |
|
639 | + break; |
|
640 | + case '%t': |
|
641 | + $format = str_replace($match, $this->daysInMonth(), $format); |
|
642 | + break; |
|
643 | + case '%L': |
|
644 | + $format = str_replace($match, (int) $this->isLeapYear(), $format); |
|
645 | + break; |
|
646 | + case '%Y': |
|
647 | + $format = str_replace($match, $this->formatLongYear(), $format); |
|
648 | + break; |
|
649 | + case '%y': |
|
650 | + $format = str_replace($match, $this->formatShortYear(), $format); |
|
651 | + break; |
|
652 | + // These 4 extensions are useful for re-formatting gedcom dates. |
|
653 | + case '%@': |
|
654 | + $format = str_replace($match, $this->calendar->gedcomCalendarEscape(), $format); |
|
655 | + break; |
|
656 | + case '%A': |
|
657 | + $format = str_replace($match, $this->formatGedcomDay(), $format); |
|
658 | + break; |
|
659 | + case '%O': |
|
660 | + $format = str_replace($match, $this->formatGedcomMonth(), $format); |
|
661 | + break; |
|
662 | + case '%E': |
|
663 | + $format = str_replace($match, $this->formatGedcomYear(), $format); |
|
664 | + break; |
|
665 | + } |
|
666 | + } |
|
667 | + |
|
668 | + return $format; |
|
669 | + } |
|
670 | + |
|
671 | + /** |
|
672 | + * Generate the %d format for a date. |
|
673 | + * |
|
674 | + * @return string |
|
675 | + */ |
|
676 | + protected function formatDayZeros() { |
|
677 | + if ($this->d > 9) { |
|
678 | + return I18N::digits($this->d); |
|
679 | + } else { |
|
680 | + return I18N::digits('0' . $this->d); |
|
681 | + } |
|
682 | + } |
|
683 | + |
|
684 | + /** |
|
685 | + * Generate the %j format for a date. |
|
686 | + * |
|
687 | + * @return string |
|
688 | + */ |
|
689 | + protected function formatDay() { |
|
690 | + return I18N::digits($this->d); |
|
691 | + } |
|
692 | + |
|
693 | + /** |
|
694 | + * Generate the %l format for a date. |
|
695 | + * |
|
696 | + * @return string |
|
697 | + */ |
|
698 | + protected function formatLongWeekday() { |
|
699 | + return $this->dayNames($this->minJD % $this->calendar->daysInWeek()); |
|
700 | + } |
|
701 | + |
|
702 | + /** |
|
703 | + * Generate the %D format for a date. |
|
704 | + * |
|
705 | + * @return string |
|
706 | + */ |
|
707 | + protected function formatShortWeekday() { |
|
708 | + return $this->dayNamesAbbreviated($this->minJD % $this->calendar->daysInWeek()); |
|
709 | + } |
|
710 | + |
|
711 | + /** |
|
712 | + * Generate the %N format for a date. |
|
713 | + * |
|
714 | + * @return string |
|
715 | + */ |
|
716 | + protected function formatIsoWeekday() { |
|
717 | + return I18N::digits($this->minJD % 7 + 1); |
|
718 | + } |
|
719 | + |
|
720 | + /** |
|
721 | + * Generate the %w format for a date. |
|
722 | + * |
|
723 | + * @return string |
|
724 | + */ |
|
725 | + protected function formatNumericWeekday() { |
|
726 | + return I18N::digits(($this->minJD + 1) % $this->calendar->daysInWeek()); |
|
727 | + } |
|
728 | + |
|
729 | + /** |
|
730 | + * Generate the %z format for a date. |
|
731 | + * |
|
732 | + * @return string |
|
733 | + */ |
|
734 | + protected function formatDayOfYear() { |
|
735 | + return I18N::digits($this->minJD - $this->calendar->ymdToJd($this->y, 1, 1)); |
|
736 | + } |
|
737 | + |
|
738 | + /** |
|
739 | + * Generate the %n format for a date. |
|
740 | + * |
|
741 | + * @return string |
|
742 | + */ |
|
743 | + protected function formatMonth() { |
|
744 | + return I18N::digits($this->m); |
|
745 | + } |
|
746 | + |
|
747 | + /** |
|
748 | + * Generate the %m format for a date. |
|
749 | + * |
|
750 | + * @return string |
|
751 | + */ |
|
752 | + protected function formatMonthZeros() { |
|
753 | + if ($this->m > 9) { |
|
754 | + return I18N::digits($this->m); |
|
755 | + } else { |
|
756 | + return I18N::digits('0' . $this->m); |
|
757 | + } |
|
758 | + } |
|
759 | + |
|
760 | + /** |
|
761 | + * Generate the %F format for a date. |
|
762 | + * |
|
763 | + * @param string $case Which grammatical case shall we use |
|
764 | + * |
|
765 | + * @return string |
|
766 | + */ |
|
767 | + protected function formatLongMonth($case = 'NOMINATIVE') { |
|
768 | + switch ($case) { |
|
769 | + case 'GENITIVE': |
|
770 | + return $this->monthNameGenitiveCase($this->m, $this->isLeapYear()); |
|
771 | + case 'NOMINATIVE': |
|
772 | + return $this->monthNameNominativeCase($this->m, $this->isLeapYear()); |
|
773 | + case 'LOCATIVE': |
|
774 | + return $this->monthNameLocativeCase($this->m, $this->isLeapYear()); |
|
775 | + case 'INSTRUMENTAL': |
|
776 | + return $this->monthNameInstrumentalCase($this->m, $this->isLeapYear()); |
|
777 | + default: |
|
778 | + throw new \InvalidArgumentException($case); |
|
779 | + } |
|
780 | + } |
|
781 | + |
|
782 | + /** |
|
783 | + * Generate the %M format for a date. |
|
784 | + * |
|
785 | + * @return string |
|
786 | + */ |
|
787 | + protected function formatShortMonth() { |
|
788 | + return $this->monthNameAbbreviated($this->m, $this->isLeapYear()); |
|
789 | + } |
|
790 | + |
|
791 | + /** |
|
792 | + * Generate the %y format for a date. |
|
793 | + * |
|
794 | + * NOTE Short year is NOT a 2-digit year. It is for calendars such as hebrew |
|
795 | + * which have a 3-digit form of 4-digit years. |
|
796 | + * |
|
797 | + * @return string |
|
798 | + */ |
|
799 | + protected function formatShortYear() { |
|
800 | + return $this->formatLongYear(); |
|
801 | + } |
|
802 | + |
|
803 | + /** |
|
804 | + * Generate the %A format for a date. |
|
805 | + * |
|
806 | + * @return string |
|
807 | + */ |
|
808 | + protected function formatGedcomDay() { |
|
809 | + if ($this->d == 0) { |
|
810 | + return ''; |
|
811 | + } else { |
|
812 | + return sprintf('%02d', $this->d); |
|
813 | + } |
|
814 | + } |
|
815 | + |
|
816 | + /** |
|
817 | + * Generate the %O format for a date. |
|
818 | + * |
|
819 | + * @return string |
|
820 | + */ |
|
821 | + protected function formatGedcomMonth() { |
|
822 | + // Our simple lookup table doesn't work correctly for Adar on leap years |
|
823 | + if ($this->m == 7 && $this->calendar instanceof JewishCalendar && !$this->calendar->isLeapYear($this->y)) { |
|
824 | + return 'ADR'; |
|
825 | + } else { |
|
826 | + return array_search($this->m, static::$MONTH_ABBREV); |
|
827 | + } |
|
828 | + } |
|
829 | + |
|
830 | + /** |
|
831 | + * Generate the %E format for a date. |
|
832 | + * |
|
833 | + * @return string |
|
834 | + */ |
|
835 | + protected function formatGedcomYear() { |
|
836 | + if ($this->y == 0) { |
|
837 | + return ''; |
|
838 | + } else { |
|
839 | + return sprintf('%04d', $this->y); |
|
840 | + } |
|
841 | + } |
|
842 | + |
|
843 | + /** |
|
844 | + * Generate the %Y format for a date. |
|
845 | + * |
|
846 | + * @return string |
|
847 | + */ |
|
848 | + protected function formatLongYear() { |
|
849 | + return I18N::digits($this->y); |
|
850 | + } |
|
851 | + |
|
852 | + /** |
|
853 | + * Which months follows this one? Calendars with leap-months should provide their own implementation. |
|
854 | + * |
|
855 | + * @return int[] |
|
856 | + */ |
|
857 | + protected function nextMonth() { |
|
858 | + return array($this->m === $this->calendar->monthsInYear() ? $this->nextYear($this->y) : $this->y, ($this->m % $this->calendar->monthsInYear()) + 1); |
|
859 | + } |
|
860 | + |
|
861 | + /** |
|
862 | + * Convert a decimal number to roman numerals |
|
863 | + * |
|
864 | + * @param int $number |
|
865 | + * |
|
866 | + * @return string |
|
867 | + */ |
|
868 | + protected function numberToRomanNumerals($number) { |
|
869 | + if ($number < 1) { |
|
870 | + // Cannot convert zero/negative numbers |
|
871 | + return (string) $number; |
|
872 | + } |
|
873 | + $roman = ''; |
|
874 | + foreach (self::$roman_numerals as $key => $value) { |
|
875 | + while ($number >= $key) { |
|
876 | + $roman .= $value; |
|
877 | + $number -= $key; |
|
878 | + } |
|
879 | + } |
|
880 | + |
|
881 | + return $roman; |
|
882 | + } |
|
883 | + |
|
884 | + /** |
|
885 | + * Convert a roman numeral to decimal |
|
886 | + * |
|
887 | + * @param string $roman |
|
888 | + * |
|
889 | + * @return int |
|
890 | + */ |
|
891 | + protected function romanNumeralsToNumber($roman) { |
|
892 | + $num = 0; |
|
893 | + foreach (self::$roman_numerals as $key => $value) { |
|
894 | + if (strpos($roman, $value) === 0) { |
|
895 | + $num += $key; |
|
896 | + $roman = substr($roman, strlen($value)); |
|
897 | + } |
|
898 | + } |
|
899 | + |
|
900 | + return $num; |
|
901 | + } |
|
902 | + |
|
903 | + /** |
|
904 | + * Get today’s date in the current calendar. |
|
905 | + * |
|
906 | + * @return int[] |
|
907 | + */ |
|
908 | + public function todayYmd() { |
|
909 | + return $this->calendar->jdToYmd(unixtojd()); |
|
910 | + } |
|
911 | + |
|
912 | + /** |
|
913 | + * Convert to today’s date. |
|
914 | + * |
|
915 | + * @return CalendarDate |
|
916 | + */ |
|
917 | + public function today() { |
|
918 | + $tmp = clone $this; |
|
919 | + $ymd = $tmp->todayYmd(); |
|
920 | + $tmp->y = $ymd[0]; |
|
921 | + $tmp->m = $ymd[1]; |
|
922 | + $tmp->d = $ymd[2]; |
|
923 | + $tmp->setJdFromYmd(); |
|
924 | + |
|
925 | + return $tmp; |
|
926 | + } |
|
927 | + |
|
928 | + /** |
|
929 | + * Create a URL that links this date to the WT calendar |
|
930 | + * |
|
931 | + * @param string $date_format |
|
932 | + * |
|
933 | + * @return string |
|
934 | + */ |
|
935 | + public function calendarUrl($date_format) { |
|
936 | + if (strpbrk($date_format, 'dDj') && $this->d) { |
|
937 | + // If the format includes a day, and the date also includes a day, then use the day view |
|
938 | + $view = 'day'; |
|
939 | + } elseif (strpbrk($date_format, 'FMmn') && $this->m) { |
|
940 | + // If the format includes a month, and the date also includes a month, then use the month view |
|
941 | + $view = 'month'; |
|
942 | + } else { |
|
943 | + // Use the year view |
|
944 | + $view = 'year'; |
|
945 | + } |
|
946 | + |
|
947 | + return |
|
948 | + 'calendar.php?cal=' . rawurlencode($this->calendar->gedcomCalendarEscape()) . |
|
949 | + '&year=' . $this->formatGedcomYear() . |
|
950 | + '&month=' . $this->formatGedcomMonth() . |
|
951 | + '&day=' . $this->formatGedcomDay() . |
|
952 | + '&view=' . $view; |
|
953 | + } |
|
954 | 954 | } |