UNPKG

@hebcal/hdate

Version:

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

159 lines (157 loc) 4.84 kB
/*! @hebcal/hdate v0.22.5, distributed under GPLv2 https://www.gnu.org/licenses/gpl-2.0.txt */ /** @private */ const lengths = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; /** @private */ const monthLengths = [lengths, lengths.slice()]; monthLengths[1][2] = 29; /** * @private */ function mod(x, y) { return x - y * Math.floor(x / y); } /** * @private */ function quotient(x, y) { return Math.floor(x / y); } /** * @private * @param abs - R.D. number of days */ function yearFromFixed(abs) { const l0 = abs - 1; const n400 = quotient(l0, 146097); const d1 = mod(l0, 146097); const n100 = quotient(d1, 36524); const d2 = mod(d1, 36524); const n4 = quotient(d2, 1461); const d3 = mod(d2, 1461); const n1 = quotient(d3, 365); const year = 400 * n400 + 100 * n100 + 4 * n4 + n1; return n100 !== 4 && n1 !== 4 ? year + 1 : year; } /* const ABS_14SEP1752 = 639797; const ABS_2SEP1752 = 639785; */ /* * Formerly in namespace, now top-level */ /** * Returns true if the Gregorian year is a leap year * @param year Gregorian year * @example * isGregLeapYear(2000); // true * isGregLeapYear(2020); // true * isGregLeapYear(2023); // false * isGregLeapYear(2100); // false (divisible by 100 but not 400) */ function isGregLeapYear(year) { return !(year % 4) && (!!(year % 100) || !(year % 400)); } /** * Number of days in the Gregorian month for given year * @param month Gregorian month (1=January, 12=December) * @param year Gregorian year * @example * daysInGregMonth(2, 2024); // 29 (February in a leap year) * daysInGregMonth(2, 2023); // 28 * daysInGregMonth(7, 2024); // 31 (July) */ function daysInGregMonth(month, year) { // 1 based months return monthLengths[+isGregLeapYear(year)][month]; } /** * Returns true if the object is a Javascript Date * @example * isDate(new Date()); // true * isDate('2024-01-01'); // false * isDate(1700000000000); // false */ function isDate(obj) { // eslint-disable-next-line no-prototype-builtins return typeof obj === 'object' && Date.prototype.isPrototypeOf(obj); } /** * @private * @param year * @param month (1-12) * @param day (1-31) */ function toFixed(year, month, day) { const py = year - 1; return (365 * py + quotient(py, 4) - quotient(py, 100) + quotient(py, 400) + quotient(367 * month - 362, 12) + (month <= 2 ? 0 : isGregLeapYear(year) ? -1 : -2) + day); } /** * Converts Gregorian date to absolute R.D. (Rata Die) days * @param date Gregorian date * @example * greg2abs(new Date(2008, 10, 13)); // 733359 (13 November 2008) * greg2abs(new Date(2005, 3, 2)); // 732038 (2 April 2005) */ function greg2abs(date) { if (!isDate(date)) { throw new TypeError(`not a Date: ${date}`); } else if (isNaN(date.getTime())) { throw new RangeError('Invalid Date'); } const abs = toFixed(date.getFullYear(), date.getMonth() + 1, date.getDate()); /* if (abs < ABS_14SEP1752 && abs > ABS_2SEP1752) { throw new RangeError(`Invalid Date: ${date}`); } */ return abs; } /** * Converts from Rata Die (R.D. number) to Gregorian date. * See the footnote on page 384 of ``Calendrical Calculations, Part II: * Three Historical Calendars'' by E. M. Reingold, N. Dershowitz, and S. M. * Clamen, Software--Practice and Experience, Volume 23, Number 4 * (April, 1993), pages 383-404 for an explanation. * * Note that this function returns the daytime portion of the date. * For example, the 15th of Cheshvan 5769 began at sundown on * 12 November 2008 and continues through 13 November 2008. This * function would return only the date 13 November 2008. * @param abs - R.D. number of days * @example * const abs = hebrew2abs(5769, months.CHESHVAN, 15); * const date = abs2greg(abs); // 13 November 2008 * const year = date.getFullYear(); // 2008 * const monthNum = date.getMonth() + 1; // 11 * const day = date.getDate(); // 13 */ function abs2greg(abs) { if (typeof abs !== 'number' || isNaN(abs)) { throw new TypeError(`not a Number: ${abs}`); } abs = Math.trunc(abs); /* if (abs < ABS_14SEP1752 && abs > ABS_2SEP1752) { throw new RangeError(`Invalid Date: ${abs}`); } */ const year = yearFromFixed(abs); const priorDays = abs - toFixed(year, 1, 1); const correction = abs < toFixed(year, 3, 1) ? 0 : isGregLeapYear(year) ? 1 : 2; const month = quotient(12 * (priorDays + correction) + 373, 367); const day = abs - toFixed(year, month, 1) + 1; const dt = new Date(year, month - 1, day); if (year < 100 && year >= 0) { dt.setFullYear(year); } return dt; } export { abs2greg, daysInGregMonth, greg2abs, isDate, isGregLeapYear }; //# sourceMappingURL=greg.js.map