Skip to content

refactor: types for utils #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: v0
Choose a base branch
from
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;
}