1
|
|
|
<?php |
2
|
|
|
namespace Fisharebest\ExtCalendar; |
3
|
|
|
|
4
|
|
|
use InvalidArgumentException; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* Class JewishCalendar - calculations for the Jewish calendar. |
8
|
|
|
* |
9
|
|
|
* Hebrew characters in the code have either ISO-8859-8 or UTF_8 encoding. |
10
|
|
|
* Hebrew characters in the comments have UTF-8 encoding. |
11
|
|
|
* |
12
|
|
|
* @author Greg Roach <[email protected]> |
13
|
|
|
* @copyright (c) 2014-2020 Greg Roach |
14
|
|
|
* @license This program is free software: you can redistribute it and/or modify |
15
|
|
|
* it under the terms of the GNU General Public License as published by |
16
|
|
|
* the Free Software Foundation, either version 3 of the License, or |
17
|
|
|
* (at your option) any later version. |
18
|
|
|
* |
19
|
|
|
* This program is distributed in the hope that it will be useful, |
20
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
21
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
22
|
|
|
* GNU General Public License for more details. |
23
|
|
|
* |
24
|
|
|
* You should have received a copy of the GNU General Public License |
25
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
26
|
|
|
*/ |
27
|
|
|
class JewishCalendar implements CalendarInterface |
28
|
|
|
{ |
29
|
|
|
/** Optional behaviour for this calendar. */ |
30
|
|
|
const EMULATE_BUG_54254 = 'EMULATE_BUG_54254'; |
31
|
|
|
|
32
|
|
|
/** Place this symbol before the final letter of a sequence of numerals */ |
33
|
|
|
const GERSHAYIM_ISO8859 = '"'; |
34
|
|
|
const GERSHAYIM = "\xd7\xb4"; |
35
|
|
|
|
36
|
|
|
/** Place this symbol after a single numeral */ |
37
|
|
|
const GERESH_ISO8859 = '\''; |
38
|
|
|
const GERESH = "\xd7\xb3"; |
39
|
|
|
|
40
|
|
|
/** The Hebrew word for thousand */ |
41
|
|
|
const ALAFIM_ISO8859 = "\xe0\xec\xf4\xe9\xed"; |
42
|
|
|
const ALAFIM = "\xd7\x90\xd7\x9c\xd7\xa4\xd7\x99\xd7\x9d"; |
43
|
|
|
|
44
|
|
|
/** A year that is one day shorter than normal. */ |
45
|
|
|
const DEFECTIVE_YEAR = -1; |
46
|
|
|
|
47
|
|
|
/** A year that has the normal number of days. */ |
48
|
|
|
const REGULAR_YEAR = 0; |
49
|
|
|
|
50
|
|
|
/** A year that is one day longer than normal. */ |
51
|
|
|
const COMPLETE_YEAR = 1; |
52
|
|
|
|
53
|
|
|
/** @var string[] Hebrew numbers are represented by letters, similar to roman numerals. */ |
54
|
|
|
private static $HEBREW_NUMERALS_ISO8859_8 = array( |
55
|
|
|
400 => "\xfa", |
56
|
|
|
300 => "\xf9", |
57
|
|
|
200 => "\xf8", |
58
|
|
|
100 => "\xf7", |
59
|
|
|
90 => "\xf6", |
60
|
|
|
80 => "\xf4", |
61
|
|
|
70 => "\xf2", |
62
|
|
|
60 => "\xf1", |
63
|
|
|
50 => "\xf0", |
64
|
|
|
40 => "\xee", |
65
|
|
|
30 => "\xec", |
66
|
|
|
20 => "\xeb", |
67
|
|
|
19 => "\xe9\xe8", |
68
|
|
|
18 => "\xe9\xe7", |
69
|
|
|
17 => "\xe9\xe6", |
70
|
|
|
16 => "\xe8\xe6", |
71
|
|
|
15 => "\xe8\xe5", |
72
|
|
|
10 => "\xe9", |
73
|
|
|
9 => "\xe8", |
74
|
|
|
8 => "\xe7", |
75
|
|
|
7 => "\xe6", |
76
|
|
|
6 => "\xe5", |
77
|
|
|
5 => "\xe4", |
78
|
|
|
4 => "\xe3", |
79
|
|
|
3 => "\xe2", |
80
|
|
|
2 => "\xe1", |
81
|
|
|
1 => "\xe0", |
82
|
|
|
); |
83
|
|
|
|
84
|
|
|
/** @var string[] Hebrew numbers are represented by letters, similar to roman numerals. */ |
85
|
|
|
private static $HEBREW_NUMERALS_UTF8 = array( |
86
|
|
|
400 => "\xd7\xaa", |
87
|
|
|
300 => "\xd7\xa9", |
88
|
|
|
200 => "\xd7\xa8", |
89
|
|
|
100 => "\xd7\xa7", |
90
|
|
|
90 => "\xd7\xa6", |
91
|
|
|
80 => "\xd7\xa4", |
92
|
|
|
70 => "\xd7\xa2", |
93
|
|
|
60 => "\xd7\xa1", |
94
|
|
|
50 => "\xd7\xa0", |
95
|
|
|
40 => "\xd7\x9e", |
96
|
|
|
30 => "\xd7\x9c", |
97
|
|
|
20 => "\xd7\x9b", |
98
|
|
|
19 => "\xd7\x99\xd7\x98", |
99
|
|
|
18 => "\xd7\x99\xd7\x97", |
100
|
|
|
17 => "\xd7\x99\xd7\x96", |
101
|
|
|
16 => "\xd7\x98\xd7\x96", |
102
|
|
|
15 => "\xd7\x98\xd7\x95", |
103
|
|
|
10 => "\xd7\x99", |
104
|
|
|
9 => "\xd7\x98", |
105
|
|
|
8 => "\xd7\x97", |
106
|
|
|
7 => "\xd7\x96", |
107
|
|
|
6 => "\xd7\x95", |
108
|
|
|
5 => "\xd7\x94", |
109
|
|
|
4 => "\xd7\x93", |
110
|
|
|
3 => "\xd7\x92", |
111
|
|
|
2 => "\xd7\x91", |
112
|
|
|
1 => "\xd7\x90", |
113
|
|
|
); |
114
|
|
|
|
115
|
|
|
/** @var string[] Some letters have a different final form */ |
116
|
|
|
private static $FINAL_FORMS_UTF8 = array( |
117
|
|
|
"\xd7\x9b" => "\xd7\x9a", |
118
|
|
|
"\xd7\x9e" => "\xd7\x9d", |
119
|
|
|
"\xd7\xa0" => "\xd7\x9f", |
120
|
|
|
"\xd7\xa4" => "\xd7\xa3", |
121
|
|
|
"\xd7\xa6" => "\xd7\xa5", |
122
|
|
|
); |
123
|
|
|
|
124
|
|
|
/** @var int[] These months have fixed lengths. Others are variable. */ |
125
|
|
|
private static $FIXED_MONTH_LENGTHS = array( |
126
|
|
|
1 => 30, 4 => 29, 5 => 30, 7 => 29, 8 => 30, 9 => 29, 10 => 30, 11 => 29, 12 => 30, 13 => 29 |
127
|
|
|
); |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Cumulative number of days for each month in each type of year. |
131
|
|
|
* First index is false/true (non-leap year, leap year) |
132
|
|
|
* Second index is year type (-1, 0, 1) |
133
|
|
|
* Third index is month number (1 ... 13) |
134
|
|
|
* |
135
|
|
|
* @var int[][][] |
136
|
|
|
*/ |
137
|
|
|
private static $CUMULATIVE_DAYS = array( |
138
|
|
|
0 => array( // Non-leap years |
139
|
|
|
self::DEFECTIVE_YEAR => array( |
140
|
|
|
1 => 0, 30, 59, 88, 117, 147, 147, 176, 206, 235, 265, 294, 324 |
141
|
|
|
), |
142
|
|
|
self::REGULAR_YEAR => array( // Regular years |
143
|
|
|
1 => 0, 30, 59, 89, 118, 148, 148, 177, 207, 236, 266, 295, 325 |
144
|
|
|
), |
145
|
|
|
self::COMPLETE_YEAR => array( // Complete years |
146
|
|
|
1 => 0, 30, 60, 90, 119, 149, 149, 178, 208, 237, 267, 296, 326 |
147
|
|
|
), |
148
|
|
|
), |
149
|
|
|
1 => array( // Leap years |
150
|
|
|
self::DEFECTIVE_YEAR => array( // Deficient years |
151
|
|
|
1 => 0, 30, 59, 88, 117, 147, 177, 206, 236, 265, 295, 324, 354 |
152
|
|
|
), |
153
|
|
|
self::REGULAR_YEAR => array( // Regular years |
154
|
|
|
1 => 0, 30, 59, 89, 118, 148, 178, 207, 237, 266, 296, 325, 355 |
155
|
|
|
), |
156
|
|
|
self::COMPLETE_YEAR => array( // Complete years |
157
|
|
|
1 => 0, 30, 60, 90, 119, 149, 179, 208, 238, 267, 297, 326, 356 |
158
|
|
|
), |
159
|
|
|
), |
160
|
|
|
); |
161
|
|
|
|
162
|
|
|
/** @var int[] Rosh Hashanah cannot fall on a Sunday, Wednesday or Friday. Move the year start accordingly. */ |
163
|
|
|
private static $ROSH_HASHANAH = array(347998, 347997, 347997, 347998, 347997, 347998, 347997); |
164
|
|
|
|
165
|
|
|
/** @var mixed[] special behaviour for this calendar */ |
166
|
|
|
protected $options = array( |
167
|
|
|
self::EMULATE_BUG_54254 => false, |
168
|
|
|
); |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* @param mixed[] $options Some calendars have options that change their behaviour. |
172
|
|
|
*/ |
173
|
|
|
public function __construct($options = array()) |
174
|
|
|
{ |
175
|
|
|
$this->options = array_merge($this->options, $options); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Determine the number of days in a specified month, allowing for leap years, etc. |
180
|
|
|
* |
181
|
|
|
* @param int $year |
182
|
|
|
* @param int $month |
183
|
|
|
* |
184
|
|
|
* @return int |
185
|
|
|
*/ |
186
|
|
|
public function daysInMonth($year, $month) |
187
|
|
|
{ |
188
|
|
|
if ($year < 1) { |
189
|
|
|
throw new InvalidArgumentException('Year ' . $year . ' is invalid for this calendar'); |
190
|
|
View Code Duplication |
} elseif ($month < 1 || $month > 13) { |
|
|
|
|
191
|
|
|
throw new InvalidArgumentException('Month ' . $month . ' is invalid for this calendar'); |
192
|
|
|
} elseif ($month == 2) { |
193
|
|
|
return $this->daysInMonthHeshvan($year); |
194
|
|
|
} elseif ($month == 3) { |
195
|
|
|
return $this->daysInMonthKislev($year); |
196
|
|
|
} elseif ($month == 6) { |
197
|
|
|
return $this->daysInMonthAdarI($year); |
198
|
|
|
} else { |
199
|
|
|
return self::$FIXED_MONTH_LENGTHS[$month]; |
200
|
|
|
} |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Determine the number of days in a week. |
205
|
|
|
* |
206
|
|
|
* @return int |
207
|
|
|
*/ |
208
|
|
|
public function daysInWeek() |
209
|
|
|
{ |
210
|
|
|
return 7; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* The escape sequence used to indicate this calendar in GEDCOM files. |
215
|
|
|
* |
216
|
|
|
* @return string |
217
|
|
|
*/ |
218
|
|
|
public function gedcomCalendarEscape() |
219
|
|
|
{ |
220
|
|
|
return '@#DHEBREW@'; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Determine whether or not a given year is a leap-year. |
225
|
|
|
* |
226
|
|
|
* @param int $year |
227
|
|
|
* |
228
|
|
|
* @return bool |
229
|
|
|
*/ |
230
|
|
|
public function isLeapYear($year) |
231
|
|
|
{ |
232
|
|
|
return (7 * $year + 1) % 19 < 7; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* What is the highest Julian day number that can be converted into this calendar. |
237
|
|
|
* |
238
|
|
|
* @return int |
239
|
|
|
*/ |
240
|
|
|
public function jdEnd() |
241
|
|
|
{ |
242
|
|
|
return PHP_INT_MAX; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* What is the lowest Julian day number that can be converted into this calendar. |
247
|
|
|
* |
248
|
|
|
* @return int |
249
|
|
|
*/ |
250
|
|
|
public function jdStart() |
251
|
|
|
{ |
252
|
|
|
return 347998; // 1 Tishri 0001 AM |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Convert a Julian day number into a year. |
257
|
|
|
* |
258
|
|
|
* @param int $julian_day |
259
|
|
|
* |
260
|
|
|
* @return int |
261
|
|
|
*/ |
262
|
|
|
protected function jdToY($julian_day) |
263
|
|
|
{ |
264
|
|
|
// Estimate the year, and underestimate it, it will be refined after |
265
|
|
|
$year = max((int) ((($julian_day - 347998) * 98496) / 35975351) - 1, 1); |
266
|
|
|
|
267
|
|
|
// Adjust by adding years; |
268
|
|
|
while ($julian_day >= $this->yToJd($year + 1)) { |
269
|
|
|
$year++; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
return $year; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Convert a Julian day number into a year/month/day. |
277
|
|
|
* |
278
|
|
|
* @param int $julian_day |
279
|
|
|
* |
280
|
|
|
* @return int[] |
281
|
|
|
*/ |
282
|
|
|
public function jdToYmd($julian_day) |
283
|
|
|
{ |
284
|
|
|
// Find the year, by adding one month at a time to use up the remaining days. |
285
|
|
|
$year = $this->jdToY($julian_day); |
286
|
|
|
$month = 1; |
287
|
|
|
$day = $julian_day - $this->yToJd($year) + 1; |
288
|
|
|
|
289
|
|
|
while ($day > $this->daysInMonth($year, $month)) { |
290
|
|
|
$day -= $this->daysInMonth($year, $month); |
291
|
|
|
$month++; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
// PHP 5.4 and earlier converted non leap-year Adar into month 6, instead of month 7. |
295
|
|
|
$month -= ($month === 7 && $this->options[self::EMULATE_BUG_54254] && !$this->isLeapYear($year)) ? 1 : 0; |
296
|
|
|
|
297
|
|
|
return array($year, $month, $day); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Determine the number of months in a year (if given), |
302
|
|
|
* or the maximum number of months in any year. |
303
|
|
|
* |
304
|
|
|
* @param int|null $year |
305
|
|
|
* |
306
|
|
|
* @return int |
307
|
|
|
*/ |
308
|
|
|
public function monthsInYear($year = null) |
309
|
|
|
{ |
310
|
|
|
if ($year !== null && !$this->isLeapYear($year)) { |
311
|
|
|
return 12; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
return 13; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Calculate the Julian Day number of the first day in a year. |
319
|
|
|
* |
320
|
|
|
* @param int $year |
321
|
|
|
* |
322
|
|
|
* @return int |
323
|
|
|
*/ |
324
|
|
|
protected function yToJd($year) |
325
|
|
|
{ |
326
|
|
|
$div19 = (int) (($year - 1) / 19); |
327
|
|
|
$mod19 = ($year - 1) % 19; |
328
|
|
|
|
329
|
|
|
$months = 235 * $div19 + 12 * $mod19 + (int) ((7 * $mod19 + 1) / 19); |
330
|
|
|
$parts = 204 + 793 * ($months % 1080); |
331
|
|
|
$hours = 5 + 12 * $months + 793 * (int) ($months / 1080) + (int) ($parts / 1080); |
332
|
|
|
$conjunction = 1080 * ($hours % 24) + ($parts % 1080); |
333
|
|
|
$julian_day = 1 + 29 * $months + (int) ($hours / 24); |
334
|
|
|
|
335
|
|
|
if ($conjunction >= 19440 || |
336
|
|
|
$julian_day % 7 === 2 && $conjunction >= 9924 && !$this->isLeapYear($year) || |
337
|
|
|
$julian_day % 7 === 1 && $conjunction >= 16789 && $this->isLeapYear($year - 1) |
338
|
|
|
) { |
339
|
|
|
$julian_day++; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
// The actual year start depends on the day of the week |
343
|
|
|
return $julian_day + self::$ROSH_HASHANAH[$julian_day % 7]; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Convert a year/month/day to a Julian day number. |
348
|
|
|
* |
349
|
|
|
* @param int $year |
350
|
|
|
* @param int $month |
351
|
|
|
* @param int $day |
352
|
|
|
* |
353
|
|
|
* @return int |
354
|
|
|
*/ |
355
|
|
|
public function ymdToJd($year, $month, $day) |
356
|
|
|
{ |
357
|
|
|
return |
358
|
|
|
$this->yToJd($year) + |
359
|
|
|
self::$CUMULATIVE_DAYS[$this->isLeapYear($year)][$this->yearType($year)][$month] + |
360
|
|
|
$day - 1; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* Determine whether a year is normal, defective or complete. |
365
|
|
|
* |
366
|
|
|
* @param int $year |
367
|
|
|
* |
368
|
|
|
* @return int defective (-1), normal (0) or complete (1) |
369
|
|
|
*/ |
370
|
|
|
private function yearType($year) |
371
|
|
|
{ |
372
|
|
|
$year_length = $this->yToJd($year + 1) - $this->yToJd($year); |
373
|
|
|
|
374
|
|
|
if ($year_length === 353 || $year_length === 383) { |
375
|
|
|
return self::DEFECTIVE_YEAR; |
376
|
|
|
} elseif ($year_length === 355 || $year_length === 385) { |
377
|
|
|
return self::COMPLETE_YEAR; |
378
|
|
|
} else { |
379
|
|
|
return self::REGULAR_YEAR; |
380
|
|
|
} |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Calculate the number of days in Heshvan. |
385
|
|
|
* |
386
|
|
|
* @param int $year |
387
|
|
|
* |
388
|
|
|
* @return int |
389
|
|
|
*/ |
390
|
|
|
private function daysInMonthHeshvan($year) |
391
|
|
|
{ |
392
|
|
|
if ($this->yearType($year) === self::COMPLETE_YEAR) { |
393
|
|
|
return 30; |
394
|
|
|
} else { |
395
|
|
|
return 29; |
396
|
|
|
} |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* Calculate the number of days in Kislev. |
401
|
|
|
* |
402
|
|
|
* @param int $year |
403
|
|
|
* |
404
|
|
|
* @return int |
405
|
|
|
*/ |
406
|
|
|
private function daysInMonthKislev($year) |
407
|
|
|
{ |
408
|
|
|
if ($this->yearType($year) === self::DEFECTIVE_YEAR) { |
409
|
|
|
return 29; |
410
|
|
|
} else { |
411
|
|
|
return 30; |
412
|
|
|
} |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
/** |
416
|
|
|
* Calculate the number of days in Adar I. |
417
|
|
|
* |
418
|
|
|
* @param int $year |
419
|
|
|
* |
420
|
|
|
* @return int |
421
|
|
|
*/ |
422
|
|
|
private function daysInMonthAdarI($year) |
423
|
|
|
{ |
424
|
|
|
if ($this->isLeapYear($year)) { |
425
|
|
|
return 30; |
426
|
|
|
} else { |
427
|
|
|
return 0; |
428
|
|
|
} |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* Hebrew month names. |
433
|
|
|
* |
434
|
|
|
* @link https://bugs.php.net/bug.php?id=54254 |
435
|
|
|
* |
436
|
|
|
* @param int $year |
437
|
|
|
* |
438
|
|
|
* @return string[] |
439
|
|
|
*/ |
440
|
|
|
protected function hebrewMonthNames($year) |
441
|
|
|
{ |
442
|
|
|
$leap_year = $this->isLeapYear($year); |
443
|
|
|
|
444
|
|
|
return array( |
445
|
|
|
1 => "\xfa\xf9\xf8\xe9", // Tishri - תשרי |
446
|
|
|
"\xe7\xf9\xe5\xef", // Heshvan - חשון |
447
|
|
|
"\xeb\xf1\xec\xe5", // Kislev - כסלו |
448
|
|
|
"\xe8\xe1\xfa", // Tevet - טבת |
449
|
|
|
"\xf9\xe1\xe8", // Shevat - שבט |
450
|
|
|
$leap_year ? ($this->options[self::EMULATE_BUG_54254] ? "\xe0\xe3\xf8" : "\xe0\xe3\xf8 \xe0'") : "\xe0\xe3\xf8", // Adar I - אדר - אדר א׳ - אדר |
451
|
|
|
$leap_year ? ($this->options[self::EMULATE_BUG_54254] ? "'\xe0\xe3\xf8 \xe1" : "\xe0\xe3\xf8 \xe1'") : "\xe0\xe3\xf8", // Adar II - 'אדר ב - אדר ב׳ - אדר |
452
|
|
|
"\xf0\xe9\xf1\xef", // Nisan - ניסן |
453
|
|
|
"\xe0\xe9\xe9\xf8", // Iyar - אייר |
454
|
|
|
"\xf1\xe9\xe5\xef", // Sivan - סיון |
455
|
|
|
"\xfa\xee\xe5\xe6", // Tammuz - תמוז |
456
|
|
|
"\xe0\xe1", // Av - אב |
457
|
|
|
"\xe0\xec\xe5\xec", // Elul - אלול |
458
|
|
|
); |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* The Hebrew name of a given month. |
463
|
|
|
* |
464
|
|
|
* @param int $year |
465
|
|
|
* @param int $month |
466
|
|
|
* |
467
|
|
|
* @return string |
468
|
|
|
*/ |
469
|
|
|
protected function hebrewMonthName($year, $month) |
470
|
|
|
{ |
471
|
|
|
$months = $this->hebrewMonthNames($year); |
472
|
|
|
|
473
|
|
|
return $months[$month]; |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* Add geresh (׳) and gershayim (״) punctuation to numeric values. |
478
|
|
|
* |
479
|
|
|
* Gereshayim is a contraction of “geresh” and “gershayim”. |
480
|
|
|
* |
481
|
|
|
* @param string $hebrew |
482
|
|
|
* |
483
|
|
|
* @return string |
484
|
|
|
*/ |
485
|
|
|
protected function addGereshayim($hebrew) |
486
|
|
|
{ |
487
|
|
|
switch (strlen($hebrew)) { |
488
|
|
|
case 0: |
489
|
|
|
// Zero, e.g. the zeros from the year 5,000 |
490
|
|
|
return $hebrew; |
491
|
|
|
case 1: |
492
|
|
|
// Single digit - append a geresh |
493
|
|
|
return $hebrew . self::GERESH_ISO8859; |
494
|
|
|
default: |
495
|
|
|
// Multiple digits - insert a gershayim |
496
|
|
|
return substr_replace($hebrew, self::GERSHAYIM_ISO8859, -1, 0); |
497
|
|
|
} |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
/** |
501
|
|
|
* Convert a number into a string, in the style of roman numerals |
502
|
|
|
* |
503
|
|
|
* @param int $number |
504
|
|
|
* @param string[] $numerals |
505
|
|
|
* |
506
|
|
|
* @return string |
507
|
|
|
*/ |
508
|
|
|
private function numberToNumerals($number, array $numerals) |
509
|
|
|
{ |
510
|
|
|
$string = ''; |
511
|
|
|
|
512
|
|
|
while ($number > 0) { |
513
|
|
|
foreach ($numerals as $n => $t) { |
514
|
|
|
if ($number >= $n) { |
515
|
|
|
$string .= $t; |
516
|
|
|
$number -= $n; |
517
|
|
|
break; |
518
|
|
|
} |
519
|
|
|
} |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
return $string; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* Convert a number into Hebrew numerals using UTF8. |
527
|
|
|
* |
528
|
|
|
* @param int $number |
529
|
|
|
* @param bool $show_thousands |
530
|
|
|
* |
531
|
|
|
* @return string |
532
|
|
|
*/ |
533
|
|
|
public function numberToHebrewNumerals($number, $show_thousands) |
534
|
|
|
{ |
535
|
|
|
// Years (e.g. "5782") may be written without the thousands (e.g. just "782"), |
536
|
|
|
// but since there is no zero, the number 5000 must be written as "5 thousand" |
537
|
|
|
if ($show_thousands || $number % 1000 === 0) { |
538
|
|
|
$thousands = (int)($number / 1000); |
539
|
|
|
} else { |
540
|
|
|
$thousands = 0; |
541
|
|
|
} |
542
|
|
|
$number = $number % 1000; |
543
|
|
|
|
544
|
|
|
$hebrew = $this->numberToNumerals($number, self::$HEBREW_NUMERALS_UTF8); |
545
|
|
|
|
546
|
|
|
// Two bytes per UTF8 character |
547
|
|
|
if (strlen($hebrew) === 2) { |
548
|
|
|
// Append a geresh after single-digit |
549
|
|
|
$hebrew .= self::GERESH; |
550
|
|
|
} elseif (strlen($hebrew) > 2) { |
551
|
|
|
// Some letters have a "final" form, when used at the end of a word. |
552
|
|
|
$hebrew = substr($hebrew, 0, -2) . strtr(substr($hebrew, -2), self::$FINAL_FORMS_UTF8); |
553
|
|
|
// Insert a gershayim before the final letter |
554
|
|
|
$hebrew = substr_replace($hebrew, self::GERSHAYIM, -2, 0); |
555
|
|
|
} |
556
|
|
|
|
557
|
|
|
if ($thousands) { |
558
|
|
|
if ($hebrew) { |
559
|
|
|
$hebrew = $this->numberToHebrewNumerals($thousands, $show_thousands) . $hebrew; |
560
|
|
|
} else { |
561
|
|
|
$hebrew = $this->numberToHebrewNumerals($thousands, $show_thousands) . ' ' . self::ALAFIM; |
562
|
|
|
} |
563
|
|
|
} |
564
|
|
|
|
565
|
|
|
return $hebrew; |
566
|
|
|
} |
567
|
|
|
|
568
|
|
|
/** |
569
|
|
|
* Convert a number into Hebrew numerals using ISO8859-8. |
570
|
|
|
* |
571
|
|
|
* @param int $number |
572
|
|
|
* @param bool $gereshayim Add punctuation to numeric values |
573
|
|
|
* |
574
|
|
|
* @return string |
575
|
|
|
*/ |
576
|
|
|
protected function numberToHebrewNumeralsIso8859($number, $gereshayim) |
577
|
|
|
{ |
578
|
|
|
$hebrew = $this->numberToNumerals($number, self::$HEBREW_NUMERALS_ISO8859_8); |
579
|
|
|
|
580
|
|
|
// Hebrew numerals are letters. Add punctuation to prevent confusion with actual words. |
581
|
|
|
if ($gereshayim) { |
582
|
|
|
return $this->addGereshayim($hebrew); |
583
|
|
|
} else { |
584
|
|
|
return $hebrew; |
585
|
|
|
} |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
/** |
589
|
|
|
* Format a year using Hebrew numerals. |
590
|
|
|
* |
591
|
|
|
* @param int $year |
592
|
|
|
* @param bool $alafim_geresh Add a geresh (׳) after thousands |
593
|
|
|
* @param bool $alafim Add the word for thousands after the thousands |
594
|
|
|
* @param bool $gereshayim Add geresh (׳) and gershayim (״) punctuation to numeric values |
595
|
|
|
* |
596
|
|
|
* @return string |
597
|
|
|
*/ |
598
|
|
|
protected function yearToHebrewNumerals($year, $alafim_geresh, $alafim, $gereshayim) |
599
|
|
|
{ |
600
|
|
|
if ($year < 1000) { |
601
|
|
|
return $this->numberToHebrewNumeralsIso8859($year, $gereshayim); |
602
|
|
|
} else { |
603
|
|
|
$thousands = $this->numberToHebrewNumeralsIso8859((int) ($year / 1000), false); |
604
|
|
|
if ($alafim_geresh) { |
605
|
|
|
$thousands .= self::GERESH_ISO8859; |
606
|
|
|
} |
607
|
|
|
if ($alafim) { |
608
|
|
|
$thousands .= ' ' . self::ALAFIM_ISO8859 . ' '; |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
return $thousands . $this->numberToHebrewNumeralsIso8859($year % 1000, $gereshayim); |
612
|
|
|
} |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
/** |
616
|
|
|
* Convert a Julian Day number into a Hebrew date. |
617
|
|
|
* |
618
|
|
|
* @param int $julian_day |
619
|
|
|
* @param bool $alafim_garesh |
620
|
|
|
* @param bool $alafim |
621
|
|
|
* @param bool $gereshayim |
622
|
|
|
* |
623
|
|
|
* @return string |
624
|
|
|
*/ |
625
|
|
|
public function jdToHebrew($julian_day, $alafim_garesh, $alafim, $gereshayim) |
626
|
|
|
{ |
627
|
|
|
list($year, $month, $day) = $this->jdToYmd($julian_day); |
628
|
|
|
|
629
|
|
|
return |
630
|
|
|
$this->numberToHebrewNumeralsIso8859($day, $gereshayim) . ' ' . |
631
|
|
|
$this->hebrewMonthName($year, $month) . ' ' . |
632
|
|
|
$this->yearToHebrewNumerals($year, $alafim_garesh, $alafim, $gereshayim); |
633
|
|
|
} |
634
|
|
|
} |
635
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.