From b1fb8b7c8528990317764d4d70f43c19a68766a1 Mon Sep 17 00:00:00 2001 From: Dmitriy Kovalenko Date: Wed, 10 Jan 2024 16:08:45 +0100 Subject: [PATCH] feat: Improve utils.date typescript typings closes #651 --- __tests__/hijri.test.ts | 12 ++----- __tests__/inheritance.test.ts | 2 +- __tests__/test-utils.ts | 4 +-- packages/core/IUtils.d.ts | 35 ++++++++++++++++--- .../src/date-fns-jalali-utils.ts | 19 ++++++---- packages/date-fns/src/date-fns-utils.ts | 21 +++++++---- packages/dayjs/src/dayjs-utils.ts | 19 +++++++--- packages/hijri/src/hijri-utils.ts | 15 +++++--- packages/jalaali/src/jalaali-utils.ts | 15 +++++--- packages/js-joda/src/js-joda-utils.ts | 26 +++++++++----- packages/luxon/src/luxon-utils.ts | 27 +++++++++----- packages/moment/src/moment-utils.ts | 33 ++++++++++------- yarn.lock | 8 ++--- 13 files changed, 160 insertions(+), 76 deletions(-) diff --git a/__tests__/hijri.test.ts b/__tests__/hijri.test.ts index 3dc3d6c5..3646559c 100644 --- a/__tests__/hijri.test.ts +++ b/__tests__/hijri.test.ts @@ -49,9 +49,7 @@ describe("Hijri", () => { it("Hijri -- getYear", () => { const date = hijriiUtils.date(TEST_TIMESTAMP); - expect(hijriiUtils.getYear(date)).toEqual( - 1440 - ); + expect(hijriiUtils.getYear(date)).toEqual(1440); }); it("Hijri -- setYear", () => { @@ -65,9 +63,7 @@ describe("Hijri", () => { it("Hijri -- getDate", () => { const date = hijriiUtils.date(TEST_TIMESTAMP); - expect(hijriiUtils.getDate(date)).toEqual( - 21 - ); + expect(hijriiUtils.getDate(date)).toEqual(21); }); it("Hijri -- setDate", () => { @@ -81,9 +77,7 @@ describe("Hijri", () => { it("Hijri -- endOfYear", () => { const date = hijriiUtils.date(TEST_TIMESTAMP); - expect(hijriiUtils.endOfYear(date).toISOString()).toEqual( - "2019-08-30T23:59:59.999Z" - ); + expect(hijriiUtils.endOfYear(date).toISOString()).toEqual("2019-08-30T23:59:59.999Z"); }); it("Hijri -- startOfYear", () => { diff --git a/__tests__/inheritance.test.ts b/__tests__/inheritance.test.ts index 99310af7..d2e91d6b 100644 --- a/__tests__/inheritance.test.ts +++ b/__tests__/inheritance.test.ts @@ -2,7 +2,7 @@ import { IUtils } from "@date-io/core/IUtils"; import DayjsUtils from "../packages/dayjs/src/dayjs-utils"; import { Dayjs } from "dayjs"; -class CustomDateTime extends DayjsUtils implements IUtils { +class CustomDateTime extends DayjsUtils implements IUtils { getYear = () => 2007; getWeekdays = () => { const start = this.dayjs().startOf("week"); diff --git a/__tests__/test-utils.ts b/__tests__/test-utils.ts index 613b81b8..fc439397 100644 --- a/__tests__/test-utils.ts +++ b/__tests__/test-utils.ts @@ -20,7 +20,7 @@ export const allUtils = [ export const utilsTest = ( name: string, - innerFn: (date: any, utils: IUtils, currentLib: TestLib) => void + innerFn: (date: any, utils: IUtils, currentLib: TestLib) => void ) => { test.each(allUtils)(`%s -- ${name}`, (name, utils) => innerFn(utils.date(TEST_TIMESTAMP), utils, name) @@ -33,7 +33,7 @@ export const localDateAllUtils = [ export const localDateutilsTest = ( name: string, - innerFn: (date: any, utils: IUtils, currentLib: TestLib) => void + innerFn: (date: any, utils: IUtils, currentLib: TestLib) => void ) => { test.each(localDateAllUtils)(`%s -- ${name}`, (name, utils) => innerFn(utils.date(LOCALDATE_TEST_TIMESTAMP), utils, name) diff --git a/packages/core/IUtils.d.ts b/packages/core/IUtils.d.ts index 82bfaa94..09cb3fd9 100644 --- a/packages/core/IUtils.d.ts +++ b/packages/core/IUtils.d.ts @@ -66,19 +66,44 @@ export type Unit = | "seconds" | "milliseconds"; -export interface ExtendableDateType {} +type ConstructorOptions = { + formats?: Partial; + locale?: TLocale; + instance?: any; +}; -export interface IUtils { +export interface IUtils { formats: DateIOFormats; - locale?: any; + locale?: TLocale; moment?: any; dayjs?: any; /** Name of the library that is used right now */ lib: string; - // constructor (options?: { formats?: DateIOFormats, locale?: any, instance?: any }); + // Constructor type + // new (options?: { + // formats?: Partial; + // locale?: TLocale; + // instance?: any; + // }): IUtils; - date(value?: any): TDate | null; + /** + * Creates a date object. Use `utils.date()` to create a new date object of the underlying library.` + * Supports some of the standard input sources like ISO strings so you can pass the string directly + * as `utils.date("2024-01-10T14:30:00Z"), and javascript `Date` objects `utils.date(new Date())`. + * + * if `null` is passed `null` will be returned. + */ + date< + TArg extends unknown = undefined, + TResultingDate extends unknown = TArg extends null + ? null + : TArg extends undefined + ? TDate + : TDate | null + >( + value?: TArg + ): TResultingDate; toJsDate(value: TDate): Date; parseISO(isString: string): TDate; toISO(value: TDate): string; diff --git a/packages/date-fns-jalali/src/date-fns-jalali-utils.ts b/packages/date-fns-jalali/src/date-fns-jalali-utils.ts index dfae1b66..0743a9f7 100644 --- a/packages/date-fns-jalali/src/date-fns-jalali-utils.ts +++ b/packages/date-fns-jalali/src/date-fns-jalali-utils.ts @@ -100,7 +100,7 @@ var symbolMap = { 0: "۰", }; -export default class DateFnsJalaliUtils implements IUtils { +export default class DateFnsJalaliUtils implements IUtils { public lib = "date-fns-jalali"; public locale?: Locale; public formats: DateIOFormats; @@ -300,17 +300,24 @@ export default class DateFnsJalaliUtils implements IUtils { return setDate(value, count); }; - public date = (value?: string | number | Date) => { + date< + TArg extends unknown = undefined, + TRes extends unknown = TArg extends null + ? null + : TArg extends undefined + ? Date + : Date | null + >(value?: TArg): TRes { if (typeof value === "undefined") { - return new Date(); + return new Date() as TRes; } if (value === null) { - return null; + return null as TRes; } - return new Date(value); - }; + return new Date(value as any) as TRes; + } public toJsDate = (value: Date) => { return value; diff --git a/packages/date-fns/src/date-fns-utils.ts b/packages/date-fns/src/date-fns-utils.ts index 92fcbff4..58afe108 100644 --- a/packages/date-fns/src/date-fns-utils.ts +++ b/packages/date-fns/src/date-fns-utils.ts @@ -85,7 +85,7 @@ const defaultFormats: DateIOFormats = { year: "yyyy", }; -export default class DateFnsUtils implements IUtils { +export default class DateFnsUtils implements IUtils { public lib = "date-fns"; public locale?: Locale; public formats: DateIOFormats; @@ -93,7 +93,7 @@ export default class DateFnsUtils implements IUtils { constructor({ locale, formats, - }: { formats?: Partial; locale?: Locale } = {}) { + }: { formats?: Partial; locale?: Locale; instance?: any } = {}) { this.locale = locale; this.formats = Object.assign({}, defaultFormats, formats); } @@ -288,17 +288,24 @@ export default class DateFnsUtils implements IUtils { return setYear(value, count); }; - public date = (value?: any) => { + date< + TArg extends unknown = undefined, + TRes extends unknown = TArg extends null + ? null + : TArg extends undefined + ? Date + : Date | null + >(value?: TArg): TRes { if (typeof value === "undefined") { - return new Date(); + return new Date() as TRes; } if (value === null) { - return null; + return null as TRes; } - return new Date(value); - }; + return new Date(value as string | number) as TRes; + } public toJsDate = (value: Date) => { return value; diff --git a/packages/dayjs/src/dayjs-utils.ts b/packages/dayjs/src/dayjs-utils.ts index ca7eb104..4e43588b 100644 --- a/packages/dayjs/src/dayjs-utils.ts +++ b/packages/dayjs/src/dayjs-utils.ts @@ -54,7 +54,9 @@ const defaultFormats: DateIOFormats = { keyboardDateTime24h: "L HH:mm", }; -export default class DayjsUtils implements IUtils { +export default class DayjsUtils + implements IUtils +{ public rawDayJsInstance: typeof defaultDayjs; public lib = "dayjs"; public dayjs: Constructor; @@ -119,13 +121,20 @@ export default class DayjsUtils implements IUtils { + date< + TArg extends unknown = undefined, + TRes extends unknown = TArg extends null + ? null + : TArg extends undefined + ? TDate + : TDate | null + >(value?: TArg): TRes { if (value === null) { - return null; + return null as TRes; } - return this.dayjs(value); - }; + return this.dayjs(value as any) as unknown as TRes; + } public toJsDate = (value: Dayjs) => { return value.toDate(); diff --git a/packages/hijri/src/hijri-utils.ts b/packages/hijri/src/hijri-utils.ts index 66567507..21518df2 100644 --- a/packages/hijri/src/hijri-utils.ts +++ b/packages/hijri/src/hijri-utils.ts @@ -81,13 +81,20 @@ export default class MomentUtils extends DefaultMomentUtils { return this.moment(value, format, true).locale("ar-SA"); }; - public date = (value?: any) => { + date< + TArg extends unknown = undefined, + TResultingDate extends unknown = TArg extends null + ? null + : TArg extends undefined + ? Moment + : Moment | null + >(value?: TArg): TResultingDate { if (value === null) { - return null; + return null as TResultingDate; } - return this.moment(value).locale("ar-SA"); - }; + return this.moment(value).locale("ar-SA") as TResultingDate; + } public isBeforeYear = (date: Moment, value: Moment) => { return date.iYear() < value.iYear(); diff --git a/packages/jalaali/src/jalaali-utils.ts b/packages/jalaali/src/jalaali-utils.ts index fd71a6f3..27f74b96 100644 --- a/packages/jalaali/src/jalaali-utils.ts +++ b/packages/jalaali/src/jalaali-utils.ts @@ -80,13 +80,20 @@ export default class MomentUtils extends DefaultMomentUtils { return this.moment(value, format, true).locale("fa"); }; - public date = (value?: any) => { + date< + TArg extends unknown = undefined, + TResultingDate extends unknown = TArg extends null + ? null + : TArg extends undefined + ? Moment + : Moment | null + >(value?: TArg): TResultingDate { if (value === null) { - return null; + return null as TResultingDate; } - return this.moment(value).locale("fa"); - }; + return this.moment(value).locale("fa") as TResultingDate; + } public isBeforeYear = (date: Moment, value: Moment) => { return date.jYear() < value.jYear(); diff --git a/packages/js-joda/src/js-joda-utils.ts b/packages/js-joda/src/js-joda-utils.ts index 9fb1945f..ce791d64 100644 --- a/packages/js-joda/src/js-joda-utils.ts +++ b/packages/js-joda/src/js-joda-utils.ts @@ -113,7 +113,7 @@ function getChronoUnit(unit?: Unit): ChronoUnit | null { } } -export default class JsJodaUtils implements IUtils { +export default class JsJodaUtils implements IUtils { lib: "js-joda"; locale?: Locale; formats: DateIOFormats; @@ -158,35 +158,43 @@ export default class JsJodaUtils implements IUtils { } } - date(value?: any): Temporal | null { + date< + TArg extends unknown = undefined, + TResultingDate extends unknown = TArg extends null + ? null + : TArg extends undefined + ? Temporal + : Temporal | null + >(value?: TArg): TResultingDate { if (value === null) { - return null; + return null as TResultingDate; } if (value === undefined) { - return LocalDateTime.now(); + return LocalDateTime.now() as TResultingDate; } if (typeof value === "string") { try { - return OPTIONAL_FORMATTER.parse(value, dateOrDateTimeQuery) ?? null; + return (OPTIONAL_FORMATTER.parse(value, dateOrDateTimeQuery) ?? + null) as TResultingDate; } catch (ex) { if (ex instanceof DateTimeParseException) { - return (ex); + return ((ex)) as TResultingDate; } } } if (value instanceof Temporal) { - return value; + return value as TResultingDate; } if (value instanceof Date) { const instant = Instant.ofEpochMilli(value.valueOf()); - return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()) as TResultingDate; } /* istanbul ignore next */ - return null; + return LocalDateTime.now() as TResultingDate; } isNull(date: Temporal | null): boolean { diff --git a/packages/luxon/src/luxon-utils.ts b/packages/luxon/src/luxon-utils.ts index c64466df..80faec9e 100644 --- a/packages/luxon/src/luxon-utils.ts +++ b/packages/luxon/src/luxon-utils.ts @@ -31,7 +31,7 @@ const defaultFormats: DateIOFormats = { year: "yyyy", }; -export default class LuxonUtils implements IUtils { +export default class LuxonUtils implements IUtils { public lib = "luxon"; public locale: string; public formats: DateIOFormats; @@ -44,25 +44,36 @@ export default class LuxonUtils implements IUtils { this.formats = Object.assign({}, defaultFormats, formats); } - public date = (value?: any) => { + date< + TArg extends unknown = undefined, + TRes extends unknown = TArg extends null + ? null + : TArg extends undefined + ? DateTime + : DateTime | null + >(value?: TArg): TRes { if (typeof value === "undefined") { - return DateTime.local(); + return DateTime.local() as TRes; } if (value === null) { - return null; + return null as TRes; } if (typeof value === "string") { - return DateTime.fromJSDate(new Date(value), { locale: this.locale }); + return DateTime.fromJSDate(new Date(value), { locale: this.locale }) as TRes; } if (DateTime.isDateTime(value)) { - return value; + return value as TRes; } - return DateTime.fromJSDate(value, { locale: this.locale }); - }; + if (value instanceof Date) { + return DateTime.fromJSDate(value, { locale: this.locale }) as TRes; + } + + return DateTime.local() as TRes; + } public toJsDate = (value: DateTime) => { return value.toJSDate(); diff --git a/packages/moment/src/moment-utils.ts b/packages/moment/src/moment-utils.ts index 4e025dcd..f81b9d5d 100644 --- a/packages/moment/src/moment-utils.ts +++ b/packages/moment/src/moment-utils.ts @@ -1,12 +1,6 @@ import defaultMoment, { LongDateFormatKey } from "moment"; import { IUtils, DateIOFormats, Unit } from "@date-io/core/IUtils"; -interface Opts { - locale?: string; - instance?: typeof defaultMoment; - formats?: Partial; -} - type Moment = defaultMoment.Moment; const defaultFormats: DateIOFormats = { normalDateWithWeekday: "ddd, MMM D", @@ -38,13 +32,21 @@ const defaultFormats: DateIOFormats = { keyboardDateTime24h: "L HH:mm", }; -export default class MomentUtils implements IUtils { +export default class MomentUtils implements IUtils { public moment: typeof defaultMoment; public lib = "moment"; public locale?: string; public formats: DateIOFormats; - constructor({ locale, formats, instance }: Opts = {}) { + constructor({ + locale, + formats, + instance, + }: { + formats?: Partial; + locale?: string; + instance?: any; + } = {}) { this.moment = instance || defaultMoment; this.locale = locale; @@ -104,9 +106,16 @@ export default class MomentUtils implements IUtils { return this.moment(value, format, true); }; - public date = (value?: any) => { + date< + TArg extends unknown = undefined, + TRes extends unknown = TArg extends null + ? null + : TArg extends undefined + ? defaultMoment.Moment + : defaultMoment.Moment | null + >(value?: TArg): TRes { if (value === null) { - return null; + return null as TRes; } const moment = this.moment(value); @@ -114,8 +123,8 @@ export default class MomentUtils implements IUtils { moment.locale(this.locale); } - return moment; - }; + return moment as TRes; + } public toJsDate = (value: Moment) => { return value.toDate(); diff --git a/yarn.lock b/yarn.lock index 92661318..73f21363 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2739,10 +2739,10 @@ date-fns-jalali@^2.19.0-2: resolved "https://registry.yarnpkg.com/date-fns-jalali/-/date-fns-jalali-2.19.0-2.tgz#69d35c6505749e9bce485c0e392604936e20cb2a" integrity sha512-ajgQt3lNFKPN7+mkXpWRJP+NyTVyHRJFVXUV8uc7d9pduVrDn/eeRMT5w1PemXqQRoNqQmuHyKWcrmNPmTRoPA== -date-fns@2.16.1: - version "2.16.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" - integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== +date-fns@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.2.0.tgz#c97cf685b62c829aa4ecba554e4a51768cf0bffc" + integrity sha512-E4KWKavANzeuusPi0jUjpuI22SURAznGkx7eZV+4i6x2A+IZxAMcajgkvuDAU1bg40+xuhW1zRdVIIM/4khuIg== dateformat@^3.0.0: version "3.0.3"