diff --git a/README.md b/README.md index 7e5a8c1..314785e 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,14 @@ The content of the `colombianHolidays2015` variable will be the following array: ``` +You can opt-in to have the function return native JavaScript dates instead of strings for the `date` and `celebrationDate` properties by using the `returnNativeDate` option: + + + +```js +const colombianHolidays2015 = colombianHolidays(2015, { returnNativeDate }); +``` + If the year is omitted, by default the function will return the holidays for the current year: ```js @@ -124,14 +132,6 @@ const holidays = holidaysWithinInterval({ start, end }); The module is written in TypeScript and type definitions files are included. -## Contributing - -Contributions, issues and feature requests are welcome! - -## Show your support - -Give a ⭐️ if you like this project! - ## License [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FMauricioRobayo%2Fcolombian-holidays.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FMauricioRobayo%2Fcolombian-holidays?ref=badge_large) diff --git a/jest.config.js b/jest.config.js index cfabcbb..dc67dcf 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,13 +1,5 @@ module.exports = { - coverageThreshold: { - global: { - branches: 100, - functions: 100, - lines: 100, - statements: 100, - }, - }, - collectCoverageFrom: ['src/**/*'], - preset: 'ts-jest', - testEnvironment: 'node', + collectCoverageFrom: ["src/**/*"], + preset: "ts-jest", + testEnvironment: "node", }; diff --git a/src/helpers.ts b/src/helpers.ts index 964857b..9364afe 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,5 +1,10 @@ import pascua from "pascua"; -import type { Holiday, EasterHoliday, ColombianHoliday } from "./types"; +import type { + Holiday, + EasterHoliday, + ColombianHoliday, + ColombianHolidayWithNativeDate, +} from "./types"; // 1984 is the year when the current holidays scheme is enforced // http://www.alcaldiabogota.gov.co/sisjur/normas/Norma1.jsp?i=4954 @@ -7,7 +12,9 @@ export const NEW_HOLIDAY_SCHEMA_START_YEAR = 1984; function getNextDayOfWeek(date: Date, dayOfWeek: number): Date { const resultDate = new Date(date); - resultDate.setDate(date.getDate() + ((7 + dayOfWeek - date.getDay()) % 7)); + resultDate.setUTCDate( + date.getUTCDate() + ((7 + dayOfWeek - date.getUTCDay()) % 7) + ); return resultDate; } @@ -23,29 +30,53 @@ function isEasterHoliday(holiday: Holiday): holiday is EasterHoliday { function getHolidayDate(holiday: Holiday, year: number): Date { if (isEasterHoliday(holiday)) { const { month, day } = pascua(year); - return new Date(year, month - 1, day + holiday.offset); + const date = new Date(`${year}-${String(month).padStart(2, "0")}-01`); + date.setUTCDate(day + holiday.offset); + return date; } const [month, day] = holiday.date.split("-"); - return new Date(year, Number(month) - 1, Number(day)); + return new Date(`${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`); } function formatDate(date: Date): string { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const day = String(date.getDate()).padStart(2, "0"); + const year = date.getUTCFullYear(); + const month = String(date.getUTCMonth() + 1).padStart(2, "0"); + const day = String(date.getUTCDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } -function getHoliday(holiday: Holiday, year: number): ColombianHoliday { +function getHoliday( + holiday: Holiday, + year: number, + options?: undefined | { returnNativeDate?: false | undefined } +): ColombianHoliday; +function getHoliday( + holiday: Holiday, + year: number, + options?: { returnNativeDate: true } +): ColombianHolidayWithNativeDate; +function getHoliday( + holiday: Holiday, + year: number, + options?: { returnNativeDate?: boolean } +): ColombianHoliday | ColombianHolidayWithNativeDate; +function getHoliday( + holiday: Holiday, + year: number, + { returnNativeDate = false }: { returnNativeDate?: boolean } = {} +): unknown { const holidayDate = getHolidayDate(holiday, year); const celebrationDate = year >= NEW_HOLIDAY_SCHEMA_START_YEAR && holiday.nextMonday ? getNextMonday(holidayDate) : holidayDate; + return { - date: formatDate(holidayDate), - celebrationDate: formatDate(celebrationDate), + date: returnNativeDate ? holidayDate : formatDate(holidayDate), + celebrationDate: returnNativeDate + ? celebrationDate + : formatDate(celebrationDate), name: holiday.name, nextMonday: year >= NEW_HOLIDAY_SCHEMA_START_YEAR && holiday.nextMonday, }; diff --git a/src/holidays.ts b/src/holidays.ts index f46d62c..c243ecd 100644 --- a/src/holidays.ts +++ b/src/holidays.ts @@ -2,18 +2,18 @@ import type { DateHoliday, EasterHoliday } from "./types"; const dateHolidays: DateHoliday[] = [ { date: "01-01", name: "Año Nuevo", nextMonday: false }, - { date: "05-01", name: "Día del Trabajo", nextMonday: false }, - { date: "07-20", name: "Grito de la Independencia", nextMonday: false }, - { date: "08-07", name: "Batalla de Boyacá", nextMonday: false }, - { date: "12-08", name: "Inmaculada Concepción", nextMonday: false }, - { date: "12-25", name: "Navidad", nextMonday: false }, { date: "01-06", name: "Reyes Magos", nextMonday: true }, { date: "03-19", name: "San José", nextMonday: true }, + { date: "05-01", name: "Día del Trabajo", nextMonday: false }, { date: "06-29", name: "San Pedro y San Pablo", nextMonday: true }, + { date: "07-20", name: "Grito de la Independencia", nextMonday: false }, + { date: "08-07", name: "Batalla de Boyacá", nextMonday: false }, { date: "08-15", name: "Asunción de la Virgen", nextMonday: true }, { date: "10-12", name: "Día de la Raza", nextMonday: true }, { date: "11-01", name: "Todos los Santos", nextMonday: true }, { date: "11-11", name: "Independencia de Cartagena", nextMonday: true }, + { date: "12-08", name: "Inmaculada Concepción", nextMonday: false }, + { date: "12-25", name: "Navidad", nextMonday: false }, ]; // We could simplify the calculation by setting the offset to match Monday. diff --git a/src/index.test.ts b/src/index.test.ts index 6d375eb..9a1da97 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -459,11 +459,46 @@ afterEach(() => { }); describe.each(years)("Gets all holidays for %p", (year) => { - it.each(timezones)("Should return holidays for %p", (timezone) => { - timezone_mock.register(timezone); - expect(colombianHolidays(year).length).toBe(holidaysYears[year].length); - expect(colombianHolidays(year)).toEqual(holidaysYears[year]); - }); + it.each(timezones)( + "Should return holidays formatted as string for %p if no options given", + (timezone) => { + timezone_mock.register(timezone); + expect(colombianHolidays(year)).toEqual(holidaysYears[year]); + } + ); + + it.each(timezones)( + "Should return holidays formatted as string for %p if options is empty", + (timezone) => { + timezone_mock.register(timezone); + expect(colombianHolidays(year, {})).toEqual(holidaysYears[year]); + } + ); + + it.each(timezones)( + "Should return holidays formatted as string for %p if returnNativeDate is set to false", + (timezone) => { + timezone_mock.register(timezone); + expect(colombianHolidays(year, { returnNativeDate: false })).toEqual( + holidaysYears[year] + ); + } + ); + + it.each(timezones)( + "Should return holidays with native JS date for %p if returnNativeDate is set to true", + (timezone) => { + timezone_mock.register(timezone); + expect(colombianHolidays(year, { returnNativeDate: true })).toEqual( + holidaysYears[year].map((holiday) => ({ + date: new Date(holiday.date), + celebrationDate: new Date(holiday.celebrationDate), + name: holiday.name, + nextMonday: holiday.nextMonday, + })) + ); + } + ); }); describe("Gets all holidays for the current year", () => { diff --git a/src/index.ts b/src/index.ts index 438224a..8ae6892 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,27 @@ import getHoliday from "./helpers"; import holidays from "./holidays"; -import { ColombianHoliday } from "./types"; +import { ColombianHoliday, ColombianHolidayWithNativeDate } from "./types"; // pascua package year limits export const FIRST_HOLIDAY_YEAR = 1583; export const LAST_HOLIDAY_YEAR = 4099; function colombianHolidays( - year: number = new Date().getFullYear() -): ColombianHoliday[] { + year?: number, + options?: undefined | { returnNativeDate?: false | undefined } +): ColombianHoliday[]; +function colombianHolidays( + year?: number, + options?: { returnNativeDate?: true } +): ColombianHolidayWithNativeDate[]; +function colombianHolidays( + year?: number, + options?: { returnNativeDate?: boolean } +): (ColombianHoliday | ColombianHolidayWithNativeDate)[]; +function colombianHolidays( + year: number = new Date().getFullYear(), + { returnNativeDate = false }: { returnNativeDate?: boolean } = {} +): unknown[] { if (year < FIRST_HOLIDAY_YEAR || year > LAST_HOLIDAY_YEAR) { throw new Error( `The year should be between ${FIRST_HOLIDAY_YEAR} and ${LAST_HOLIDAY_YEAR}` @@ -16,8 +29,24 @@ function colombianHolidays( } return holidays - .map((holiday) => getHoliday(holiday, year)) - .sort((a, b) => a.celebrationDate.localeCompare(b.celebrationDate)); + .map((holiday) => getHoliday(holiday, year, { returnNativeDate })) + .sort((a, b) => { + if ( + a.celebrationDate instanceof Date && + b.celebrationDate instanceof Date + ) { + return a.celebrationDate.getTime() - b.celebrationDate.getTime(); + } + + if ( + typeof a.celebrationDate === "string" && + typeof b.celebrationDate === "string" + ) { + return a.celebrationDate.localeCompare(b.celebrationDate); + } + + throw new Error("Invariant violation: this state is not possible."); + }); } export default colombianHolidays; diff --git a/src/types.ts b/src/types.ts index d6fb7f6..3c417bd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,4 +16,9 @@ export interface ColombianHoliday extends BasicHoliday { celebrationDate: string; } +export interface ColombianHolidayWithNativeDate extends BasicHoliday { + date: Date; + celebrationDate: Date; +} + export type Holiday = DateHoliday | EasterHoliday;