From 7ac4e085ebd46217dd81e8469ee65d0ccbd7aaa9 Mon Sep 17 00:00:00 2001 From: Christopher Orr Date: Fri, 15 Nov 2024 10:07:38 +0100 Subject: [PATCH 1/2] Try to show holiday names using the browser's preferred language(s). The date-holidays library has translations for many holidays, so we can usually show a useful translation to the user, rather than all holidays appearing in the language of the selected country. However, they only support two-character language codes, and don't have any sort of fallback, e.g. using an available `en` translation if we pass in `en-GB`. So we just truncate all languages to two characters. --- src/lib/holidayUtils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/holidayUtils.ts b/src/lib/holidayUtils.ts index 098e2e4..133f389 100644 --- a/src/lib/holidayUtils.ts +++ b/src/lib/holidayUtils.ts @@ -30,7 +30,11 @@ const formatDate = (date: Date): string => date.toLocaleDateString('en-US', { mo // Get holidays for a specific year and country export function getHolidaysForYear(countryCode: string, year: number, stateCode?: string): { date: Date; name: string }[] { - const hd = stateCode ? new Holidays(countryCode, stateCode) : new Holidays(countryCode); + // The date-holidays lib has translations for many holidays, but defaults to using the language of the country. + // We can pass in the browser's preferred languages (though the lib doesn't fall back, e.g. from `de-AT` to `de`) + const languages = navigator.languages.map(lang => lang.split('-')[0]); + const opts = { languages } + const hd = stateCode ? new Holidays(countryCode, stateCode, opts) : new Holidays(countryCode, opts); return hd.getHolidays(year) .filter(holiday => holiday.type === 'public') .map(holiday => ({ From 3d89eb8582a25722a7c978f2408fb5a1b878e56e Mon Sep 17 00:00:00 2001 From: Christopher Orr Date: Fri, 15 Nov 2024 10:10:11 +0100 Subject: [PATCH 2/2] Handle multi-day holidays. Some countries (e.g. Russia, or Vietnam) have holidays which span several days. Rather than using `holiday.date` from the date-holidays library, we can check the `.start` (inclusive) and `.end` (exclusive) dates, and use these to show multiple holiday days on the calendar. However since these properties return a `Date` representing the start of the day in that country, rather than an ISO string like `.date` does, we also need to pass in the current browser `timezone` to the library, so that we show the correct dates to the user. --- src/lib/holidayUtils.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/holidayUtils.ts b/src/lib/holidayUtils.ts index 133f389..59d8fe3 100644 --- a/src/lib/holidayUtils.ts +++ b/src/lib/holidayUtils.ts @@ -33,14 +33,18 @@ export function getHolidaysForYear(countryCode: string, year: number, stateCode? // The date-holidays lib has translations for many holidays, but defaults to using the language of the country. // We can pass in the browser's preferred languages (though the lib doesn't fall back, e.g. from `de-AT` to `de`) const languages = navigator.languages.map(lang => lang.split('-')[0]); - const opts = { languages } + // Start/end dates are returned in that country/state's time zone, so we need to provide our time zone to localise + const opts = { languages, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }; const hd = stateCode ? new Holidays(countryCode, stateCode, opts) : new Holidays(countryCode, opts); return hd.getHolidays(year) .filter(holiday => holiday.type === 'public') - .map(holiday => ({ - date: new Date(holiday.date), - name: holiday.name - })); + .flatMap(holiday => + // To handle single- and multi-day holidays, we generate a holiday entry for each day in the period + Array.from({ length: daysBetween(holiday.start, holiday.end) }, (_, i) => ({ + date: new Date(holiday.start.getFullYear(), holiday.start.getMonth(), holiday.start.getDate() + i), + name: holiday.name, + })) + ); } // Optimize days off to create the longest possible chains