diff --git a/packages/time/src/formatter/buildDateFormatter.ts b/packages/time/src/formatter/buildDateFormatter.ts index 680fba2..b552078 100644 --- a/packages/time/src/formatter/buildDateFormatter.ts +++ b/packages/time/src/formatter/buildDateFormatter.ts @@ -1,9 +1,9 @@ import { getDefaultLocale } from "../utils/dateDefaults"; import { extractLocaleOptions } from "./extractLocaleOptions"; -import type { IDateFormatterBuildParams } from "./shared"; +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 { IDateFormatterBuildParams } 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] */ /** @@ -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 bc02c82..b96c625 100644 --- a/packages/time/src/formatter/buildDateTimeFormatter.ts +++ b/packages/time/src/formatter/buildDateTimeFormatter.ts @@ -1,9 +1,9 @@ import { getDefaultLocale } from "../utils/dateDefaults"; import { extractLocaleOptions } from "./extractLocaleOptions"; -import type { IDateTimeFormatterBuildParams } from "./shared"; +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 { IDateTimeFormatterBuildParams } 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] */ /** @@ -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 9ddee8d..3ea3a4c 100644 --- a/packages/time/src/formatter/buildTimeFormatter.ts +++ b/packages/time/src/formatter/buildTimeFormatter.ts @@ -1,9 +1,9 @@ import { getDefaultLocale } from "../utils/dateDefaults"; import { extractLocaleOptions } from "./extractLocaleOptions"; -import type { ITimeFormatterBuildParams } from "./shared"; +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 { ITimeFormatterBuildParams } 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,13 +44,13 @@ import type { ITimeFormatterBuildParams } 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({ 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; @@ -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 c7fed83..be50e99 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 { IDateFormatterOptions, IDateTimeFormatterOptions, ITimeFormatterOptions } 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 { IDateFormatterOptions, IDateTimeFormatterOptions, ITimeFormatterOp * @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 { IDateFormatterOptions, IDateTimeFormatterOptions, ITimeFormatterOp * @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 { IDateFormatterOptions, IDateTimeFormatterOptions, ITimeFormatterOp * @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 {IDateFormatterOptions | IDateTimeFormatterOptions | ITimeFormatterOptions} 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 -}: IDateFormatterOptions | IDateTimeFormatterOptions | ITimeFormatterOptions) { +}: 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 af409b9..8bbe07e 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,17 @@ export interface ITimeFormatterOptions extends ILocaleFormatterOptions { timeStyle?: FormatStyle; } -export interface IDateTimeFormatterOptions extends IDateFormatterOptions, ITimeFormatterOptions { -} +export type DateTimeFormatterOptions = DateFormatterOptions & TimeFormatterOptions -export interface IDateFormatterBuildParams { - locale?: string | Intl.Locale | Array; - options?: FormatStyle | IDateFormatterOptions; +interface FormatterBuildParams { + locale?: string | Intl.Locale | Array; + options?: FormatStyle | TOptions; } -export interface ITimeFormatterBuildParams { - locale?: string | Intl.Locale | Array; - options?: FormatStyle | ITimeFormatterOptions; -} +export type DateFormatterBuildParams = FormatterBuildParams; +export type TimeFormatterBuildParams = FormatterBuildParams; +export type DateTimeFormatterBuildParams = FormatterBuildParams; -export interface IDateTimeFormatterBuildParams { - locale?: string | Intl.Locale | Array; - options?: FormatStyle | IDateTimeFormatterOptions; -} \ 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 diff --git a/packages/time/src/tests/isValidDate.test.ts b/packages/time/src/tests/isValidDate.test.ts index 57a3556..4d01970 100644 --- a/packages/time/src/tests/isValidDate.test.ts +++ b/packages/time/src/tests/isValidDate.test.ts @@ -1,16 +1,34 @@ -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 { + expect(() => { + // @ts-expect-error + notADate.getTime() + }).toThrowError() + } + }) +}) diff --git a/packages/time/src/utils/isValidDate.ts b/packages/time/src/utils/isValidDate.ts index 987df31..c0d1599 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()); +} diff --git a/packages/time/src/utils/validateDate.ts b/packages/time/src/utils/validateDate.ts index 9416553..eb7556f 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