From 169e06d83cef5116f0e39c74d2c497f04ba6278d Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 29 May 2024 16:55:35 +0200 Subject: [PATCH 1/8] refactor: update isValidDate return type for improved type safety --- packages/time/src/tests/isValidDate.test.ts | 38 +++++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/time/src/tests/isValidDate.test.ts b/packages/time/src/tests/isValidDate.test.ts index 57a35567..188c31cb 100644 --- a/packages/time/src/tests/isValidDate.test.ts +++ b/packages/time/src/tests/isValidDate.test.ts @@ -1,16 +1,32 @@ -import {describe, expect, test} from 'vitest'; -import {isValidDate} from '../utils/isValidDate'; +import { describe, expect, test } from 'vitest' +import { isValidDate } from '../utils/isValidDate' describe('isValidDate', () => { test('should return true for a valid date', () => { - expect(isValidDate(new Date())).toBe(true); - }); + const date = new Date(); + expect(isValidDate(date)).toBe(true) + }) - test('should return false for an invalid date', () => { - expect(isValidDate(new Date("invalid"))).toBe(false); - }); + test.each([ + '2021-10-10', + new Date('invalid'), + {}, + undefined, + null, + NaN, + 0, + ])('should return false for invalid date %p', (date) => { + expect(isValidDate(date)).toBe(false) + }) - test("should return false for null", () => { - expect(isValidDate(null)).toBe(false); - }); -}); \ No newline at end of file + test('should assert type guards correctly', () => { + const notADate = 'not a date'; + if (isValidDate(notADate)) { + expect(notADate).toBeInstanceOf(Date) + notADate.getDate() + } else { + // @ts-expect-error + notADate.getTime() + } + }) +}) From 75cb7b331a5c2ee41263f45509378197e9645b60 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 29 May 2024 16:55:47 +0200 Subject: [PATCH 2/8] refactor: update isValidDate return type for improved type safety --- packages/time/src/utils/isValidDate.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/time/src/utils/isValidDate.ts b/packages/time/src/utils/isValidDate.ts index 987df31a..c0d15991 100644 --- a/packages/time/src/utils/isValidDate.ts +++ b/packages/time/src/utils/isValidDate.ts @@ -4,9 +4,6 @@ * @param date Date * @returns boolean */ -export function isValidDate(date: any): boolean { - if (Object.prototype.toString.call(date) !== '[object Date]') { - return false; - } - return date.getTime() === date.getTime(); -} \ No newline at end of file +export function isValidDate(date: unknown): date is Date { + return date instanceof Date && !isNaN(date.getTime()); +} From 5dca8d5e763a5e7cb2879ae836fec9cd16070a89 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 30 May 2024 17:52:05 +0200 Subject: [PATCH 3/8] refactor: update isValidDate return type for improved type safety --- packages/time/src/tests/isValidDate.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/time/src/tests/isValidDate.test.ts b/packages/time/src/tests/isValidDate.test.ts index 188c31cb..4d019705 100644 --- a/packages/time/src/tests/isValidDate.test.ts +++ b/packages/time/src/tests/isValidDate.test.ts @@ -25,8 +25,10 @@ describe('isValidDate', () => { expect(notADate).toBeInstanceOf(Date) notADate.getDate() } else { - // @ts-expect-error - notADate.getTime() + expect(() => { + // @ts-expect-error + notADate.getTime() + }).toThrowError() } }) }) From 8c4a89269d7b975729b0685a0d8a200b212947fa Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 30 May 2024 19:44:41 +0200 Subject: [PATCH 4/8] refactor: types for utils --- .../time/src/formatter/buildDateFormatter.ts | 6 +++--- .../src/formatter/buildDateTimeFormatter.ts | 4 ++-- .../time/src/formatter/buildTimeFormatter.ts | 4 ++-- .../src/formatter/extractLocaleOptions.ts | 6 +++--- packages/time/src/formatter/shared.ts | 21 +++++++++---------- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/time/src/formatter/buildDateFormatter.ts b/packages/time/src/formatter/buildDateFormatter.ts index 680fba25..fa0bd6a0 100644 --- a/packages/time/src/formatter/buildDateFormatter.ts +++ b/packages/time/src/formatter/buildDateFormatter.ts @@ -1,6 +1,6 @@ import { getDefaultLocale } from "../utils/dateDefaults"; import { extractLocaleOptions } from "./extractLocaleOptions"; -import type { IDateFormatterBuildParams } from "./shared"; +import type { DateFormatterBuildParams } from "./shared"; /** * @typedef {Object} IDateFormatterOptions @@ -43,13 +43,13 @@ import type { IDateFormatterBuildParams } from "./shared"; * When using UTC date strings, it is suggested that you use the 'options' object * to set the 'timeZone' when building the formatter. The 'timeZone' is defaulted * to the user's browser timezone. - * @param {IDateFormatterBuildParams} [param0] + * @param {DateFormatterBuildParams} [param0] * @returns Intl.DateTimeFormat */ export function buildDateFormatter({ locale = getDefaultLocale(), options -}: IDateFormatterBuildParams = {}): Intl.DateTimeFormat { +}: DateFormatterBuildParams = {}): Intl.DateTimeFormat { const opts = (typeof options === 'string') ? { dateStyle: options } : options ?? {}; const {formatOptions = {}, ...localeOptions} = extractLocaleOptions(opts); const { dateStyle, ...rest } = formatOptions; diff --git a/packages/time/src/formatter/buildDateTimeFormatter.ts b/packages/time/src/formatter/buildDateTimeFormatter.ts index bc02c82e..73e6c06f 100644 --- a/packages/time/src/formatter/buildDateTimeFormatter.ts +++ b/packages/time/src/formatter/buildDateTimeFormatter.ts @@ -1,6 +1,6 @@ import { getDefaultLocale } from "../utils/dateDefaults"; import { extractLocaleOptions } from "./extractLocaleOptions"; -import type { IDateTimeFormatterBuildParams } from "./shared"; +import type { DateTimeFormatterBuildParams } from "./shared"; /** * @typedef {Object} IDateTimeFormatterOptions @@ -56,7 +56,7 @@ import type { IDateTimeFormatterBuildParams } from "./shared"; export function buildDateTimeFormatter({ locale = getDefaultLocale(), options -}: IDateTimeFormatterBuildParams): Intl.DateTimeFormat { +}: DateTimeFormatterBuildParams): Intl.DateTimeFormat { const opts = (typeof options === 'string') ? { dateStyle: options, timeStyle: options } : options ?? {}; const {formatOptions = {}, ...localeOptions} = extractLocaleOptions(opts); const { dateStyle, timeStyle, ...rest } = formatOptions; diff --git a/packages/time/src/formatter/buildTimeFormatter.ts b/packages/time/src/formatter/buildTimeFormatter.ts index 9ddee8d5..b0235bcc 100644 --- a/packages/time/src/formatter/buildTimeFormatter.ts +++ b/packages/time/src/formatter/buildTimeFormatter.ts @@ -1,6 +1,6 @@ import { getDefaultLocale } from "../utils/dateDefaults"; import { extractLocaleOptions } from "./extractLocaleOptions"; -import type { ITimeFormatterBuildParams } from "./shared"; +import type { TimeFormatterBuildParams } from "./shared"; /** * @typedef {Object} ITimeFormatterOptions @@ -50,7 +50,7 @@ import type { ITimeFormatterBuildParams } from "./shared"; export function buildTimeFormatter({ locale = getDefaultLocale(), options -}: ITimeFormatterBuildParams): Intl.DateTimeFormat { +}: TimeFormatterBuildParams): Intl.DateTimeFormat { const opts = (typeof options === 'string') ? { dateStyle: options } : options ?? {}; const {formatOptions = {}, ...localeOptions} = extractLocaleOptions(opts); const { timeStyle, ...rest } = formatOptions; diff --git a/packages/time/src/formatter/extractLocaleOptions.ts b/packages/time/src/formatter/extractLocaleOptions.ts index c7fed83a..843c3012 100644 --- a/packages/time/src/formatter/extractLocaleOptions.ts +++ b/packages/time/src/formatter/extractLocaleOptions.ts @@ -1,5 +1,5 @@ import { getDefaultCalendar, getDefaultTimeZone } from "../utils/dateDefaults"; -import type { IDateFormatterOptions, IDateTimeFormatterOptions, ITimeFormatterOptions } from "./shared"; +import type { DateFormatterOptions, DateTimeFormatterOptions, TimeFormatterOptions } from "./shared"; /** * @typedef {Object} IDateFormatterOptions @@ -63,7 +63,7 @@ import type { IDateFormatterOptions, IDateTimeFormatterOptions, ITimeFormatterOp * * If 'calender' or 'timeZone' are not provided, the default values provided by the * Intl.DateTimeFormat().resolvedOptions() are used. - * @param {IDateFormatterOptions | IDateTimeFormatterOptions | ITimeFormatterOptions} param0 + * @param {DateFormatterOptions | DateTimeFormatterOptions | TimeFormatterOptions} param0 * @returns */ export function extractLocaleOptions({ @@ -74,7 +74,7 @@ export function extractLocaleOptions({ hourCycle, timeZone = getDefaultTimeZone(), ...formatOptions -}: IDateFormatterOptions | IDateTimeFormatterOptions | ITimeFormatterOptions) { +}: DateFormatterOptions | DateTimeFormatterOptions | TimeFormatterOptions) { return { localeMatcher, calendar, numberingSystem, hour12, hourCycle, timeZone, formatOptions }; diff --git a/packages/time/src/formatter/shared.ts b/packages/time/src/formatter/shared.ts index af409b95..1bd4503f 100644 --- a/packages/time/src/formatter/shared.ts +++ b/packages/time/src/formatter/shared.ts @@ -1,4 +1,4 @@ -export interface ILocaleFormatterOptions { +export interface LocaleFormatterOptions { localeMatcher?: 'lookup' | 'best fit'; calendar?: string; numberingSystem?: string; @@ -9,7 +9,7 @@ export interface ILocaleFormatterOptions { type FormatStyle = 'full' | 'long' | 'medium' | 'short'; -export interface IDateFormatterOptions extends ILocaleFormatterOptions { +export interface DateFormatterOptions extends LocaleFormatterOptions { formatMatcher?: 'basic' | 'best fit'; weekday?: 'narrow' | 'short' | 'long'; era?: 'narrow' | 'short' | 'long'; @@ -20,7 +20,7 @@ export interface IDateFormatterOptions extends ILocaleFormatterOptions { dateStyle?: FormatStyle; } -export interface ITimeFormatterOptions extends ILocaleFormatterOptions { +export interface TimeFormatterOptions extends LocaleFormatterOptions { formatMatcher?: 'basic' | 'best fit'; dayPeriod?: 'narrow' | 'short' | 'long'; hour?: '2-digit' | 'numeric'; @@ -32,20 +32,19 @@ export interface ITimeFormatterOptions extends ILocaleFormatterOptions { timeStyle?: FormatStyle; } -export interface IDateTimeFormatterOptions extends IDateFormatterOptions, ITimeFormatterOptions { -} +export type DateTimeFormatterOptions = DateFormatterOptions & TimeFormatterOptions -export interface IDateFormatterBuildParams { +export interface DateFormatterBuildParams { locale?: string | Intl.Locale | Array; - options?: FormatStyle | IDateFormatterOptions; + options?: FormatStyle | DateFormatterOptions; } -export interface ITimeFormatterBuildParams { +export interface TimeFormatterBuildParams { locale?: string | Intl.Locale | Array; - options?: FormatStyle | ITimeFormatterOptions; + options?: FormatStyle | TimeFormatterOptions; } -export interface IDateTimeFormatterBuildParams { +export interface DateTimeFormatterBuildParams { locale?: string | Intl.Locale | Array; - options?: FormatStyle | IDateTimeFormatterOptions; + options?: FormatStyle | DateTimeFormatterOptions; } \ No newline at end of file From 3b2015d11ea11a339a0439916dc271883c095190 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 30 May 2024 19:55:34 +0200 Subject: [PATCH 5/8] refactor: types for utils --- .../time/src/formatter/buildDateFormatter.ts | 6 +-- .../src/formatter/buildDateTimeFormatter.ts | 6 +-- .../time/src/formatter/buildTimeFormatter.ts | 11 ++-- .../src/formatter/extractLocaleOptions.ts | 53 +++++++++++-------- packages/time/src/formatter/shared.ts | 6 ++- 5 files changed, 49 insertions(+), 33 deletions(-) diff --git a/packages/time/src/formatter/buildDateFormatter.ts b/packages/time/src/formatter/buildDateFormatter.ts index fa0bd6a0..b552078b 100644 --- a/packages/time/src/formatter/buildDateFormatter.ts +++ b/packages/time/src/formatter/buildDateFormatter.ts @@ -3,7 +3,7 @@ import { extractLocaleOptions } from "./extractLocaleOptions"; import type { DateFormatterBuildParams } from "./shared"; /** - * @typedef {Object} IDateFormatterOptions + * @typedef {Object} DateFormatterOptions * @property {string} [localeMatcher] * @property {string} [calendar] * @property {string} [numberingSystem] @@ -20,9 +20,9 @@ import type { DateFormatterBuildParams } from "./shared"; * * @typedef {string | Intl.Locale | string[] | Intl.Locale[]} Locale * - * @typedef {Object} IDateFormatterBuildParams + * @typedef {Object} DateFormatterBuildParams * @property {Locale} [locale] - * @property {string | IDateFormatterOptions} [options] + * @property {string | DateFormatterOptions} [options] */ /** diff --git a/packages/time/src/formatter/buildDateTimeFormatter.ts b/packages/time/src/formatter/buildDateTimeFormatter.ts index 73e6c06f..b96c6252 100644 --- a/packages/time/src/formatter/buildDateTimeFormatter.ts +++ b/packages/time/src/formatter/buildDateTimeFormatter.ts @@ -3,7 +3,7 @@ import { extractLocaleOptions } from "./extractLocaleOptions"; import type { DateTimeFormatterBuildParams } from "./shared"; /** - * @typedef {Object} IDateTimeFormatterOptions + * @typedef {Object} DateTimeFormatterOptions * @property {string} [localeMatcher] * @property {string} [calendar] * @property {string} [numberingSystem] @@ -27,9 +27,9 @@ import type { DateTimeFormatterBuildParams } from "./shared"; * * @typedef {string | Intl.Locale | string[] | Intl.Locale[]} Locale * - * @typedef {Object} IDateTimeFormatterOptions + * @typedef {Object} DateTimeFormatterOptions * @property {Locale} [locale] - * @property {string | IDateFormatterOptions} [options] + * @property {string | DateFormatterOptions} [options] */ /** diff --git a/packages/time/src/formatter/buildTimeFormatter.ts b/packages/time/src/formatter/buildTimeFormatter.ts index b0235bcc..3ea3a4c3 100644 --- a/packages/time/src/formatter/buildTimeFormatter.ts +++ b/packages/time/src/formatter/buildTimeFormatter.ts @@ -3,7 +3,7 @@ import { extractLocaleOptions } from "./extractLocaleOptions"; import type { TimeFormatterBuildParams } from "./shared"; /** - * @typedef {Object} ITimeFormatterOptions + * @typedef {Object} TimeFormatterOptions * @property {string} [localeMatcher] * @property {string} [calendar] * @property {string} [numberingSystem] @@ -21,9 +21,9 @@ import type { TimeFormatterBuildParams } from "./shared"; * * @typedef {string | Intl.Locale | string[] | Intl.Locale[]} Locale * - * @typedef {Object} ITimeFormatterBuildParams + * @typedef {Object} TimeFormatterBuildParams * @property {Locale} [locale] - * @property {string | IDateFormatterOptions} [options] + * @property {string | DateFormatterOptions} [options] */ /** @@ -44,7 +44,7 @@ import type { TimeFormatterBuildParams } from "./shared"; * When using UTC date strings, it is suggested that you use the 'options' object * to set the 'timeZone' when building the formatter. The 'timeZone' is defaulted * to the user's browser timezone. - * @param {IDateFormatterBuildParams} [param0] + * @param {DateFormatterBuildParams} [param0] * @returns Intl.DateTimeFormat */ export function buildTimeFormatter({ @@ -59,4 +59,5 @@ export function buildTimeFormatter({ ...(timeStyle ? { timeStyle } : rest), }; return new Intl.DateTimeFormat(locale, newOptions); -} \ No newline at end of file +} + diff --git a/packages/time/src/formatter/extractLocaleOptions.ts b/packages/time/src/formatter/extractLocaleOptions.ts index 843c3012..be50e999 100644 --- a/packages/time/src/formatter/extractLocaleOptions.ts +++ b/packages/time/src/formatter/extractLocaleOptions.ts @@ -1,8 +1,13 @@ -import { getDefaultCalendar, getDefaultTimeZone } from "../utils/dateDefaults"; -import type { DateFormatterOptions, DateTimeFormatterOptions, TimeFormatterOptions } from "./shared"; +import { getDefaultCalendar, getDefaultTimeZone } from '../utils/dateDefaults' +import type { + DateFormatterOptions, + DateTimeFormatterOptions, + StrictUnion, + TimeFormatterOptions, +} from './shared' /** - * @typedef {Object} IDateFormatterOptions + * @typedef {Object} DateFormatterOptions * @property {string} [localeMatcher] * @property {string} [calendar] * @property {string} [numberingSystem] @@ -16,8 +21,8 @@ import type { DateFormatterOptions, DateTimeFormatterOptions, TimeFormatterOptio * @property {string} [month] * @property {string} [day] * @property {string} [dateStyle] - * - * @typedef {Object} ITimeFormatterOptions + * + * @typedef {Object} TimeFormatterOptions * @property {string} [localeMatcher] * @property {string} [calendar] * @property {string} [numberingSystem] @@ -32,8 +37,8 @@ import type { DateFormatterOptions, DateTimeFormatterOptions, TimeFormatterOptio * @property {number} [fractionalSecondDigits] * @property {string} [timeZoneName] * @property {string} [timeStyle] - * - * @typedef {Object} IDateTimeFormatterOptions + * + * @typedef {Object} DateTimeFormatterOptions * @property {string} [localeMatcher] * @property {string} [calendar] * @property {string} [numberingSystem] @@ -56,26 +61,32 @@ import type { DateFormatterOptions, DateTimeFormatterOptions, TimeFormatterOptio * @property {string} [timeStyle] */ - /** +/** * Function: extractLocaleOptions * This function is used to extract the locale options from the 'options' parameter of the various * formatter 'build' functions. - * + * * If 'calender' or 'timeZone' are not provided, the default values provided by the * Intl.DateTimeFormat().resolvedOptions() are used. - * @param {DateFormatterOptions | DateTimeFormatterOptions | TimeFormatterOptions} param0 - * @returns + * @param {DateFormatterOptions | DateTimeFormatterOptions | TimeFormatterOptions} param0 + * @returns */ export function extractLocaleOptions({ - localeMatcher, - calendar = getDefaultCalendar(), - numberingSystem, - hour12, - hourCycle, - timeZone = getDefaultTimeZone(), + localeMatcher, + calendar = getDefaultCalendar(), + numberingSystem, + hour12, + hourCycle, + timeZone = getDefaultTimeZone(), ...formatOptions -}: DateFormatterOptions | DateTimeFormatterOptions | TimeFormatterOptions) { +}: StrictUnion) { return { - localeMatcher, calendar, numberingSystem, hour12, hourCycle, timeZone, formatOptions - }; -} \ No newline at end of file + localeMatcher, + calendar, + numberingSystem, + hour12, + hourCycle, + timeZone, + formatOptions, + } +} diff --git a/packages/time/src/formatter/shared.ts b/packages/time/src/formatter/shared.ts index 1bd4503f..5d45411c 100644 --- a/packages/time/src/formatter/shared.ts +++ b/packages/time/src/formatter/shared.ts @@ -47,4 +47,8 @@ export interface TimeFormatterBuildParams { export interface DateTimeFormatterBuildParams { locale?: string | Intl.Locale | Array; options?: FormatStyle | DateTimeFormatterOptions; -} \ No newline at end of file +} + +export type UnionKeys = T extends T ? keyof T : never; +export type StrictUnionHelper = T extends any ? T & Partial, keyof T>, never>> : never; +export type StrictUnion = StrictUnionHelper \ No newline at end of file From 389009155cc8c546d2efd2205976f722b4842324 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 30 May 2024 20:06:53 +0200 Subject: [PATCH 6/8] refactor: types for utils --- packages/time/src/utils/validateDate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/time/src/utils/validateDate.ts b/packages/time/src/utils/validateDate.ts index 9416553d..eb7556f9 100644 --- a/packages/time/src/utils/validateDate.ts +++ b/packages/time/src/utils/validateDate.ts @@ -19,5 +19,5 @@ export function validateDate({date, parse = parser, errorMessage = `Invalid Date if (!isValidDate(d)) { throw new Error(`${errorMessage}: "${date}"`); } - return d as Date; + return d; } \ No newline at end of file From 97c4f3fe258f2119597d27e58d251c8b0a00953e Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 3 Jun 2024 16:57:33 +0200 Subject: [PATCH 7/8] refactor: update formatter build params to improve type safety --- packages/time/src/formatter/shared.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/time/src/formatter/shared.ts b/packages/time/src/formatter/shared.ts index 5d45411c..1458b41c 100644 --- a/packages/time/src/formatter/shared.ts +++ b/packages/time/src/formatter/shared.ts @@ -34,20 +34,14 @@ export interface TimeFormatterOptions extends LocaleFormatterOptions { export type DateTimeFormatterOptions = DateFormatterOptions & TimeFormatterOptions -export interface DateFormatterBuildParams { - locale?: string | Intl.Locale | Array; - options?: FormatStyle | DateFormatterOptions; +interface FormatterBuildParams { + locale?: string | Intl.Locale | Array; + options?: FormatStyle | TOptions; } -export interface TimeFormatterBuildParams { - locale?: string | Intl.Locale | Array; - options?: FormatStyle | TimeFormatterOptions; -} - -export interface DateTimeFormatterBuildParams { - locale?: string | Intl.Locale | Array; - options?: FormatStyle | DateTimeFormatterOptions; -} +export type DateFormatterBuildParams = FormatterBuildParams; +export type TimeFormatterBuildParams = FormatterBuildParams; +export type DateTimeFormatterBuildParams = FormatterBuildParams; export type UnionKeys = T extends T ? keyof T : never; export type StrictUnionHelper = T extends any ? T & Partial, keyof T>, never>> : never; From 3a21d0c917c02ad1162df8f9eb6e7e5f21e1d16d Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 3 Jun 2024 16:59:47 +0200 Subject: [PATCH 8/8] refactor: update formatter build params to improve type safety --- packages/time/src/formatter/shared.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/time/src/formatter/shared.ts b/packages/time/src/formatter/shared.ts index 1458b41c..8bbe07e7 100644 --- a/packages/time/src/formatter/shared.ts +++ b/packages/time/src/formatter/shared.ts @@ -34,7 +34,7 @@ export interface TimeFormatterOptions extends LocaleFormatterOptions { export type DateTimeFormatterOptions = DateFormatterOptions & TimeFormatterOptions -interface FormatterBuildParams { +interface FormatterBuildParams { locale?: string | Intl.Locale | Array; options?: FormatStyle | TOptions; }