@hebcal/core 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,351 +1,6 @@
1
- /*! @hebcal/core v5.0.0 */
1
+ /*! @hebcal/core v5.0.2 */
2
2
  'use strict';
3
3
 
4
- const GERESH = '׳';
5
- const GERSHAYIM = '״';
6
- const heb2num = {
7
- 'א': 1,
8
- 'ב': 2,
9
- 'ג': 3,
10
- 'ד': 4,
11
- 'ה': 5,
12
- 'ו': 6,
13
- 'ז': 7,
14
- 'ח': 8,
15
- 'ט': 9,
16
- 'י': 10,
17
- 'כ': 20,
18
- 'ל': 30,
19
- 'מ': 40,
20
- 'נ': 50,
21
- 'ס': 60,
22
- 'ע': 70,
23
- 'פ': 80,
24
- 'צ': 90,
25
- 'ק': 100,
26
- 'ר': 200,
27
- 'ש': 300,
28
- 'ת': 400
29
- };
30
- const num2heb = new Map();
31
- Object.keys(heb2num).forEach(key => {
32
- const val = heb2num[key];
33
- num2heb.set(val, key);
34
- });
35
-
36
- /**
37
- * @private
38
- * @param {number} num
39
- * @return {number[]}
40
- */
41
- function num2digits(num) {
42
- const digits = [];
43
- while (num > 0) {
44
- if (num === 15 || num === 16) {
45
- digits.push(9);
46
- digits.push(num - 9);
47
- break;
48
- }
49
- let incr = 100;
50
- let i;
51
- for (i = 400; i > num; i -= incr) {
52
- if (i === incr) {
53
- incr = incr / 10;
54
- }
55
- }
56
- digits.push(i);
57
- num -= i;
58
- }
59
- return digits;
60
- }
61
-
62
- /**
63
- * Converts a numerical value to a string of Hebrew letters.
64
- *
65
- * When specifying years of the Hebrew calendar in the present millennium,
66
- * we omit the thousands (which is presently 5 [ה]).
67
- * @example
68
- * gematriya(5774) // 'תשע״ד' - cropped to 774
69
- * gematriya(25) // 'כ״ה'
70
- * gematriya(60) // 'ס׳'
71
- * gematriya(3761) // 'ג׳תשס״א'
72
- * gematriya(1123) // 'א׳קכ״ג'
73
- * @param {number} number
74
- * @return {string}
75
- */
76
- function gematriya(number) {
77
- const num = parseInt(number, 10);
78
- if (!num) {
79
- throw new TypeError(`invalid parameter to gematriya ${number}`);
80
- }
81
- let str = '';
82
- const thousands = Math.floor(num / 1000);
83
- if (thousands > 0 && thousands !== 5) {
84
- const tdigits = num2digits(thousands);
85
- for (const tdig of tdigits) {
86
- str += num2heb.get(tdig);
87
- }
88
- str += GERESH;
89
- }
90
- const digits = num2digits(num % 1000);
91
- if (digits.length == 1) {
92
- return str + num2heb.get(digits[0]) + GERESH;
93
- }
94
- for (let i = 0; i < digits.length; i++) {
95
- if (i + 1 === digits.length) {
96
- str += GERSHAYIM;
97
- }
98
- str += num2heb.get(digits[i]);
99
- }
100
- return str;
101
- }
102
-
103
- /**
104
- * Converts a string of Hebrew letters to a numerical value.
105
- *
106
- * Only considers the value of Hebrew letters `א` through `ת`.
107
- * Ignores final Hebrew letters such as `ך` (kaf sofit) or `ם` (mem sofit)
108
- * and vowels (nekudot).
109
- *
110
- * @param {string} str
111
- * @return {number}
112
- */
113
- function gematriyaStrToNum(str) {
114
- let num = 0;
115
- const gereshIdx = str.indexOf(GERESH);
116
- if (gereshIdx !== -1 && gereshIdx !== str.length - 1) {
117
- const thousands = str.substring(0, gereshIdx);
118
- num += gematriyaStrToNum(thousands) * 1000;
119
- str = str.substring(gereshIdx);
120
- }
121
- for (const ch of str) {
122
- const n = heb2num[ch];
123
- if (typeof n === 'number') {
124
- num += n;
125
- }
126
- }
127
- return num;
128
- }
129
-
130
- const noopLocale = {
131
- headers: {
132
- 'plural-forms': 'nplurals=2; plural=(n!=1);'
133
- },
134
- contexts: {
135
- '': {}
136
- }
137
- };
138
- const alias = {
139
- 'h': 'he',
140
- 'a': 'ashkenazi',
141
- 's': 'en',
142
- '': 'en'
143
- };
144
-
145
- /** @private */
146
- const locales = new Map();
147
- /** @private */
148
- let activeLocale = null;
149
- /** @private */
150
- let activeName = null;
151
-
152
- /**
153
- * A locale in Hebcal is used for translations/transliterations of
154
- * holidays. `@hebcal/core` supports four locales by default
155
- * * `en` - default, Sephardic transliterations (e.g. "Shabbat")
156
- * * `ashkenazi` - Ashkenazi transliterations (e.g. "Shabbos")
157
- * * `he` - Hebrew (e.g. "שַׁבָּת")
158
- * * `he-x-NoNikud` - Hebrew without nikud (e.g. "שבת")
159
- */
160
- class Locale {
161
- /**
162
- * Returns translation only if `locale` offers a non-empty translation for `id`.
163
- * Otherwise, returns `undefined`.
164
- * @param {string} id Message ID to translate
165
- * @param {string} [locale] Optional locale name (i.e: `'he'`, `'fr'`). Defaults to active locale.
166
- * @return {string}
167
- */
168
- static lookupTranslation(id, locale) {
169
- const locale0 = locale === null || locale === void 0 ? void 0 : locale.toLowerCase();
170
- const loc = typeof locale == 'string' && locales.get(locale0) || activeLocale;
171
- const array = loc[id];
172
- if (array && array.length && array[0].length) {
173
- return array[0];
174
- }
175
- return undefined;
176
- }
177
-
178
- /**
179
- * By default, if no translation was found, returns `id`.
180
- * @param {string} id Message ID to translate
181
- * @param {string} [locale] Optional locale name (i.e: `'he'`, `'fr'`). Defaults to active locale.
182
- * @return {string}
183
- */
184
- static gettext(id, locale) {
185
- const text = this.lookupTranslation(id, locale);
186
- if (typeof text == 'undefined') {
187
- return id;
188
- }
189
- return text;
190
- }
191
-
192
- /**
193
- * Register locale translations.
194
- * @param {string} locale Locale name (i.e.: `'he'`, `'fr'`)
195
- * @param {LocaleData} data parsed data from a `.po` file.
196
- */
197
- static addLocale(locale, data) {
198
- if (typeof locale !== 'string') {
199
- throw new TypeError(`Invalid locale name: ${locale}`);
200
- }
201
- if (typeof data.contexts !== 'object' || typeof data.contexts[''] !== 'object') {
202
- throw new TypeError(`Locale '${locale}' invalid compact format`);
203
- }
204
- locales.set(locale.toLowerCase(), data.contexts['']);
205
- }
206
-
207
- /**
208
- * Adds a translation to `locale`, replacing any previous translation.
209
- * @param {string} locale Locale name (i.e: `'he'`, `'fr'`).
210
- * @param {string} id Message ID to translate
211
- * @param {string} translation Translation text
212
- */
213
- static addTranslation(locale, id, translation) {
214
- if (typeof locale !== 'string') {
215
- throw new TypeError(`Invalid locale name: ${locale}`);
216
- }
217
- const locale0 = locale.toLowerCase();
218
- const loc = locales.get(locale0);
219
- if (!loc) {
220
- throw new TypeError(`Unknown locale: ${locale}`);
221
- }
222
- if (typeof id !== 'string' || id.length === 0) {
223
- throw new TypeError(`Invalid id: ${id}`);
224
- }
225
- const isArray = Array.isArray(translation);
226
- if (isArray) {
227
- const t0 = translation[0];
228
- if (typeof t0 !== 'string' || t0.length === 0) {
229
- throw new TypeError(`Invalid translation array: ${translation}`);
230
- }
231
- } else if (typeof translation !== 'string') {
232
- throw new TypeError(`Invalid translation: ${translation}`);
233
- }
234
- loc[id] = isArray ? translation : [translation];
235
- }
236
- /**
237
- * Adds multiple translations to `locale`, replacing any previous translations.
238
- * @param {string} locale Locale name (i.e: `'he'`, `'fr'`).
239
- * @param {LocaleData} data parsed data from a `.po` file.
240
- */
241
- static addTranslations(locale, data) {
242
- if (typeof locale !== 'string') {
243
- throw new TypeError(`Invalid locale name: ${locale}`);
244
- }
245
- const locale0 = locale.toLowerCase();
246
- const loc = locales.get(locale0);
247
- if (!loc) {
248
- throw new TypeError(`Unknown locale: ${locale}`);
249
- }
250
- if (typeof data.contexts !== 'object' || typeof data.contexts[''] !== 'object') {
251
- throw new TypeError(`Locale '${locale}' invalid compact format`);
252
- }
253
- const ctx = data.contexts[''];
254
- Object.keys(ctx).forEach(id => {
255
- loc[id] = ctx[id];
256
- });
257
- }
258
- /**
259
- * Activates a locale. Throws an error if the locale has not been previously added.
260
- * After setting the locale to be used, all strings marked for translations
261
- * will be represented by the corresponding translation in the specified locale.
262
- * @param {string} locale Locale name (i.e: `'he'`, `'fr'`)
263
- * @return {LocaleData}
264
- */
265
- static useLocale(locale) {
266
- const locale0 = locale.toLowerCase();
267
- const obj = locales.get(locale0);
268
- if (!obj) {
269
- throw new RangeError(`Locale '${locale}' not found`);
270
- }
271
- activeName = alias[locale0] || locale0;
272
- activeLocale = obj;
273
- return activeLocale;
274
- }
275
-
276
- /**
277
- * Returns the name of the active locale (i.e. 'he', 'ashkenazi', 'fr')
278
- * @return {string}
279
- */
280
- static getLocaleName() {
281
- return activeName;
282
- }
283
-
284
- /**
285
- * Returns the names of registered locales
286
- * @return {string[]}
287
- */
288
- static getLocaleNames() {
289
- const keys = Array.from(locales.keys());
290
- return keys.sort((a, b) => a.localeCompare(b));
291
- }
292
-
293
- /**
294
- * @param {number} n
295
- * @param {string} [locale] Optional locale name (i.e: `'he'`, `'fr'`). Defaults to active locale.
296
- * @return {string}
297
- */
298
- static ordinal(n, locale) {
299
- const locale1 = locale === null || locale === void 0 ? void 0 : locale.toLowerCase();
300
- const locale0 = locale1 || activeName;
301
- if (!locale0) {
302
- return this.getEnOrdinal(n);
303
- }
304
- switch (locale0) {
305
- case 'en':
306
- case 's':
307
- case 'a':
308
- case 'ashkenazi':
309
- case 'ashkenazi_litvish':
310
- case 'ashkenazi_poylish':
311
- case 'ashkenazi_standard':
312
- return this.getEnOrdinal(n);
313
- case 'es':
314
- return n + 'º';
315
- case 'h':
316
- case 'he':
317
- case 'he-x-nonikud':
318
- return String(n);
319
- default:
320
- return n + '.';
321
- }
322
- }
323
-
324
- /**
325
- * @private
326
- * @param {number} n
327
- * @return {string}
328
- */
329
- static getEnOrdinal(n) {
330
- const s = ['th', 'st', 'nd', 'rd'];
331
- const v = n % 100;
332
- return n + (s[(v - 20) % 10] || s[v] || s[0]);
333
- }
334
-
335
- /**
336
- * Removes nekudot from Hebrew string
337
- * @param {string} str
338
- * @return {string}
339
- */
340
- static hebrewStripNikkud(str) {
341
- return str.replace(/[\u0590-\u05bd]/g, '').replace(/[\u05bf-\u05c7]/g, '');
342
- }
343
- }
344
- Locale.addLocale('en', noopLocale);
345
- Locale.addLocale('s', noopLocale);
346
- Locale.addLocale('', noopLocale);
347
- Locale.useLocale('en');
348
-
349
4
  /** @private */
350
5
  const lengths = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
351
6
  /** @private */
@@ -939,76 +594,413 @@ function toSimpleHebrewDate(obj) {
939
594
  else {
940
595
  throw new TypeError(`Argument not a Date: ${obj}`);
941
596
  }
942
- }
943
- function getYahrzeitHD(hyear, date) {
944
- let hDeath = toSimpleHebrewDate(date);
945
- if (hyear <= hDeath.yy) {
946
- // Hebrew year ${hyear} occurs on or before original date in ${hDeath.yy}
947
- return undefined;
597
+ }
598
+ function getYahrzeitHD(hyear, date) {
599
+ let hDeath = toSimpleHebrewDate(date);
600
+ if (hyear <= hDeath.yy) {
601
+ // Hebrew year ${hyear} occurs on or before original date in ${hDeath.yy}
602
+ return undefined;
603
+ }
604
+ if (hDeath.mm == CHESHVAN && hDeath.dd == 30 && !longCheshvan(hDeath.yy + 1)) {
605
+ // If it's Heshvan 30 it depends on the first anniversary;
606
+ // if that was not Heshvan 30, use the day before Kislev 1.
607
+ hDeath = abs2hebrew(hebrew2abs(hyear, KISLEV$1, 1) - 1);
608
+ }
609
+ else if (hDeath.mm == KISLEV$1 && hDeath.dd == 30 && shortKislev(hDeath.yy + 1)) {
610
+ // If it's Kislev 30 it depends on the first anniversary;
611
+ // if that was not Kislev 30, use the day before Teveth 1.
612
+ hDeath = abs2hebrew(hebrew2abs(hyear, TEVET$1, 1) - 1);
613
+ }
614
+ else if (hDeath.mm == ADAR_II) {
615
+ // If it's Adar II, use the same day in last month of year (Adar or Adar II).
616
+ hDeath.mm = monthsInYear(hyear);
617
+ }
618
+ else if (hDeath.mm == ADAR_I$1 && hDeath.dd == 30 && !isLeapYear(hyear)) {
619
+ // If it's the 30th in Adar I and year is not a leap year
620
+ // (so Adar has only 29 days), use the last day in Shevat.
621
+ hDeath.dd = 30;
622
+ hDeath.mm = SHVAT;
623
+ }
624
+ // In all other cases, use the normal anniversary of the date of death.
625
+ // advance day to rosh chodesh if needed
626
+ if (hDeath.mm == CHESHVAN && hDeath.dd == 30 && !longCheshvan(hyear)) {
627
+ hDeath.mm = KISLEV$1;
628
+ hDeath.dd = 1;
629
+ }
630
+ else if (hDeath.mm == KISLEV$1 && hDeath.dd == 30 && shortKislev(hyear)) {
631
+ hDeath.mm = TEVET$1;
632
+ hDeath.dd = 1;
633
+ }
634
+ hDeath.yy = hyear;
635
+ return hDeath;
636
+ }
637
+ function getBirthdayHD(hyear, date) {
638
+ const orig = toSimpleHebrewDate(date);
639
+ const origYear = orig.yy;
640
+ if (hyear === origYear) {
641
+ return orig;
642
+ }
643
+ else if (hyear < origYear) {
644
+ // Hebrew year ${hyear} occurs on or before original date in ${origYear}
645
+ return undefined;
646
+ }
647
+ const isOrigLeap = isLeapYear(origYear);
648
+ let month = orig.mm;
649
+ let day = orig.dd;
650
+ if ((month == ADAR_I$1 && !isOrigLeap) || (month == ADAR_II && isOrigLeap)) {
651
+ month = monthsInYear(hyear);
652
+ }
653
+ else if (month == CHESHVAN && day == 30 && !longCheshvan(hyear)) {
654
+ month = KISLEV$1;
655
+ day = 1;
656
+ }
657
+ else if (month == KISLEV$1 && day == 30 && shortKislev(hyear)) {
658
+ month = TEVET$1;
659
+ day = 1;
660
+ }
661
+ else if (month == ADAR_I$1 && day == 30 && isOrigLeap && !isLeapYear(hyear)) {
662
+ month = NISAN$3;
663
+ day = 1;
664
+ }
665
+ return { yy: hyear, mm: month, dd: day };
666
+ }
667
+
668
+ const GERESH = '׳';
669
+ const GERSHAYIM = '״';
670
+ const alefbet = {
671
+ 'א': 1,
672
+ 'ב': 2,
673
+ 'ג': 3,
674
+ 'ד': 4,
675
+ 'ה': 5,
676
+ 'ו': 6,
677
+ 'ז': 7,
678
+ 'ח': 8,
679
+ 'ט': 9,
680
+ 'י': 10,
681
+ 'כ': 20,
682
+ 'ל': 30,
683
+ 'מ': 40,
684
+ 'נ': 50,
685
+ 'ס': 60,
686
+ 'ע': 70,
687
+ 'פ': 80,
688
+ 'צ': 90,
689
+ 'ק': 100,
690
+ 'ר': 200,
691
+ 'ש': 300,
692
+ 'ת': 400,
693
+ };
694
+ const heb2num = new Map();
695
+ const num2heb = new Map();
696
+ for (const [key, val] of Object.entries(alefbet)) {
697
+ heb2num.set(key, val);
698
+ num2heb.set(val, key);
699
+ }
700
+ function num2digits(num) {
701
+ const digits = [];
702
+ while (num > 0) {
703
+ if (num === 15 || num === 16) {
704
+ digits.push(9);
705
+ digits.push(num - 9);
706
+ break;
707
+ }
708
+ let incr = 100;
709
+ let i;
710
+ for (i = 400; i > num; i -= incr) {
711
+ if (i === incr) {
712
+ incr = incr / 10;
713
+ }
714
+ }
715
+ digits.push(i);
716
+ num -= i;
717
+ }
718
+ return digits;
719
+ }
720
+ /**
721
+ * Converts a numerical value to a string of Hebrew letters.
722
+ *
723
+ * When specifying years of the Hebrew calendar in the present millennium,
724
+ * we omit the thousands (which is presently 5 [ה]).
725
+ * @example
726
+ * gematriya(5774) // 'תשע״ד' - cropped to 774
727
+ * gematriya(25) // 'כ״ה'
728
+ * gematriya(60) // 'ס׳'
729
+ * gematriya(3761) // 'ג׳תשס״א'
730
+ * gematriya(1123) // 'א׳קכ״ג'
731
+ * @param {number} num
732
+ * @return {string}
733
+ */
734
+ function gematriya(num) {
735
+ const num0 = num;
736
+ const num1 = parseInt(num0, 10);
737
+ if (!num1) {
738
+ throw new TypeError(`invalid parameter to gematriya ${num}`);
739
+ }
740
+ let str = '';
741
+ const thousands = Math.floor(num1 / 1000);
742
+ if (thousands > 0 && thousands !== 5) {
743
+ const tdigits = num2digits(thousands);
744
+ for (const tdig of tdigits) {
745
+ str += num2heb.get(tdig);
746
+ }
747
+ str += GERESH;
748
+ }
749
+ const digits = num2digits(num1 % 1000);
750
+ if (digits.length == 1) {
751
+ return str + num2heb.get(digits[0]) + GERESH;
752
+ }
753
+ for (let i = 0; i < digits.length; i++) {
754
+ if (i + 1 === digits.length) {
755
+ str += GERSHAYIM;
756
+ }
757
+ str += num2heb.get(digits[i]);
758
+ }
759
+ return str;
760
+ }
761
+ /**
762
+ * Converts a string of Hebrew letters to a numerical value.
763
+ *
764
+ * Only considers the value of Hebrew letters `א` through `ת`.
765
+ * Ignores final Hebrew letters such as `ך` (kaf sofit) or `ם` (mem sofit)
766
+ * and vowels (nekudot).
767
+ *
768
+ * @param {string} str
769
+ * @return {number}
770
+ */
771
+ function gematriyaStrToNum(str) {
772
+ let num = 0;
773
+ const gereshIdx = str.indexOf(GERESH);
774
+ if (gereshIdx !== -1 && gereshIdx !== str.length - 1) {
775
+ const thousands = str.substring(0, gereshIdx);
776
+ num += gematriyaStrToNum(thousands) * 1000;
777
+ str = str.substring(gereshIdx);
778
+ }
779
+ for (const ch of str) {
780
+ const n = heb2num.get(ch);
781
+ if (typeof n === 'number') {
782
+ num += n;
783
+ }
784
+ }
785
+ return num;
786
+ }
787
+
788
+ const noopLocale = {
789
+ headers: {
790
+ 'plural-forms': 'nplurals=2; plural=(n!=1);'
791
+ },
792
+ contexts: {
793
+ '': {}
794
+ }
795
+ };
796
+ const alias = {
797
+ 'h': 'he',
798
+ 'a': 'ashkenazi',
799
+ 's': 'en',
800
+ '': 'en'
801
+ };
802
+
803
+ /** @private */
804
+ const locales = new Map();
805
+ /** @private */
806
+ let activeLocale = null;
807
+ /** @private */
808
+ let activeName = null;
809
+
810
+ /**
811
+ * A locale in Hebcal is used for translations/transliterations of
812
+ * holidays. `@hebcal/core` supports four locales by default
813
+ * * `en` - default, Sephardic transliterations (e.g. "Shabbat")
814
+ * * `ashkenazi` - Ashkenazi transliterations (e.g. "Shabbos")
815
+ * * `he` - Hebrew (e.g. "שַׁבָּת")
816
+ * * `he-x-NoNikud` - Hebrew without nikud (e.g. "שבת")
817
+ */
818
+ class Locale {
819
+ /**
820
+ * Returns translation only if `locale` offers a non-empty translation for `id`.
821
+ * Otherwise, returns `undefined`.
822
+ * @param {string} id Message ID to translate
823
+ * @param {string} [locale] Optional locale name (i.e: `'he'`, `'fr'`). Defaults to active locale.
824
+ * @return {string}
825
+ */
826
+ static lookupTranslation(id, locale) {
827
+ const locale0 = locale === null || locale === void 0 ? void 0 : locale.toLowerCase();
828
+ const loc = typeof locale == 'string' && locales.get(locale0) || activeLocale;
829
+ const array = loc[id];
830
+ if (array && array.length && array[0].length) {
831
+ return array[0];
832
+ }
833
+ return undefined;
834
+ }
835
+
836
+ /**
837
+ * By default, if no translation was found, returns `id`.
838
+ * @param {string} id Message ID to translate
839
+ * @param {string} [locale] Optional locale name (i.e: `'he'`, `'fr'`). Defaults to active locale.
840
+ * @return {string}
841
+ */
842
+ static gettext(id, locale) {
843
+ const text = this.lookupTranslation(id, locale);
844
+ if (typeof text == 'undefined') {
845
+ return id;
948
846
  }
949
- if (hDeath.mm == CHESHVAN && hDeath.dd == 30 && !longCheshvan(hDeath.yy + 1)) {
950
- // If it's Heshvan 30 it depends on the first anniversary;
951
- // if that was not Heshvan 30, use the day before Kislev 1.
952
- hDeath = abs2hebrew(hebrew2abs(hyear, KISLEV$1, 1) - 1);
847
+ return text;
848
+ }
849
+
850
+ /**
851
+ * Register locale translations.
852
+ * @param {string} locale Locale name (i.e.: `'he'`, `'fr'`)
853
+ * @param {LocaleData} data parsed data from a `.po` file.
854
+ */
855
+ static addLocale(locale, data) {
856
+ if (typeof locale !== 'string') {
857
+ throw new TypeError(`Invalid locale name: ${locale}`);
953
858
  }
954
- else if (hDeath.mm == KISLEV$1 && hDeath.dd == 30 && shortKislev(hDeath.yy + 1)) {
955
- // If it's Kislev 30 it depends on the first anniversary;
956
- // if that was not Kislev 30, use the day before Teveth 1.
957
- hDeath = abs2hebrew(hebrew2abs(hyear, TEVET$1, 1) - 1);
859
+ if (typeof data.contexts !== 'object' || typeof data.contexts[''] !== 'object') {
860
+ throw new TypeError(`Locale '${locale}' invalid compact format`);
958
861
  }
959
- else if (hDeath.mm == ADAR_II) {
960
- // If it's Adar II, use the same day in last month of year (Adar or Adar II).
961
- hDeath.mm = monthsInYear(hyear);
862
+ locales.set(locale.toLowerCase(), data.contexts['']);
863
+ }
864
+
865
+ /**
866
+ * Adds a translation to `locale`, replacing any previous translation.
867
+ * @param {string} locale Locale name (i.e: `'he'`, `'fr'`).
868
+ * @param {string} id Message ID to translate
869
+ * @param {string} translation Translation text
870
+ */
871
+ static addTranslation(locale, id, translation) {
872
+ if (typeof locale !== 'string') {
873
+ throw new TypeError(`Invalid locale name: ${locale}`);
962
874
  }
963
- else if (hDeath.mm == ADAR_I$1 && hDeath.dd == 30 && !isLeapYear(hyear)) {
964
- // If it's the 30th in Adar I and year is not a leap year
965
- // (so Adar has only 29 days), use the last day in Shevat.
966
- hDeath.dd = 30;
967
- hDeath.mm = SHVAT;
875
+ const locale0 = locale.toLowerCase();
876
+ const loc = locales.get(locale0);
877
+ if (!loc) {
878
+ throw new TypeError(`Unknown locale: ${locale}`);
968
879
  }
969
- // In all other cases, use the normal anniversary of the date of death.
970
- // advance day to rosh chodesh if needed
971
- if (hDeath.mm == CHESHVAN && hDeath.dd == 30 && !longCheshvan(hyear)) {
972
- hDeath.mm = KISLEV$1;
973
- hDeath.dd = 1;
880
+ if (typeof id !== 'string' || id.length === 0) {
881
+ throw new TypeError(`Invalid id: ${id}`);
974
882
  }
975
- else if (hDeath.mm == KISLEV$1 && hDeath.dd == 30 && shortKislev(hyear)) {
976
- hDeath.mm = TEVET$1;
977
- hDeath.dd = 1;
883
+ const isArray = Array.isArray(translation);
884
+ if (isArray) {
885
+ const t0 = translation[0];
886
+ if (typeof t0 !== 'string' || t0.length === 0) {
887
+ throw new TypeError(`Invalid translation array: ${translation}`);
888
+ }
889
+ } else if (typeof translation !== 'string') {
890
+ throw new TypeError(`Invalid translation: ${translation}`);
978
891
  }
979
- hDeath.yy = hyear;
980
- return hDeath;
981
- }
982
- function getBirthdayHD(hyear, date) {
983
- const orig = toSimpleHebrewDate(date);
984
- const origYear = orig.yy;
985
- if (hyear === origYear) {
986
- return orig;
892
+ loc[id] = isArray ? translation : [translation];
893
+ }
894
+ /**
895
+ * Adds multiple translations to `locale`, replacing any previous translations.
896
+ * @param {string} locale Locale name (i.e: `'he'`, `'fr'`).
897
+ * @param {LocaleData} data parsed data from a `.po` file.
898
+ */
899
+ static addTranslations(locale, data) {
900
+ if (typeof locale !== 'string') {
901
+ throw new TypeError(`Invalid locale name: ${locale}`);
987
902
  }
988
- else if (hyear < origYear) {
989
- // Hebrew year ${hyear} occurs on or before original date in ${origYear}
990
- return undefined;
903
+ const locale0 = locale.toLowerCase();
904
+ const loc = locales.get(locale0);
905
+ if (!loc) {
906
+ throw new TypeError(`Unknown locale: ${locale}`);
991
907
  }
992
- const isOrigLeap = isLeapYear(origYear);
993
- let month = orig.mm;
994
- let day = orig.dd;
995
- if ((month == ADAR_I$1 && !isOrigLeap) || (month == ADAR_II && isOrigLeap)) {
996
- month = monthsInYear(hyear);
908
+ if (typeof data.contexts !== 'object' || typeof data.contexts[''] !== 'object') {
909
+ throw new TypeError(`Locale '${locale}' invalid compact format`);
997
910
  }
998
- else if (month == CHESHVAN && day == 30 && !longCheshvan(hyear)) {
999
- month = KISLEV$1;
1000
- day = 1;
911
+ const ctx = data.contexts[''];
912
+ Object.assign(loc, ctx);
913
+ }
914
+ /**
915
+ * Activates a locale. Throws an error if the locale has not been previously added.
916
+ * After setting the locale to be used, all strings marked for translations
917
+ * will be represented by the corresponding translation in the specified locale.
918
+ * @param {string} locale Locale name (i.e: `'he'`, `'fr'`)
919
+ * @return {LocaleData}
920
+ */
921
+ static useLocale(locale) {
922
+ const locale0 = locale.toLowerCase();
923
+ const obj = locales.get(locale0);
924
+ if (!obj) {
925
+ throw new RangeError(`Locale '${locale}' not found`);
1001
926
  }
1002
- else if (month == KISLEV$1 && day == 30 && shortKislev(hyear)) {
1003
- month = TEVET$1;
1004
- day = 1;
927
+ activeName = alias[locale0] || locale0;
928
+ activeLocale = obj;
929
+ return activeLocale;
930
+ }
931
+
932
+ /**
933
+ * Returns the name of the active locale (i.e. 'he', 'ashkenazi', 'fr')
934
+ * @return {string}
935
+ */
936
+ static getLocaleName() {
937
+ return activeName;
938
+ }
939
+
940
+ /**
941
+ * Returns the names of registered locales
942
+ * @return {string[]}
943
+ */
944
+ static getLocaleNames() {
945
+ const keys = Array.from(locales.keys());
946
+ return keys.sort((a, b) => a.localeCompare(b));
947
+ }
948
+
949
+ /**
950
+ * @param {number} n
951
+ * @param {string} [locale] Optional locale name (i.e: `'he'`, `'fr'`). Defaults to active locale.
952
+ * @return {string}
953
+ */
954
+ static ordinal(n, locale) {
955
+ const locale1 = locale === null || locale === void 0 ? void 0 : locale.toLowerCase();
956
+ const locale0 = locale1 || activeName;
957
+ if (!locale0) {
958
+ return this.getEnOrdinal(n);
1005
959
  }
1006
- else if (month == ADAR_I$1 && day == 30 && isOrigLeap && !isLeapYear(hyear)) {
1007
- month = NISAN$3;
1008
- day = 1;
960
+ switch (locale0) {
961
+ case 'en':
962
+ case 's':
963
+ case 'a':
964
+ case 'ashkenazi':
965
+ case 'ashkenazi_litvish':
966
+ case 'ashkenazi_poylish':
967
+ case 'ashkenazi_standard':
968
+ return this.getEnOrdinal(n);
969
+ case 'es':
970
+ return n + 'º';
971
+ case 'h':
972
+ case 'he':
973
+ case 'he-x-nonikud':
974
+ return String(n);
975
+ default:
976
+ return n + '.';
1009
977
  }
1010
- return { yy: hyear, mm: month, dd: day };
978
+ }
979
+
980
+ /**
981
+ * @private
982
+ * @param {number} n
983
+ * @return {string}
984
+ */
985
+ static getEnOrdinal(n) {
986
+ const s = ['th', 'st', 'nd', 'rd'];
987
+ const v = n % 100;
988
+ return n + (s[(v - 20) % 10] || s[v] || s[0]);
989
+ }
990
+
991
+ /**
992
+ * Removes nekudot from Hebrew string
993
+ * @param {string} str
994
+ * @return {string}
995
+ */
996
+ static hebrewStripNikkud(str) {
997
+ return str.replace(/[\u0590-\u05bd]/g, '').replace(/[\u05bf-\u05c7]/g, '');
998
+ }
1011
999
  }
1000
+ Locale.addLocale('en', noopLocale);
1001
+ Locale.addLocale('s', noopLocale);
1002
+ Locale.addLocale('', noopLocale);
1003
+ Locale.useLocale('en');
1012
1004
 
1013
1005
  /**
1014
1006
  * @private
@@ -1837,7 +1829,7 @@ class Event {
1837
1829
  this.desc = desc;
1838
1830
  this.mask = +mask;
1839
1831
  if (typeof attrs === 'object' && attrs !== null) {
1840
- Object.keys(attrs).forEach(k => this[k] = attrs[k]);
1832
+ Object.assign(this, attrs);
1841
1833
  }
1842
1834
  }
1843
1835
  /**
@@ -3430,10 +3422,10 @@ class Location extends GeoLocation {
3430
3422
  return true;
3431
3423
  }
3432
3424
  }
3433
- classicCities0.forEach(city => {
3425
+ for (const city of classicCities0) {
3434
3426
  const location = new Location(city[2], city[3], city[1] == 'IL', city[4], city[0], city[1], undefined, city[5]);
3435
3427
  Location.addLocation(location.getName(), location);
3436
- });
3428
+ }
3437
3429
 
3438
3430
  const _formatters = new Map();
3439
3431
 
@@ -5612,40 +5604,7 @@ const MINOR_HOLIDAY$1 = flags.MINOR_HOLIDAY;
5612
5604
  const EREV$1 = flags.EREV;
5613
5605
  // const CHOL_HAMOED = flags.CHOL_HAMOED;
5614
5606
 
5615
- /**
5616
- * Avoid dependency on ES6 Map object
5617
- * @private
5618
- */
5619
- class SimpleMap {
5620
- /**
5621
- * @param {string} key
5622
- * @return {boolean}
5623
- */
5624
- has(key) {
5625
- return typeof this[key] !== 'undefined';
5626
- }
5627
- /**
5628
- * @param {string} key
5629
- * @return {any}
5630
- */
5631
- get(key) {
5632
- return this[key];
5633
- }
5634
- /**
5635
- * @param {string} key
5636
- * @param {any} val
5637
- */
5638
- set(key, val) {
5639
- this[key] = val;
5640
- }
5641
- /**
5642
- * @return {string[]}
5643
- */
5644
- keys() {
5645
- return Object.keys(this);
5646
- }
5647
- }
5648
- const sedraCache = new SimpleMap();
5607
+ const sedraCache = new Map();
5649
5608
 
5650
5609
  /**
5651
5610
  * @private
@@ -5689,13 +5648,12 @@ function getHolidaysForYear_(year) {
5689
5648
  }
5690
5649
  const RH = new HDate(1, TISHREI$1, year);
5691
5650
  const pesach = new HDate(15, NISAN$1, year);
5692
- const h = new SimpleMap();
5651
+ const map = new Map();
5693
5652
  // eslint-disable-next-line require-jsdoc
5694
5653
  function add(...events) {
5695
- // for (const ev of events) {
5696
- events.forEach(ev => {
5654
+ for (const ev of events) {
5697
5655
  const key = ev.date.toString();
5698
- const arr = h.get(key);
5656
+ const arr = map.get(key);
5699
5657
  if (typeof arr === 'object') {
5700
5658
  if (arr[0].getFlags() & flags.EREV) {
5701
5659
  arr.unshift(ev);
@@ -5703,17 +5661,17 @@ function getHolidaysForYear_(year) {
5703
5661
  arr.push(ev);
5704
5662
  }
5705
5663
  } else {
5706
- h.set(key, [ev]);
5664
+ map.set(key, [ev]);
5707
5665
  }
5708
- });
5666
+ }
5709
5667
  }
5710
- staticHolidays.forEach(h => {
5668
+ for (const h of staticHolidays) {
5711
5669
  const hd = new HDate(h.dd, h.mm, year);
5712
5670
  const ev = new HolidayEvent(hd, h.desc, h.flags);
5713
5671
  if (h.emoji) ev.emoji = h.emoji;
5714
5672
  if (h.chmDay) ev.cholHaMoedDay = h.chmDay;
5715
5673
  add(ev);
5716
- });
5674
+ }
5717
5675
 
5718
5676
  // standard holidays that don't shift based on year
5719
5677
  add(new RoshHashanaEvent(RH, year, CHAG | LIGHT_CANDLES_TZEIS$1));
@@ -5765,7 +5723,7 @@ function getHolidaysForYear_(year) {
5765
5723
  if (yomHaZikaronDt) {
5766
5724
  add(new HolidayEvent(yomHaZikaronDt, 'Yom HaZikaron', MODERN_HOLIDAY$1, emojiIsraelFlag), new HolidayEvent(yomHaZikaronDt.next(), 'Yom HaAtzma\'ut', MODERN_HOLIDAY$1, emojiIsraelFlag));
5767
5725
  }
5768
- staticModernHolidays.forEach(h => {
5726
+ for (const h of staticModernHolidays) {
5769
5727
  if (year >= h.firstYear) {
5770
5728
  let hd = new HDate(h.dd, h.mm, year);
5771
5729
  const dow = hd.getDay();
@@ -5783,7 +5741,7 @@ function getHolidaysForYear_(year) {
5783
5741
  }
5784
5742
  add(ev);
5785
5743
  }
5786
- });
5744
+ }
5787
5745
  let tamuz17 = new HDate(17, TAMUZ, year);
5788
5746
  let tamuz17attrs;
5789
5747
  if (tamuz17.getDay() == SAT$1) {
@@ -5854,8 +5812,8 @@ function getHolidaysForYear_(year) {
5854
5812
  emoji: '☀️'
5855
5813
  }));
5856
5814
  }
5857
- yearCache.set(year, h);
5858
- return h;
5815
+ yearCache.set(year, map);
5816
+ return map;
5859
5817
  }
5860
5818
 
5861
5819
  /**
@@ -5922,7 +5880,7 @@ class DailyLearning {
5922
5880
  }
5923
5881
  }
5924
5882
 
5925
- const version="5.0.0";
5883
+ const version="5.0.2";
5926
5884
 
5927
5885
  const headers$1={"plural-forms":"nplurals=2; plural=(n > 1);"};const contexts$1={"":{Shabbat:["Shabbos"],"Achrei Mot":["Achrei Mos"],Bechukotai:["Bechukosai"],"Beha'alotcha":["Beha'aloscha"],Bereshit:["Bereshis"],Chukat:["Chukas"],"Erev Shavuot":["Erev Shavuos"],"Erev Sukkot":["Erev Sukkos"],"Ki Tavo":["Ki Savo"],"Ki Teitzei":["Ki Seitzei"],"Ki Tisa":["Ki Sisa"],Matot:["Matos"],"Purim Katan":["Purim Koton"],"Shabbat Chazon":["Shabbos Chazon"],"Shabbat HaChodesh":["Shabbos HaChodesh"],"Shabbat HaGadol":["Shabbos HaGadol"],"Shabbat Nachamu":["Shabbos Nachamu"],"Shabbat Parah":["Shabbos Parah"],"Shabbat Shekalim":["Shabbos Shekalim"],"Shabbat Shuva":["Shabbos Shuvah"],"Shabbat Zachor":["Shabbos Zachor"],Shavuot:["Shavuos"],"Shavuot I":["Shavuos I"],"Shavuot II":["Shavuos II"],Shemot:["Shemos"],"Shmini Atzeret":["Shmini Atzeres"],"Simchat Torah":["Simchas Torah"],Sukkot:["Sukkos"],"Sukkot I":["Sukkos I"],"Sukkot II":["Sukkos II"],"Sukkot II (CH''M)":["Sukkos II (CH''M)"],"Sukkot III (CH''M)":["Sukkos III (CH''M)"],"Sukkot IV (CH''M)":["Sukkos IV (CH''M)"],"Sukkot V (CH''M)":["Sukkos V (CH''M)"],"Sukkot VI (CH''M)":["Sukkos VI (CH''M)"],"Sukkot VII (Hoshana Raba)":["Sukkos VII (Hoshana Raba)"],"Ta'anit Bechorot":["Ta'anis Bechoros"],"Ta'anit Esther":["Ta'anis Esther"],Toldot:["Toldos"],Vaetchanan:["Vaeschanan"],Yitro:["Yisro"],"Vezot Haberakhah":["Vezos Haberakhah"],Parashat:["Parshas"],"Leil Selichot":["Leil Selichos"],"Shabbat Mevarchim Chodesh":["Shabbos Mevorchim Chodesh"],"Shabbat Shirah":["Shabbos Shirah"],Tevet:["Teves"],"Asara B'Tevet":["Asara B'Teves"],"Alot HaShachar":["Alos HaShachar"],"Kriat Shema, sof zeman":["Krias Shema, sof zman"],"Tefilah, sof zeman":["Tefilah, sof zman"],"Kriat Shema, sof zeman (MGA)":["Krias Shema, sof zman (MGA)"],"Tefilah, sof zeman (MGA)":["Tefilah, sof zman (MGA)"],"Chatzot HaLailah":["Chatzos HaLailah"],"Chatzot hayom":["Chatzos"],"Tzeit HaKochavim":["Tzeis HaKochavim"],"Birkat Hachamah":["Birkas Hachamah"],"Shushan Purim Katan":["Shushan Purim Koton"]}};var poAshkenazi = {headers:headers$1,contexts:contexts$1};
5928
5886
 
@@ -5935,9 +5893,9 @@ Locale.addLocale('he', poHe);
5935
5893
  Locale.addLocale('h', poHe);
5936
5894
  const heStrs = poHe.contexts[''];
5937
5895
  const heNoNikud = {};
5938
- Object.keys(heStrs).forEach(key => {
5939
- heNoNikud[key] = [Locale.hebrewStripNikkud(heStrs[key][0])];
5940
- });
5896
+ for (const [key, val] of Object.entries(heStrs)) {
5897
+ heNoNikud[key] = [Locale.hebrewStripNikkud(val[0])];
5898
+ }
5941
5899
  const poHeNoNikud = {
5942
5900
  headers: poHe.headers,
5943
5901
  contexts: {
@@ -6218,24 +6176,12 @@ const RECOGNIZED_OPTIONS = {
6218
6176
  * @param {CalOptions} options
6219
6177
  */
6220
6178
  function warnUnrecognizedOptions(options) {
6221
- Object.keys(options).forEach(k => {
6179
+ for (const k of Object.keys(options)) {
6222
6180
  if (typeof RECOGNIZED_OPTIONS[k] === 'undefined' && !unrecognizedAlreadyWarned.has(k)) {
6223
6181
  console.warn(`Ignoring unrecognized HebrewCalendar option: ${k}`);
6224
6182
  unrecognizedAlreadyWarned.add(k);
6225
6183
  }
6226
- });
6227
- }
6228
-
6229
- /**
6230
- * A bit like Object.assign(), but just a shallow copy
6231
- * @private
6232
- * @param {any} target
6233
- * @param {any} source
6234
- * @return {any}
6235
- */
6236
- function shallowCopy(target, source) {
6237
- Object.keys(source).forEach(k => target[k] = source[k]);
6238
- return target;
6184
+ }
6239
6185
  }
6240
6186
  const israelCityOffset = {
6241
6187
  'Jerusalem': 40,
@@ -6596,6 +6542,7 @@ function observedInDiaspora(ev) {
6596
6542
  return ev.observedInDiaspora();
6597
6543
  }
6598
6544
  const yearArrayCache = new Map();
6545
+ const holidaysOnDate = new Map();
6599
6546
 
6600
6547
  /**
6601
6548
  * HebrewCalendar is the main interface to the `@hebcal/core` library.
@@ -6713,7 +6660,7 @@ class HebrewCalendar {
6713
6660
  * @return {Event[]}
6714
6661
  */
6715
6662
  static calendar(options = {}) {
6716
- options = shallowCopy({}, options); // so we can modify freely
6663
+ options = Object.assign({}, options); // so we can modify freely
6717
6664
  checkCandleOptions(options);
6718
6665
  const location = options.location = options.location || defaultLocation;
6719
6666
  const il = options.il = options.il || location.il || false;
@@ -6762,9 +6709,9 @@ class HebrewCalendar {
6762
6709
  const dow = hd.getDay();
6763
6710
  let candlesEv;
6764
6711
  const ev = holidaysYear.get(hd.toString()) || [];
6765
- ev.forEach(e => {
6712
+ for (const e of ev) {
6766
6713
  candlesEv = appendHolidayAndRelated(evts, e, options, candlesEv, dow);
6767
- });
6714
+ }
6768
6715
  if (options.sedrot && dow === SAT) {
6769
6716
  const parsha0 = sedra.lookup(abs);
6770
6717
  if (!parsha0.chag) {
@@ -6773,8 +6720,7 @@ class HebrewCalendar {
6773
6720
  }
6774
6721
  const dailyLearning = options.dailyLearning;
6775
6722
  if (typeof dailyLearning === 'object') {
6776
- Object.keys(dailyLearning).forEach(key => {
6777
- const val = dailyLearning[key];
6723
+ for (const [key, val] of Object.entries(dailyLearning)) {
6778
6724
  if (val) {
6779
6725
  const name = key === 'yerushalmi' ? val === 2 ? 'yerushalmi-schottenstein' : 'yerushalmi-vilna' : key;
6780
6726
  const learningEv = DailyLearning.lookup(name, hd);
@@ -6782,7 +6728,7 @@ class HebrewCalendar {
6782
6728
  evts.push(learningEv);
6783
6729
  }
6784
6730
  }
6785
- });
6731
+ }
6786
6732
  }
6787
6733
  if (options.omer && abs >= beginOmer && abs <= endOmer) {
6788
6734
  const omer = abs - beginOmer + 1;
@@ -6944,20 +6890,29 @@ class HebrewCalendar {
6944
6890
  }
6945
6891
 
6946
6892
  /**
6947
- * Returns an array of Events on this date (or undefined if no events)
6893
+ * Returns an array of Events on this date (or `undefined` if no events)
6948
6894
  * @param {HDate|Date|number} date Hebrew Date, Gregorian date, or absolute R.D. day number
6949
6895
  * @param {boolean} [il] use the Israeli schedule for holidays
6950
6896
  * @return {Event[]}
6951
6897
  */
6952
6898
  static getHolidaysOnDate(date, il) {
6953
6899
  const hd = HDate.isHDate(date) ? date : new HDate(date);
6900
+ const hdStr = hd.toString();
6901
+ const cacheKey = hdStr + '/' + (typeof il === 'undefined' ? 2 : il ? 1 : 0);
6902
+ if (holidaysOnDate.has(cacheKey)) {
6903
+ return holidaysOnDate.get(cacheKey);
6904
+ }
6954
6905
  const yearMap = getHolidaysForYear_(hd.getFullYear());
6955
- const events = yearMap.get(hd.toString());
6906
+ const events = yearMap.get(hdStr);
6907
+ // if il isn't a boolean return both diaspora + IL for day
6956
6908
  if (typeof il === 'undefined' || typeof events === 'undefined') {
6909
+ holidaysOnDate.set(cacheKey, events);
6957
6910
  return events;
6958
6911
  }
6959
6912
  const myFilter = il ? observedInIsrael : observedInDiaspora;
6960
- return events.filter(myFilter);
6913
+ const filtered = events.filter(myFilter);
6914
+ holidaysOnDate.set(cacheKey, filtered);
6915
+ return filtered;
6961
6916
  }
6962
6917
 
6963
6918
  /**