Skip to content
12 changes: 6 additions & 6 deletions packages/time/src/formatter/buildDateFormatter.ts
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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]
*/

/**
Expand All @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions packages/time/src/formatter/buildDateTimeFormatter.ts
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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]
*/

/**
Expand All @@ -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;
Expand Down
15 changes: 8 additions & 7 deletions packages/time/src/formatter/buildTimeFormatter.ts
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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]
*/

/**
Expand All @@ -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;
Expand All @@ -59,4 +59,5 @@ export function buildTimeFormatter({
...(timeStyle ? { timeStyle } : rest),
};
return new Intl.DateTimeFormat(locale, newOptions);
}
}

53 changes: 32 additions & 21 deletions packages/time/src/formatter/extractLocaleOptions.ts
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand All @@ -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<DateFormatterOptions | DateTimeFormatterOptions | TimeFormatterOptions>) {
return {
localeMatcher, calendar, numberingSystem, hour12, hourCycle, timeZone, formatOptions
};
}
localeMatcher,
calendar,
numberingSystem,
hour12,
hourCycle,
timeZone,
formatOptions,
}
}
29 changes: 13 additions & 16 deletions packages/time/src/formatter/shared.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface ILocaleFormatterOptions {
export interface LocaleFormatterOptions {
localeMatcher?: 'lookup' | 'best fit';
calendar?: string;
numberingSystem?: string;
Expand All @@ -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';
Expand All @@ -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';
Expand All @@ -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<string|Intl.Locale>;
options?: FormatStyle | IDateFormatterOptions;
interface FormatterBuildParams<TOptions extends LocaleFormatterOptions> {
locale?: string | Intl.Locale | Array<string | Intl.Locale>;
options?: FormatStyle | TOptions;
}

export interface ITimeFormatterBuildParams {
locale?: string | Intl.Locale | Array<string|Intl.Locale>;
options?: FormatStyle | ITimeFormatterOptions;
}
export type DateFormatterBuildParams = FormatterBuildParams<DateFormatterOptions>;
export type TimeFormatterBuildParams = FormatterBuildParams<TimeFormatterOptions>;
export type DateTimeFormatterBuildParams = FormatterBuildParams<DateTimeFormatterOptions>;

export interface IDateTimeFormatterBuildParams {
locale?: string | Intl.Locale | Array<string|Intl.Locale>;
options?: FormatStyle | IDateTimeFormatterOptions;
}
export type UnionKeys<T> = T extends T ? keyof T : never;
export type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
export type StrictUnion<T> = StrictUnionHelper<T, T>
40 changes: 29 additions & 11 deletions packages/time/src/tests/isValidDate.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
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()
}
})
})
9 changes: 3 additions & 6 deletions packages/time/src/utils/isValidDate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
export function isValidDate(date: unknown): date is Date {
return date instanceof Date && !isNaN(date.getTime());
}
2 changes: 1 addition & 1 deletion packages/time/src/utils/validateDate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}