@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
JavaScript
/*! @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