UNPKG

@hebcal/hdate

Version:

converts between Hebrew and Gregorian dates using Rata Die (R.D.) algorithm by Dershowitz and Reingold

446 lines (442 loc) 12.9 kB
/*! @hebcal/hdate v0.22.5, distributed under GPLv2 https://www.gnu.org/licenses/gpl-2.0.txt */ import { hebrewStripNikkud } from './hebrewStripNikkud.js'; /* * More minimal HDate */ const NISAN = 1; const IYYAR = 2; const SIVAN = 3; const TAMUZ = 4; const AV = 5; const ELUL = 6; const TISHREI = 7; const CHESHVAN = 8; const KISLEV = 9; const TEVET = 10; const SHVAT = 11; const ADAR_I = 12; const ADAR_II = 13; /** * Hebrew months of the year (NISAN=1, TISHREI=7) * @readonly * @enum {number} */ const months = { /** Nissan / ניסן */ NISAN, /** Iyyar / אייר */ IYYAR, /** Sivan / סיון */ SIVAN, /** Tamuz (sometimes Tammuz) / תמוז */ TAMUZ, /** Av / אב */ AV, /** Elul / אלול */ ELUL, /** Tishrei / תִּשְׁרֵי */ TISHREI, /** Cheshvan / חשון */ CHESHVAN, /** Kislev / כסלו */ KISLEV, /** Tevet / טבת */ TEVET, /** Sh'vat / שבט */ SHVAT, /** Adar or Adar Rishon / אדר */ ADAR_I, /** Adar Sheini (only on leap years) / אדר ב׳ */ ADAR_II, }; const NISAN_STR = 'Nisan'; const monthNames0 = [ '', NISAN_STR, 'Iyyar', 'Sivan', 'Tamuz', 'Av', 'Elul', 'Tishrei', 'Cheshvan', 'Kislev', 'Tevet', "Sh'vat", ]; /* * Transliterations of Hebrew month names. * Regular years are index 0 and leap years are index 1. * @private */ const monthNames = [ [...monthNames0, 'Adar', NISAN_STR], [...monthNames0, 'Adar I', 'Adar II', NISAN_STR], ]; // Typed-array cache for elapsedDays, indexed by `year - ED_CACHE_MIN`. // The range covers Hebrew years ~AD 1240 through ~AD 3240, which spans // every realistic modern use. Years outside the range fall through // uncached. 0 is the "not computed" sentinel; every valid input // (year >= 1) produces a result >= 1, so it can't collide. // elapsedDays(6999) is ~2.56M, well within Int32 range. const ED_CACHE_MIN = 5000; const ED_CACHE_MAX = 6999; const edCache = new Int32Array(ED_CACHE_MAX - ED_CACHE_MIN + 1); const EPOCH = -1373428; // Avg year length in the cycle (19 solar years with 235 lunar months) const AVG_HEBYEAR_DAYS = 365.24682220597794; /** * @private */ function assertNumber(n, name) { if (typeof n !== 'number' || isNaN(n)) { throw new TypeError(`param '${name}' not a number: ${n}`); } } /** * Converts Hebrew date to R.D. (Rata Die) fixed days. * R.D. 1 is the imaginary date Monday, January 1, 1 on the Gregorian * Calendar. * @param year Hebrew year * @param month Hebrew month * @param day Hebrew date (1-30) * @example * import {hebrew2abs, months} from '@hebcal/hdate'; * hebrew2abs(5769, months.CHESHVAN, 15); // 733359 */ function hebrew2abs(year, month, day) { assertNumber(year, 'year'); assertNumber(month, 'month'); assertNumber(day, 'day'); if (year < 1) { throw new RangeError(`hebrew2abs: invalid year ${year}`); } let tempabs = day; if (month < TISHREI) { const endMonth = monthsInYear(year); for (let m = TISHREI; m <= endMonth; m++) { tempabs += daysInMonth(m, year); } for (let m = NISAN; m < month; m++) { tempabs += daysInMonth(m, year); } } else { for (let m = TISHREI; m < month; m++) { tempabs += daysInMonth(m, year); } } return EPOCH + elapsedDays(year) + tempabs - 1; } /** * Convenience wrapper for `hebrew2abs` that accepts a * `SimpleHebrewDate` (`{yy, mm, dd}`) rather than three separate * arguments. Returns the same R.D. (Rata Die) day number. * @example * import {hd2abs, months} from '@hebcal/hdate'; * hd2abs({yy: 5769, mm: months.CHESHVAN, dd: 15}); // 733359 */ function hd2abs(hdate) { return hebrew2abs(hdate.yy, hdate.mm, hdate.dd); } /** * @private */ function newYear(year) { return EPOCH + elapsedDays(year); } /** * Converts absolute R.D. days to Hebrew date * @param abs absolute R.D. days * @example * abs2hebrew(733359); // {yy: 5769, mm: 8, dd: 15} (15 Cheshvan 5769) */ function abs2hebrew(abs) { assertNumber(abs, 'abs'); abs = Math.trunc(abs); if (abs <= EPOCH) { throw new RangeError(`abs2hebrew: ${abs} is before epoch`); } // first, quickly approximate year let year = Math.floor((abs - EPOCH) / AVG_HEBYEAR_DAYS); while (newYear(year) <= abs) { ++year; } --year; let month = abs < hebrew2abs(year, 1, 1) ? 7 : 1; while (abs > hebrew2abs(year, month, daysInMonth(month, year))) { ++month; } const day = 1 + abs - hebrew2abs(year, month, 1); return { yy: year, mm: month, dd: day }; } /** * Returns true if Hebrew year is a leap year * @param year Hebrew year * @example * isLeapYear(5783); // false * isLeapYear(5784); // true */ function isLeapYear(year) { return (1 + year * 7) % 19 < 7; } /** * Number of months in this Hebrew year (either 12 or 13 depending on leap year) * @param year Hebrew year * @example * monthsInYear(5783); // 12 * monthsInYear(5784); // 13 */ function monthsInYear(year) { return 12 + +isLeapYear(year); // boolean is cast to 1 or 0 } // Static day counts indexed by month number. 0 marks months whose length // depends on the year (CHESHVAN, KISLEV, ADAR_I). const STATIC_DAYS_IN_MONTH = [ 0, 30, 29, 30, 29, 30, 29, 30, 0, 0, 29, 30, 0, 29, ]; /** * Number of days in Hebrew month in a given year (29 or 30) * @param month Hebrew month (e.g. months.TISHREI) * @param year Hebrew year * @example * import {daysInMonth, months} from '@hebcal/hdate'; * daysInMonth(months.CHESHVAN, 5769); // 29 * daysInMonth(months.KISLEV, 5769); // 30 */ function daysInMonth(month, year) { const d = STATIC_DAYS_IN_MONTH[month]; if (d !== 0) return d; if (month === ADAR_I) return isLeapYear(year) ? 30 : 29; if (month === CHESHVAN) return longCheshvan(year) ? 30 : 29; return shortKislev(year) ? 29 : 30; // KISLEV } /** * Returns a transliterated string name of Hebrew month in year, * for example 'Elul' or 'Cheshvan'. * @param month Hebrew month (e.g. months.TISHREI) * @param year Hebrew year * @example * import {getMonthName, months} from '@hebcal/hdate'; * getMonthName(months.CHESHVAN, 5769); // 'Cheshvan' * getMonthName(months.ADAR_I, 5784); // 'Adar I' (leap year) * getMonthName(months.ADAR_I, 5783); // 'Adar' (common year) */ function getMonthName(month, year) { assertNumber(month, 'month'); assertNumber(year, 'year'); if (month < 1 || month > 14) { throw new TypeError(`bad monthNum: ${month}`); } return monthNames[+isLeapYear(year)][month]; } /** * Days from sunday prior to start of Hebrew calendar to mean * conjunction of Tishrei in Hebrew YEAR * @param year Hebrew year */ function elapsedDays(year) { if (year >= ED_CACHE_MIN && year <= ED_CACHE_MAX) { const idx = year - ED_CACHE_MIN; const n = edCache[idx]; if (n !== 0) return n; const elapsed = elapsedDays0(year); edCache[idx] = elapsed; return elapsed; } return elapsedDays0(year); } /** * Days from sunday prior to start of Hebrew calendar to mean * conjunction of Tishrei in Hebrew YEAR * @private * @param year Hebrew year */ function elapsedDays0(year) { const prevYear = year - 1; const mElapsed = 235 * Math.floor(prevYear / 19) + // Months in complete 19 year lunar (Metonic) cycles so far 12 * (prevYear % 19) + // Regular months in this cycle Math.floor(((prevYear % 19) * 7 + 1) / 19); // Leap months this cycle const pElapsed = 204 + 793 * (mElapsed % 1080); const hElapsed = 5 + 12 * mElapsed + 793 * Math.floor(mElapsed / 1080) + Math.floor(pElapsed / 1080); const parts = (pElapsed % 1080) + 1080 * (hElapsed % 24); const day = 1 + 29 * mElapsed + Math.floor(hElapsed / 24); let altDay = day; if (parts >= 19440 || (2 === day % 7 && parts >= 9924 && !isLeapYear(year)) || (1 === day % 7 && parts >= 16789 && isLeapYear(prevYear))) { altDay++; } if (altDay % 7 === 0 || altDay % 7 === 3 || altDay % 7 === 5) { return altDay + 1; } else { return altDay; } } /** * Number of days in the hebrew YEAR. * A common Hebrew calendar year can have a length of 353, 354 or 355 days * A leap Hebrew calendar year can have a length of 383, 384 or 385 days * @param year Hebrew year * @example * daysInYear(5783); // 355 * daysInYear(5784); // 383 (leap year) */ function daysInYear(year) { return elapsedDays(year + 1) - elapsedDays(year); } /** * true if Cheshvan is long in Hebrew year * @param year Hebrew year * @example * longCheshvan(5783); // true * longCheshvan(5784); // false */ function longCheshvan(year) { return daysInYear(year) % 10 === 5; } /** * true if Kislev is short in Hebrew year * @param year Hebrew year * @example * shortKislev(5783); // false * shortKislev(5784); // true */ function shortKislev(year) { return daysInYear(year) % 10 === 3; } /** * Converts Hebrew month string name to numeric * @param monthName monthName * @example * monthFromName('Cheshvan'); // 8 * monthFromName('חשון'); // 8 * monthFromName('Adar II'); // 13 * monthFromName(7); // 7 (passthrough) */ function monthFromName(monthName) { if (typeof monthName === 'number') { if (isNaN(monthName) || monthName < 1 || monthName > 14) { throw new RangeError(`bad monthName: ${monthName}`); } return monthName; } if (typeof monthName !== 'string') { throw new TypeError(`bad monthName: ${monthName}`); } let c = monthName.trim().toLowerCase(); // remove all niqud and trailing gershayim (for Adar Alef/Bet) c = hebrewStripNikkud(c).replace(/׳$/, ''); // If Hebrew month starts with a bet (for example `בתמוז`) then ignore it if (c.startsWith('ב')) { c = c.substring(1); } /* the Hebrew months are unique to their second letter N Nisan (November?) I Iyyar E Elul C Cheshvan K Kislev 1 1Adar 2 2Adar Si Sh Sivan, Shvat Ta Ti Te Tamuz, Tishrei, Tevet Av Ad Av, Adar אב אד אי אל אב אדר אייר אלול ח חשון ט טבת כ כסלו נ ניסן ס סיון ש שבט תמ תש תמוז תשרי */ switch (c[0]) { case 'n': case 'נ': if (c[1] === 'o') { break; /* this catches "november" */ } return NISAN; case 'i': return IYYAR; case 'e': return ELUL; case 'c': case 'ח': return CHESHVAN; case 'k': case 'כ': return KISLEV; case 's': switch (c[1]) { case 'i': return SIVAN; case 'h': return SHVAT; } break; case 't': switch (c[1]) { case 'a': return TAMUZ; case 'i': return TISHREI; case 'e': return TEVET; } break; case 'a': switch (c[1]) { case 'v': return AV; case 'd': if (/(1|[^i]i|a|א)$/i.test(c)) { return ADAR_I; } return ADAR_II; // else assume sheini } break; case 'ס': return SIVAN; case 'ט': return TEVET; case 'ש': return SHVAT; case 'א': switch (c[1]) { case 'ב': return AV; case 'ד': if (/(1|[^i]i|a|א)$/i.test(c)) { return ADAR_I; } return ADAR_II; // else assume sheini case 'י': return IYYAR; case 'ל': return ELUL; } break; case 'ת': switch (c[1]) { case 'מ': return TAMUZ; case 'ש': return TISHREI; } break; } throw new RangeError(`bad monthName: ${monthName}`); } export { abs2hebrew, daysInMonth, daysInYear, elapsedDays, getMonthName, hd2abs, hebrew2abs, isLeapYear, longCheshvan, monthFromName, months, monthsInYear, shortKislev }; //# sourceMappingURL=hdateBase.js.map