kenat
Version:
A JavaScript library for the Ethiopian calendar with date and time support.
191 lines (157 loc) • 8.47 kB
JavaScript
import { toEC, toGC, hijriToGregorian, getHijriYear } from "./conversions.js";
import { holidayInfo, HolidayTags, keyToTewsakMap,movableHolidays } from "./constants.js";
import { validateNumericInputs } from "./utils.js";
import { InvalidInputTypeError, UnknownHolidayError } from "./errors/errorHandler.js";
import { getMovableHoliday } from "./bahireHasab.js";
const fixedHolidays = {
enkutatash: { month: 1, day: 1, tags: [HolidayTags.PUBLIC, HolidayTags.CULTURAL] },
meskel: { month: 1, day: 17, tags: [HolidayTags.PUBLIC, HolidayTags.RELIGIOUS, HolidayTags.CHRISTIAN] },
beherbehereseb: { month: 3, day: 20, tags: [HolidayTags.PUBLIC, HolidayTags.STATE] },
gena: { month: 4, day: 29, tags: [HolidayTags.PUBLIC, HolidayTags.RELIGIOUS, HolidayTags.CHRISTIAN] },
timket: { month: 5, day: 11, tags: [HolidayTags.PUBLIC, HolidayTags.RELIGIOUS, HolidayTags.CHRISTIAN] },
martyrsDay: { month: 6, day: 12, tags: [HolidayTags.PUBLIC, HolidayTags.STATE] },
adwa: { month: 6, day: 23, tags: [HolidayTags.PUBLIC, HolidayTags.STATE] },
labour: { month: 8, day: 23, tags: [HolidayTags.PUBLIC, HolidayTags.STATE] },
patriots: { month: 8, day: 27, tags: [HolidayTags.PUBLIC, HolidayTags.STATE] },
};
function findAllIslamicOccurrences(ethiopianYear, hijriMonth, hijriDay) {
const startGregorianYear = toGC(ethiopianYear, 1, 1).year;
const endGregorianYear = toGC(ethiopianYear, 13, 5).year;
const occurrences = [];
const checkGregorianYear = (gYear) => {
const hijriYearAtStart = getHijriYear(new Date(gYear, 0, 1));
const hijriYearsToCheck = [hijriYearAtStart, hijriYearAtStart + 1];
hijriYearsToCheck.forEach(hYear => {
const gregorianDate = hijriToGregorian(hYear, hijriMonth, hijriDay, gYear);
if (gregorianDate && gregorianDate.getFullYear() === gYear) {
const ecDate = toEC(gregorianDate.getFullYear(), gregorianDate.getMonth() + 1, gregorianDate.getDate());
if (ecDate.year === ethiopianYear) {
occurrences.push({
gregorian: { year: gregorianDate.getFullYear(), month: gregorianDate.getMonth() + 1, day: gregorianDate.getDate() },
ethiopian: ecDate
});
}
}
});
};
checkGregorianYear(startGregorianYear);
if (startGregorianYear !== endGregorianYear) {
checkGregorianYear(endGregorianYear);
}
return Array.from(new Map(occurrences.map(item => [JSON.stringify(item.ethiopian), item])).values());
}
const getAllMoulidDates = (year) => findAllIslamicOccurrences(year, 3, 12);
const getAllEidFitrDates = (year) => findAllIslamicOccurrences(year, 10, 1);
const getAllEidAdhaDates = (year) => findAllIslamicOccurrences(year, 12, 10);
export function getHoliday(holidayKey, ethYear, options = {}) {
validateNumericInputs('getHoliday', { ethYear });
const { lang = 'amharic' } = options;
const info = holidayInfo[holidayKey];
if (!info) return null;
const name = info?.name?.[lang] || info?.name?.english;
const description = info?.description?.[lang] || info?.description?.english;
if (fixedHolidays[holidayKey]) {
const rules = fixedHolidays[holidayKey];
return { key: holidayKey, tags: rules.tags, movable: false, name, description, ethiopian: { year: ethYear, month: rules.month, day: rules.day } };
}
const tewsakKey = keyToTewsakMap[holidayKey];
if (tewsakKey) {
const date = getMovableHoliday(tewsakKey, ethYear);
return { key: holidayKey, tags: movableHolidays[holidayKey].tags, movable: true, name, description, ethiopian: date, gregorian: toGC(date.year, date.month, date.day) };
}
let muslimDateData;
if (holidayKey === 'eidFitr') muslimDateData = getAllEidFitrDates(ethYear)[0];
else if (holidayKey === 'eidAdha') muslimDateData = getAllEidAdhaDates(ethYear)[0];
else if (holidayKey === 'moulid') muslimDateData = getAllMoulidDates(ethYear)[0];
if (muslimDateData) {
return { key: holidayKey, tags: movableHolidays[holidayKey].tags, movable: true, name, description, ethiopian: muslimDateData.ethiopian, gregorian: muslimDateData.gregorian };
}
return null;
}
export function getHolidaysInMonth(ethYear, ethMonth, options = {}) {
validateNumericInputs("getHolidaysInMonth", { ethYear, ethMonth });
if (ethMonth < 1 || ethMonth > 13) {
throw new InvalidInputTypeError("getHolidaysInMonth", "ethMonth", "number between 1 and 13", ethMonth);
}
const { lang = 'amharic', filter = null } = options;
const allHolidaysForMonth = [];
const allHolidayKeys = Object.keys(holidayInfo);
allHolidayKeys.forEach(key => {
const holiday = getHoliday(key, ethYear, { lang });
if (holiday && holiday.ethiopian.month === ethMonth) {
allHolidaysForMonth.push(holiday);
}
});
// Handle cases where Islamic holidays occur twice
const muslimHolidays = [
...getAllMoulidDates(ethYear).map(d => ({...d, key: 'moulid'})),
...getAllEidFitrDates(ethYear).map(d => ({...d, key: 'eidFitr'})),
...getAllEidAdhaDates(ethYear).map(d => ({...d, key: 'eidAdha'})),
];
muslimHolidays.forEach(data => {
if(data.ethiopian.month === ethMonth) {
const info = holidayInfo[data.key];
const holidayObj = {
key: data.key,
tags: movableHolidays[data.key].tags,
movable: true,
name: info?.name?.[lang] || info?.name?.english,
description: info?.description?.[lang] || info?.description?.english,
ethiopian: data.ethiopian,
gregorian: data.gregorian,
};
// Avoid duplicates from the getHoliday call
if (!allHolidaysForMonth.some(h => JSON.stringify(h.ethiopian) === JSON.stringify(holidayObj.ethiopian))) {
allHolidaysForMonth.push(holidayObj);
}
}
});
const filterTags = filter ? (Array.isArray(filter) ? filter : [filter]) : null;
const finalHolidays = filterTags
? allHolidaysForMonth.filter(holiday => holiday.tags.some(tag => filterTags.includes(tag)))
: allHolidaysForMonth;
finalHolidays.sort((a, b) => a.ethiopian.day - b.ethiopian.day);
return finalHolidays;
}
export function getHolidaysForYear(ethYear, options = {}) {
validateNumericInputs('getHolidaysForYear', { ethYear });
const { lang = 'amharic', filter = null } = options;
const allHolidaysForYear = [];
// Process all fixed and Christian movable holidays
const singleOccurrenceKeys = Object.keys(fixedHolidays).concat(Object.keys(keyToTewsakMap));
singleOccurrenceKeys.forEach(key => {
const holiday = getHoliday(key, ethYear, { lang });
if (holiday) {
allHolidaysForYear.push(holiday);
}
});
// Process all occurrences of Islamic holidays
const addMuslimHolidays = (key, dateArray) => {
dateArray.forEach(data => {
const info = holidayInfo[key];
allHolidaysForYear.push({
key,
tags: movableHolidays[key].tags,
movable: true,
name: info?.name?.[lang] || info?.name?.english,
description: info?.description?.[lang] || info?.description?.english,
ethiopian: data.ethiopian,
gregorian: data.gregorian,
});
});
};
addMuslimHolidays('moulid', getAllMoulidDates(ethYear));
addMuslimHolidays('eidFitr', getAllEidFitrDates(ethYear));
addMuslimHolidays('eidAdha', getAllEidAdhaDates(ethYear));
const filterTags = filter ? (Array.isArray(filter) ? filter : [filter]) : null;
const finalHolidays = filterTags
? allHolidaysForYear.filter(holiday => holiday.tags.some(tag => filterTags.includes(tag)))
: allHolidaysForYear;
finalHolidays.sort((a, b) => {
if (a.ethiopian.month !== b.ethiopian.month) {
return a.ethiopian.month - b.ethiopian.month;
}
return a.ethiopian.day - b.ethiopian.day;
});
return finalHolidays;
}