| | import { |
| | HEBREW_EPOCH, |
| | type HebrewMonthCode, |
| | MONTH_SEQUENCE_COMMON, |
| | MONTH_SEQUENCE_LEAP, |
| | type YearType, |
| | } from "./constants.js"; |
| |
|
| | const roshHashanahCache = new Map<number, number>(); |
| | const yearLengthCache = new Map<number, number>(); |
| |
|
| | |
| | |
| | |
| | |
| | export function mod(value: number, divisor: number): number { |
| | return ((value % divisor) + divisor) % divisor; |
| | } |
| |
|
| | |
| | export function isHebrewLeapYear(year: number): boolean { |
| | return mod(7 * year + 1, 19) < 7; |
| | } |
| |
|
| | |
| | function monthsElapsed(year: number): number { |
| | return Math.floor((235 * year - 234) / 19); |
| | } |
| |
|
| | |
| | function hebrewCalendarElapsedDays(year: number): number { |
| | const months = monthsElapsed(year); |
| | const parts = 204 + 793 * mod(months, 1080); |
| | const hours = 5 + 12 * months + 793 * Math.floor(months / 1080); |
| | const day = 1 + 29 * months + Math.floor(hours / 24); |
| | const partsRemain = mod(hours, 24) * 1080 + parts; |
| |
|
| | let roshHashanah = day; |
| | const leapYear = isHebrewLeapYear(year); |
| | const lastYearLeap = isHebrewLeapYear(year - 1); |
| |
|
| | if ( |
| | partsRemain >= 19440 || |
| | (mod(roshHashanah, 7) === 2 && partsRemain >= 9924 && !leapYear) || |
| | (mod(roshHashanah, 7) === 1 && partsRemain >= 16789 && lastYearLeap) |
| | ) { |
| | roshHashanah += 1; |
| | } |
| |
|
| | const weekday = mod(roshHashanah, 7); |
| | if (weekday === 0 || weekday === 3 || weekday === 5) { |
| | roshHashanah += 1; |
| | } |
| |
|
| | return roshHashanah; |
| | } |
| |
|
| | |
| | export function roshHashanah(year: number): number { |
| | const cached = roshHashanahCache.get(year); |
| | if (cached !== undefined) { |
| | return cached; |
| | } |
| | const value = HEBREW_EPOCH + hebrewCalendarElapsedDays(year); |
| | roshHashanahCache.set(year, value); |
| | return value; |
| | } |
| |
|
| | |
| | export function daysInHebrewYear(year: number): number { |
| | const cached = yearLengthCache.get(year); |
| | if (cached !== undefined) { |
| | return cached; |
| | } |
| | const days = roshHashanah(year + 1) - roshHashanah(year); |
| | yearLengthCache.set(year, days); |
| | return days; |
| | } |
| |
|
| | |
| | function yearType(year: number): YearType { |
| | const days = daysInHebrewYear(year); |
| | const leap = isHebrewLeapYear(year); |
| | if (leap) { |
| | if (days === 383) return "deficient"; |
| | if (days === 384) return "regular"; |
| | return "complete"; |
| | } |
| | if (days === 353) return "deficient"; |
| | if (days === 354) return "regular"; |
| | return "complete"; |
| | } |
| |
|
| | |
| | function monthSequence(year: number) { |
| | return isHebrewLeapYear(year) ? MONTH_SEQUENCE_LEAP : MONTH_SEQUENCE_COMMON; |
| | } |
| |
|
| | |
| | function monthCode(year: number, monthIndex: number): HebrewMonthCode { |
| | const sequence = monthSequence(year); |
| | if (monthIndex < 0 || monthIndex >= sequence.length) { |
| | throw new RangeError(`Invalid month index ${monthIndex} for year ${year}`); |
| | } |
| | return sequence[monthIndex]; |
| | } |
| |
|
| | |
| | export function monthsInHebrewYear(year: number): number { |
| | return monthSequence(year).length; |
| | } |
| |
|
| | |
| | export function daysInHebrewMonth(year: number, monthIndex: number): number { |
| | const code = monthCode(year, monthIndex); |
| | const type = yearType(year); |
| | switch (code) { |
| | case "tishrei": |
| | return 30; |
| | case "cheshvan": |
| | return type === "complete" ? 30 : 29; |
| | case "kislev": |
| | return type === "deficient" ? 29 : 30; |
| | case "tevet": |
| | return 29; |
| | case "shevat": |
| | return 30; |
| | case "adarI": |
| | return 30; |
| | case "adar": |
| | return 29; |
| | case "nisan": |
| | return 30; |
| | case "iyar": |
| | return 29; |
| | case "sivan": |
| | return 30; |
| | case "tamuz": |
| | return 29; |
| | case "av": |
| | return 30; |
| | case "elul": |
| | return 29; |
| | default: |
| | return 0; |
| | } |
| | } |
| |
|
| | export function getMonthCode( |
| | year: number, |
| | monthIndex: number, |
| | ): HebrewMonthCode { |
| | return monthCode(year, monthIndex); |
| | } |
| |
|