From df9a6d466328ef49c28fc4f77995336e28e46b12 Mon Sep 17 00:00:00 2001 From: Simon J Date: Wed, 2 Feb 2022 13:26:01 +1100 Subject: [PATCH] Improve date handling (#94) --- src/clock.ts | 48 +++++++++++++---- tests/clock.test.ts | 127 +++++++++++++++++++++++++++----------------- tsconfig.json | 2 +- 3 files changed, 115 insertions(+), 62 deletions(-) diff --git a/src/clock.ts b/src/clock.ts index 77b16d4..e3cf267 100644 --- a/src/clock.ts +++ b/src/clock.ts @@ -1,13 +1,38 @@ import dayjs, { Dayjs } from "dayjs"; -export const isChristmas = (clock: Clock = SystemClock) => clock.now().month() == 11 && clock.now().date() == 25; -export const isMay4 = (clock: Clock = SystemClock) => clock.now().month() == 4 && clock.now().date() == 4; -export const isHalloween = (clock: Clock = SystemClock) => clock.now().month() == 9 && clock.now().date() == 31 -export const isHoli = (clock: Clock = SystemClock) => ["2022/03/18", "2023/03/07", "2024/03/25", "2025/03/14"].map(dayjs).find(it => it.isSame(clock.now())) != undefined -export const isCNY = (clock: Clock = SystemClock) => ["2022/02/01", "2023/01/22", "2024/02/10", "2025/02/29"].map(dayjs).find(it => it.isSame(clock.now())) != undefined -export const isCNY_2022 = (clock: Clock = SystemClock) => clock.now().isSame(dayjs("2022/02/01")) -export const isCNY_2023 = (clock: Clock = SystemClock) => clock.now().isSame(dayjs("2023/01/22")) -export const isCNY_2024 = (clock: Clock = SystemClock) => clock.now().isSame(dayjs("2024/02/10")) +function fixedDateMonthEvent(dateMonth: string) { + const date = Number.parseInt(dateMonth.split("/")[0]!); + const month = Number.parseInt(dateMonth.split("/")[1]!); + return (clock: Clock = SystemClock) => { + return clock.now().date() == date && clock.now().month() == month - 1; + }; +} + +function fixedDateEvent(date: string) { + const dayjsDate = dayjs(date); + return (clock: Clock = SystemClock) => { + return clock.now().isSame(dayjsDate, "day"); + }; +} + +function anyOf(rules: ((clock: Clock) => boolean)[]) { + return (clock: Clock = SystemClock) => { + return rules.find((rule) => rule(clock)) != undefined; + }; +} + +export const isChristmas = fixedDateMonthEvent("25/12"); +export const isMay4 = fixedDateMonthEvent("04/05"); +export const isHalloween = fixedDateMonthEvent("31/10"); +export const isHoli = anyOf( + ["2022/03/18", "2023/03/07", "2024/03/25", "2025/03/14"].map(fixedDateEvent) +) + +export const isCNY_2022 = fixedDateEvent("2022/02/01"); +export const isCNY_2023 = fixedDateEvent("2023/01/22"); +export const isCNY_2024 = fixedDateEvent("2024/02/10"); +export const isCNY_2025 = fixedDateEvent("2025/02/29"); +export const isCNY = anyOf([isCNY_2022, isCNY_2023, isCNY_2024, isCNY_2025]); export interface Clock { now(): Dayjs; @@ -17,12 +42,13 @@ export const SystemClock = { now: () => dayjs() }; export class FixedClock implements Clock { time: Dayjs; - + constructor(time: Dayjs = dayjs()) { this.time = time; } - add = (t: number, unit: dayjs.UnitTypeShort) => this.time = this.time.add(t, unit) + add = (t: number, unit: dayjs.UnitTypeShort) => + (this.time = this.time.add(t, unit)); now = () => this.time; -} \ No newline at end of file +} diff --git a/tests/clock.test.ts b/tests/clock.test.ts index 1d90ff3..b4e0220 100644 --- a/tests/clock.test.ts +++ b/tests/clock.test.ts @@ -1,58 +1,85 @@ -import dayjs from "dayjs"; -import { isChristmas, isCNY, isHalloween, isHoli } from "../src/clock"; +import { randomInt } from "crypto"; +import dayjs, { Dayjs } from "dayjs"; +import timezone from "dayjs/plugin/timezone"; +dayjs.extend(timezone); -describe("isChristmas", () => { - ["2000/12/25", "2022/12/25", "2030/12/25"].forEach((date) => { - it(`should return true for ${date} regardless of year`, () => { - expect(isChristmas({ now: () => dayjs(date) })).toEqual(true); +import { Clock, isChristmas, isCNY, isCNY_2022, isCNY_2023, isCNY_2024, isCNY_2025, isHalloween, isHoli, isMay4 } from "../src/clock"; + + + +const randomDate = () => dayjs().subtract(randomInt(1, 1000), 'days'); +const randomDates = (count: number, exclude: string[]) => { + const result: Dayjs[] = []; + while(result.length < count) { + const next = randomDate(); + if(!exclude.find(it => dayjs(it).isSame(next, 'date'))) { + result.push(next) + } + } + return result +} + +function describeFixedDateMonthEvent( + name: string, + dateMonth: string, + f: (clock: Clock) => boolean +) { + const randomYear = randomInt(2020, 3000); + const date = dateMonth.split("/")[0]; + const month = dateMonth.split("/")[1]; + + describe(name, () => { + it(`should return true for ${randomYear}-${month}-${date}T00:00:00 ragardless of year`, () => { + expect(f({ now: () => dayjs(`${randomYear}-${month}-${date}T00:00:00Z`) })).toEqual(true); + }); + + it(`should return true for ${randomYear}-${month}-${date}T12:00:00 regardless of year`, () => { + expect(f({ now: () => dayjs(`${randomYear}-${month}-${date}T12:00:00Z`) })).toEqual(true); + }); + + it(`should return true for ${randomYear}-${month}-${date}T23:59:00 regardless of year`, () => { + expect(f({ now: () => dayjs(`${randomYear}-${month}-${date}T23:59:00`) })).toEqual(true); + }); + + ["2000/12/24", "2000/12/26", "2021/01/01"].forEach((date) => { + it(`should return false for ${date}`, () => { + expect(f({ now: () => dayjs(date) })).toEqual(false); + }); }); }); +} - ["2000/12/24", "2000/12/26", "2021/01/01"].forEach((date) => { - it(`should return false for ${date} regardless of year`, () => { - expect(isChristmas({ now: () => dayjs(date) })).toEqual(false); +function describeFixedDateEvent( + name: string, + dates: string[], + f: (clock: Clock) => boolean +) { + describe(name, () => { + dates.forEach((date) => { + it(`should return true for ${date}T00:00:00`, () => { + expect(f({ now: () => dayjs(`${date}T00:00:00`) })).toEqual(true); + }); + + it(`should return true for ${date}T23:59:59`, () => { + expect(f({ now: () => dayjs(`${date}T23:59:59`) })).toEqual(true); + }); + }); + + randomDates(10, dates).forEach((date) => { + it(`should return false for ${date}`, () => { + expect(f({ now: () => dayjs(date) })).toEqual(false); + }); }); }); -}); +} -describe("isHalloween", () => { - ["2000/10/31", "2022/10/31", "2030/10/31"].forEach((date) => { - it(`should return true for ${date} regardless of year`, () => { - expect(isHalloween({ now: () => dayjs(date) })).toEqual(true); - }); - }); +describeFixedDateMonthEvent("christmas", "25/12", isChristmas); +describeFixedDateMonthEvent("halloween", "31/10", isHalloween); +describeFixedDateMonthEvent("may4", "04/05", isMay4); - ["2000/09/31", "2000/10/30", "2021/01/01"].forEach((date) => { - it(`should return false for ${date} regardless of year`, () => { - expect(isHalloween({ now: () => dayjs(date) })).toEqual(false); - }); - }); -}); - -describe("isHoli", () => { - ["2022/03/18", "2023/03/07", "2024/03/25", "2025/03/14"].forEach((date) => { - it(`should return true for ${date} regardless of year`, () => { - expect(isHoli({ now: () => dayjs(date) })).toEqual(true); - }); - }); - - ["2000/09/31", "2000/10/30", "2021/01/01"].forEach((date) => { - it(`should return false for ${date} regardless of year`, () => { - expect(isHoli({ now: () => dayjs(date) })).toEqual(false); - }); - }); -}); - -describe("isCNY", () => { - ["2022/02/01", "2023/01/22", "2024/02/10", "2025/02/29"].forEach((date) => { - it(`should return true for ${date} regardless of year`, () => { - expect(isCNY({ now: () => dayjs(date) })).toEqual(true); - }); - }); - - ["2000/09/31", "2000/10/30", "2021/01/01"].forEach((date) => { - it(`should return false for ${date} regardless of year`, () => { - expect(isCNY({ now: () => dayjs(date) })).toEqual(false); - }); - }); -}); +describeFixedDateEvent("holi", ["2022-03-18", "2023-03-07", "2024-03-25", "2025-03-14"], isHoli); +describeFixedDateEvent("cny", ["2022-02-01", "2023-01-22", "2024-02-10", "2025-02-29"], isCNY); +describeFixedDateEvent("cny 2022", ["2022-02-01"], isCNY_2022); +describeFixedDateEvent("cny 2023", ["2023/01/22"], isCNY_2023); +describeFixedDateEvent("cny 2024", ["2024/02/10"], isCNY_2024); +describeFixedDateEvent("cny 2025", ["2025/02/29"], isCNY_2025); diff --git a/tsconfig.json b/tsconfig.json index a4c8f0a..fca4321 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -54,7 +54,7 @@ ] /* List of folders to include type definitions from. */, // "types": ["src/customTypes/scale-that-svg.d.ts"], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */