@hebcal/hdate
Version:
converts between Hebrew and Gregorian dates using Rata Die (R.D.) algorithm by Dershowitz and Reingold
399 lines (395 loc) • 10.8 kB
JavaScript
/*! @hebcal/hdate v0.14.4, 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],
];
const edCache = new Map();
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
* const abs = hebrew2abs(5769, months.CHESHVAN, 15);
*/
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) {
for (let m = TISHREI; m <= monthsInYear(year); 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;
}
/**
* 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.
*/
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
*/
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
*/
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
*/
function monthsInYear(year) {
return 12 + +isLeapYear(year); // boolean is cast to 1 or 0
}
/**
* 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
*/
function daysInMonth(month, year) {
switch (month) {
case IYYAR:
case TAMUZ:
case ELUL:
case TEVET:
case ADAR_II:
return 29;
}
if ((month === ADAR_I && !isLeapYear(year)) ||
(month === CHESHVAN && !longCheshvan(year)) ||
(month === KISLEV && shortKislev(year))) {
return 29;
}
else {
return 30;
}
}
/**
* 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
*/
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) {
const n = edCache.get(year);
if (typeof n === 'number') {
return n;
}
const elapsed = elapsedDays0(year);
edCache.set(year, elapsed);
return elapsed;
}
/**
* 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
*/
function daysInYear(year) {
return elapsedDays(year + 1) - elapsedDays(year);
}
/**
* true if Cheshvan is long in Hebrew year
* @param year Hebrew year
*/
function longCheshvan(year) {
return daysInYear(year) % 10 === 5;
}
/**
* true if Kislev is short in Hebrew year
* @param year Hebrew year
*/
function shortKislev(year) {
return daysInYear(year) % 10 === 3;
}
/**
* Converts Hebrew month string name to numeric
* @param monthName monthName
*/
function monthFromName(monthName) {
if (typeof monthName === 'number') {
if (isNaN(monthName) || monthName < 1 || monthName > 14) {
throw new RangeError(`bad monthName: ${monthName}`);
}
return 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[0] === 'ב') {
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