From 23ce36642303b4fa01389c9337ec332efe65f380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Connor=20B=C3=A4r?= Date: Thu, 10 Oct 2024 14:17:46 +0200 Subject: [PATCH 1/2] Allow Temporal objects --- .changeset/strong-trains-collect.md | 5 +++ src/lib/date-time-format/index.ts | 19 ++++++---- .../unsupported-intl-api.spec.ts.snap | 35 +++++++++++++++++++ .../tests/format-to-parts.spec.ts | 10 +++++- src/lib/date-time-format/tests/format.spec.ts | 31 ++++++++++++++-- src/lib/date-time-format/tests/shared.ts | 28 ++++++++++++++- .../tests/unsupported-intl-api.spec.ts | 17 ++++----- .../tests/unsupported-styles.spec.ts | 7 ++-- src/types/index.ts | 17 +++++++++ 9 files changed, 148 insertions(+), 21 deletions(-) create mode 100644 .changeset/strong-trains-collect.md create mode 100644 src/lib/date-time-format/tests/__snapshots__/unsupported-intl-api.spec.ts.snap diff --git a/.changeset/strong-trains-collect.md b/.changeset/strong-trains-collect.md new file mode 100644 index 0000000..d7f1288 --- /dev/null +++ b/.changeset/strong-trains-collect.md @@ -0,0 +1,5 @@ +--- +'@sumup-oss/intl': patch +--- + +Improved the types to allow passing Temporal objects to the date and time formatting functions. diff --git a/src/lib/date-time-format/index.ts b/src/lib/date-time-format/index.ts index ac22869..8e9ce2b 100644 --- a/src/lib/date-time-format/index.ts +++ b/src/lib/date-time-format/index.ts @@ -13,7 +13,12 @@ * limitations under the License. */ -import type { Locale } from '../../types'; +import type { + FormattableDate, + FormattableDateTime, + FormattableTime, + Locale, +} from '../../types'; import { DATE_STYLES, TIME_STYLES } from '../../data/date-time-styles'; import { @@ -79,7 +84,7 @@ export const formatDateTime = formatDateTimeFactory(); * @category Date & Time */ export function formatDate( - date: Date, // in UTC + date: FormattableDate, // in UTC locales?: Locale | Locale[], dateStyle: Intl.DateTimeFormatOptions['dateStyle'] = 'short', ) { @@ -111,7 +116,7 @@ export function formatDate( * @category Date & Time */ export function formatTime( - date: Date, // in UTC + date: FormattableTime, // in UTC locales?: Locale | Locale[], timeStyle: Intl.DateTimeFormatOptions['timeStyle'] = 'short', ) { @@ -119,7 +124,7 @@ export function formatTime( } function formatDateTimeFactory(): ( - date: Date, // in UTC + date: FormattableDateTime, // in UTC locales?: Locale | Locale[], options?: Intl.DateTimeFormatOptions, ) => string { @@ -143,10 +148,10 @@ function formatDateTimeFactory(): ( if (includeDate && includeTime) { return date.toLocaleString(); } - if (includeDate) { + if (includeDate && 'toLocaleDateString' in date) { return date.toLocaleDateString(); } - if (includeTime) { + if (includeTime && 'toLocaleTimeString' in date) { return date.toLocaleTimeString(); } return date.toLocaleString(); @@ -208,7 +213,7 @@ function formatDateTimeFactory(): ( export const formatDateTimeToParts = formatDateTimeToPartsFactory(); function formatDateTimeToPartsFactory(): ( - date: Date, // in UTC + date: FormattableDateTime, // in UTC locales?: Locale | Locale[], options?: Intl.DateTimeFormatOptions, ) => (Intl.DateTimeFormatPart | { type: 'date'; value: string })[] { diff --git a/src/lib/date-time-format/tests/__snapshots__/unsupported-intl-api.spec.ts.snap b/src/lib/date-time-format/tests/__snapshots__/unsupported-intl-api.spec.ts.snap new file mode 100644 index 0000000..d9bae3e --- /dev/null +++ b/src/lib/date-time-format/tests/__snapshots__/unsupported-intl-api.spec.ts.snap @@ -0,0 +1,35 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a 1899-12-31T00:00:00.000Z as a date 1`] = `"12/31/1899"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a 1899-12-31T00:00:00.000Z as a date time 1`] = `"12/31/1899, 12:00:00 AM"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a 1899-12-31T00:00:00.000Z as a time 1`] = `"12:00:00 AM"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.Instant{} as a date 1`] = `"1/1/1970, 12:00:00 AM"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.Instant{} as a date time 1`] = `"1/1/1970, 12:00:00 AM"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.Instant{} as a time 1`] = `"1/1/1970, 12:00:00 AM"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.PlainDate{} as a date 1`] = `"1/1/2024"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.PlainDate{} as a date time 1`] = `"1/1/2024"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.PlainDateTime{} as a date 1`] = `"1/1/2024, 12:00:00 AM"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.PlainDateTime{} as a date time 1`] = `"1/1/2024, 12:00:00 AM"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.PlainDateTime{} as a time 1`] = `"1/1/2024, 12:00:00 AM"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.PlainMonthDay{} as a date 1`] = `"1/1"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.PlainMonthDay{} as a date time 1`] = `"1/1"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.PlainTime{} as a date time 1`] = `"12:00:00 AM"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.PlainTime{} as a time 1`] = `"12:00:00 AM"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.PlainYearMonth{} as a date 1`] = `"1/2024"`; + +exports[`Dates & times > when Intl.DateTimeFormat is unsupported > should format a Temporal.PlainYearMonth{} as a date time 1`] = `"1/2024"`; diff --git a/src/lib/date-time-format/tests/format-to-parts.spec.ts b/src/lib/date-time-format/tests/format-to-parts.spec.ts index ed20769..b54d2ac 100644 --- a/src/lib/date-time-format/tests/format-to-parts.spec.ts +++ b/src/lib/date-time-format/tests/format-to-parts.spec.ts @@ -18,11 +18,19 @@ import { Intl } from 'temporal-polyfill'; import { formatDateTimeToParts } from '..'; -import { date, locales } from './shared'; +import { datetimes, locales } from './shared'; describe('Dates & times', () => { describe('formatDateTimeToParts', () => { it.each(locales)('should format a date for %o', (locale) => { + const date = datetimes[0]; + const actual = formatDateTimeToParts(date, locale); + expect(actual).toBeArray(); + expect(Intl.DateTimeFormat).toHaveBeenCalledWith(locale, undefined); + }); + + it.each(datetimes)('should format a %o', (date) => { + const locale = locales[0]; const actual = formatDateTimeToParts(date, locale); expect(actual).toBeArray(); expect(Intl.DateTimeFormat).toHaveBeenCalledWith(locale, undefined); diff --git a/src/lib/date-time-format/tests/format.spec.ts b/src/lib/date-time-format/tests/format.spec.ts index 6627be7..a7ff280 100644 --- a/src/lib/date-time-format/tests/format.spec.ts +++ b/src/lib/date-time-format/tests/format.spec.ts @@ -18,11 +18,19 @@ import { Intl } from 'temporal-polyfill'; import { formatDateTime, formatDate, formatTime } from '..'; -import { date, locales } from './shared'; +import { locales, dates, times, datetimes } from './shared'; describe('Dates & times', () => { describe('formatDateTime', () => { it.each(locales)('should format a date time for %o', (locale) => { + const date = datetimes[0]; + const actual = formatDateTime(date, locale); + expect(actual).toBeString(); + expect(Intl.DateTimeFormat).toHaveBeenCalledWith(locale, undefined); + }); + + it.each(datetimes)('should format a %o', (date) => { + const locale = locales[0]; const actual = formatDateTime(date, locale); expect(actual).toBeString(); expect(Intl.DateTimeFormat).toHaveBeenCalledWith(locale, undefined); @@ -31,6 +39,16 @@ describe('Dates & times', () => { describe('formatDate', () => { it.each(locales)('should format a date for %o', (locale) => { + const date = dates[0]; + const actual = formatDate(date, locale); + expect(actual).toBeString(); + expect(Intl.DateTimeFormat).toHaveBeenCalledWith(locale, { + dateStyle: 'short', + }); + }); + + it.each(dates)('should format a %o', (date) => { + const locale = locales[0]; const actual = formatDate(date, locale); expect(actual).toBeString(); expect(Intl.DateTimeFormat).toHaveBeenCalledWith(locale, { @@ -41,7 +59,16 @@ describe('Dates & times', () => { describe('formatTime', () => { it.each(locales)('should format a time for %o', (locale) => { - const actual = formatTime(date, locale); + const time = times[0]; + const actual = formatTime(time, locale); + expect(actual).toBeString(); + expect(Intl.DateTimeFormat).toHaveBeenCalledWith(locale, { + timeStyle: 'short', + }); + }); + it.each(times)('should format a %o', (time) => { + const locale = locales[0]; + const actual = formatTime(time, locale); expect(actual).toBeString(); expect(Intl.DateTimeFormat).toHaveBeenCalledWith(locale, { timeStyle: 'short', diff --git a/src/lib/date-time-format/tests/shared.ts b/src/lib/date-time-format/tests/shared.ts index 471b37b..4e02b0a 100644 --- a/src/lib/date-time-format/tests/shared.ts +++ b/src/lib/date-time-format/tests/shared.ts @@ -13,6 +13,8 @@ * limitations under the License. */ +import { Temporal } from 'temporal-polyfill'; + export const locales: (string | string[])[] = [ 'de-DE', 'es-US', @@ -20,4 +22,28 @@ export const locales: (string | string[])[] = [ ['de-DE', 'es-US'], ]; -export const date = new Date(Date.UTC(0, 0, 0, 0, 0, 0)); +export const dates = [ + new Date(Date.UTC(0, 0, 0, 0, 0, 0)), + new Temporal.Instant(BigInt(0)), + new Temporal.PlainDate(2024, 1, 1, 'gregory'), + new Temporal.PlainDateTime(2024, 1, 1, 0, 0, 0, 0, 0, 0, 'gregory'), + new Temporal.PlainYearMonth(2024, 1, 'gregory'), + new Temporal.PlainMonthDay(1, 1, 'gregory'), +]; + +export const times = [ + new Date(Date.UTC(0, 0, 0, 0, 0, 0)), + new Temporal.Instant(BigInt(0)), + new Temporal.PlainTime(0, 0, 0), + new Temporal.PlainDateTime(2024, 1, 1, 0, 0, 0, 0, 0, 0, 'gregory'), +]; + +export const datetimes = [ + new Date(Date.UTC(0, 0, 0, 0, 0, 0)), + new Temporal.Instant(BigInt(0)), + new Temporal.PlainDate(2024, 1, 1, 'gregory'), + new Temporal.PlainTime(0, 0, 0), + new Temporal.PlainDateTime(2024, 1, 1, 0, 0, 0, 0, 0, 0, 'gregory'), + new Temporal.PlainYearMonth(2024, 1, 'gregory'), + new Temporal.PlainMonthDay(1, 1, 'gregory'), +]; diff --git a/src/lib/date-time-format/tests/unsupported-intl-api.spec.ts b/src/lib/date-time-format/tests/unsupported-intl-api.spec.ts index c47475b..93de65e 100644 --- a/src/lib/date-time-format/tests/unsupported-intl-api.spec.ts +++ b/src/lib/date-time-format/tests/unsupported-intl-api.spec.ts @@ -21,7 +21,7 @@ import { resolveDateTimeFormat, } from '..'; -import { date } from './shared'; +import { dates, datetimes, times } from './shared'; vi.mock('../intl', async () => { const intl = await vi.importActual('../intl'); @@ -36,25 +36,26 @@ const locale = 'xx-XX'; describe('Dates & times', () => { describe('when Intl.DateTimeFormat is unsupported', () => { - it('should format a date', () => { + it.each(dates)('should format a %o as a date', (date) => { const actual = formatDateTime(date, locale, { dateStyle: 'short' }); - expect(actual).toMatchInlineSnapshot('"12/31/1899"'); + expect(actual).toMatchSnapshot(); }); - it('should format a time', () => { - const actual = formatDateTime(date, locale, { timeStyle: 'short' }); - expect(actual).toMatchInlineSnapshot('"12:00:00 AM"'); + it.each(times)('should format a %o as a time', (time) => { + const actual = formatDateTime(time, locale, { timeStyle: 'short' }); + expect(actual).toMatchSnapshot(); }); - it('should format a date time', () => { + it.each(datetimes)('should format a %o as a date time', (date) => { const actual = formatDateTime(date, locale, { dateStyle: 'short', timeStyle: 'short', }); - expect(actual).toMatchInlineSnapshot('"12/31/1899, 12:00:00 AM"'); + expect(actual).toMatchSnapshot(); }); it('should format a date time to a single literal part', () => { + const date = datetimes[0]; const parts = formatDateTimeToParts(date, locale); expect(parts).toHaveLength(1); expect(parts[0]).toHaveProperty('type', 'literal'); diff --git a/src/lib/date-time-format/tests/unsupported-styles.spec.ts b/src/lib/date-time-format/tests/unsupported-styles.spec.ts index f1d122e..c04b99e 100644 --- a/src/lib/date-time-format/tests/unsupported-styles.spec.ts +++ b/src/lib/date-time-format/tests/unsupported-styles.spec.ts @@ -18,7 +18,7 @@ import { Intl } from 'temporal-polyfill'; import { formatDateTime } from '..'; -import { date } from './shared'; +import { dates, datetimes, times } from './shared'; vi.mock('../intl', async () => { const intl = await vi.importActual('../intl'); @@ -30,6 +30,7 @@ const locale = 'xx-XX'; describe('Dates & times', () => { describe('when the `dateStyle` and `timeStyle` options are unsupported', () => { it('should fallback to an approximate date format', () => { + const date = dates[0]; const actual = formatDateTime(date, locale, { dateStyle: 'short' }); expect(actual).toBeString(); expect(Intl.DateTimeFormat).toHaveBeenCalledWith(locale, { @@ -40,7 +41,8 @@ describe('Dates & times', () => { }); it('should fallback to an approximate time format', () => { - const actual = formatDateTime(date, locale, { timeStyle: 'short' }); + const time = times[0]; + const actual = formatDateTime(time, locale, { timeStyle: 'short' }); expect(actual).toBeString(); expect(Intl.DateTimeFormat).toHaveBeenCalledWith(locale, { hour: '2-digit', @@ -49,6 +51,7 @@ describe('Dates & times', () => { }); it('should fallback to an approximate date time format', () => { + const date = datetimes[0]; const actual = formatDateTime(date, locale, { dateStyle: 'short', timeStyle: 'short', diff --git a/src/types/index.ts b/src/types/index.ts index d085fb3..297eb43 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -13,6 +13,8 @@ * limitations under the License. */ +import type { Temporal } from 'temporal-polyfill'; + export type Locale = string; export type Currency = string; @@ -24,6 +26,21 @@ export type NumberFormat = Intl.ResolvedNumberFormatOptions & { currencyPosition?: 'prefix' | 'suffix'; }; +export type FormattableTime = + | Date + | Temporal.Instant + | Temporal.PlainTime + | Temporal.PlainDateTime; +export type FormattableDate = + | Date + | Temporal.Instant + | Temporal.PlainDate + | Temporal.PlainDateTime + | Temporal.PlainYearMonth + | Temporal.PlainMonthDay; + +export type FormattableDateTime = FormattableDate | FormattableTime; + export interface CurrencyFormatOptions extends Intl.NumberFormatOptions { style: 'currency'; currency: Currency; From 0311b4700268391480cc286697c65ca57ffda649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Connor=20B=C3=A4r?= Date: Thu, 10 Oct 2024 14:23:33 +0200 Subject: [PATCH 2/2] Update src/types/index.ts --- src/types/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/index.ts b/src/types/index.ts index 297eb43..0fe61b2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -31,6 +31,7 @@ export type FormattableTime = | Temporal.Instant | Temporal.PlainTime | Temporal.PlainDateTime; + export type FormattableDate = | Date | Temporal.Instant