From a45e170a18e95eebfdf6d2e83baa7a8467c0f57d Mon Sep 17 00:00:00 2001 From: Tomasz Swistak Date: Wed, 13 Mar 2024 20:29:24 +0100 Subject: [PATCH] parsing fixed-date holidays #11 --- .github/workflows/holiday-generator/config.js | 1 + .github/workflows/holiday-generator/main.js | 58 ++++++++++++++----- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/.github/workflows/holiday-generator/config.js b/.github/workflows/holiday-generator/config.js index 672ab825b..247a678ab 100644 --- a/.github/workflows/holiday-generator/config.js +++ b/.github/workflows/holiday-generator/config.js @@ -79,6 +79,7 @@ export const COUNTRIES = [ export const START_YEAR = new Date().getFullYear(); // start with current year export const END_YEAR = START_YEAR + 1; +export const FIXED_DATE_START_YEAR = 1970; // start recurring events from start of Unix epoch // https://www.npmjs.com/package/date-holidays#types-of-holidays export const TYPE_WHITELIST = ["public", "bank"]; diff --git a/.github/workflows/holiday-generator/main.js b/.github/workflows/holiday-generator/main.js index cc8f3130a..10c3355fd 100644 --- a/.github/workflows/holiday-generator/main.js +++ b/.github/workflows/holiday-generator/main.js @@ -6,7 +6,7 @@ import { promisify } from "node:util"; import Holidays from "date-holidays"; import { createEvents as icsCreateEvents } from "ics"; -import { COUNTRIES, END_YEAR, ICS_PATH, SHOULD_LOG, START_YEAR, TYPE_WHITELIST } from "./config.js"; +import { COUNTRIES, END_YEAR, FIXED_DATE_START_YEAR, ICS_PATH, SHOULD_LOG, START_YEAR, TYPE_WHITELIST } from "./config.js"; // converting createEvents from ics from function with callback to async function for easier usage const createEvents = promisify(icsCreateEvents); @@ -39,11 +39,12 @@ function getEvents(countryCode) { * Generates reproducible ID for holiday * @param {string} countryCode * @param {string} date + * @param {string} rule * @returns */ -function generateUid(countryCode, date) { +function generateUid(countryCode, date, rule) { const hashGen = createHash("sha256"); - hashGen.update(`${countryCode},${date}`); + hashGen.update(`${countryCode},${date},${rule}`); return hashGen.digest("hex"); } @@ -56,6 +57,16 @@ function getDateArray(date) { return [date.getFullYear(), date.getMonth() + 1, date.getDate()]; } +/** + * Checks if holiday is a fixed-date holiday. + * Regex based on https://github.com/commenthol/date-holidays/blob/master/docs/specification.md#fixed-date + * @param {string} rule + * @returns + */ +function isFixedDate(rule) { + return /^\d\d-\d\d$/.test(rule); +} + /** * Generate ical file from given set of events * @param {ReturnType} events @@ -63,16 +74,37 @@ function getDateArray(date) { * @returns {Promise} */ async function generateIcal(events, countryCode) { - const ical = await createEvents( - events.map((x) => ({ - title: x.name, - uid: generateUid(countryCode, x.date), - start: getDateArray(x.start), - end: getDateArray(x.end), - productId: "Fossify Calendar Holiday Generator", - status: "CONFIRMED", - })) - ); + const eventsMap = new Map(); + events.forEach((x) => { + if (isFixedDate(x.rule)) { + const uid = generateUid(countryCode, "", x.rule); + if (!eventsMap.has(uid)) { + const yearDiff = x.end.getFullYear() - x.start.getFullYear(); + x.start.setFullYear(FIXED_DATE_START_YEAR); + x.end.setFullYear(FIXED_DATE_START_YEAR + yearDiff); + eventsMap.set(uid, { + title: x.name, + uid, + start: getDateArray(x.start), + end: getDateArray(x.end), + recurrenceRule: "FREQ=YEARLY", + productId: "Fossify Calendar Holiday Generator", + status: "CONFIRMED", + }); + } + } else { + const uid = generateUid(countryCode, x.date, x.rule); + eventsMap.set(uid, { + title: x.name, + uid, + start: getDateArray(x.start), + end: getDateArray(x.end), + productId: "Fossify Calendar Holiday Generator", + status: "CONFIRMED", + }); + } + }); + const ical = await createEvents([...eventsMap.values()]); return ical; }