diff --git a/package.json b/package.json index 13726e500..76a566ad7 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "build": "lerna exec --stream --ignore @scaleway/eslint-* --ignore @scaleway/countries -- rollup -c ../../rollup.config.mjs", "build:profile": "cross-env PROFILE=true yarn run build", "commit": "npx git-cz -a", - "test": "jest", + "test": "TZ=UTC jest", "test:watch": "yarn run test --watch", "test:coverage": "yarn run test --coverage", "prepare": "husky install" diff --git a/packages/use-i18n/README.md b/packages/use-i18n/README.md index 3e3c0f19a..52f8b756c 100644 --- a/packages/use-i18n/README.md +++ b/packages/use-i18n/README.md @@ -160,6 +160,55 @@ const App = () => { } ``` +### formatDate + +This hook exposes a `formatDate` function which can be used to format JS dates + +The first parameter is anything that can be accepted as a valid JS Date (Date, number, string) + +It accepts an `options` as second parameter which can eiter be one of predefined shorthand formats (see below) or an [Intl.DateTimeFormat `options` object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat) + +Shorthand formats: +``` +"long" => "February 13, 2020" +"short" => (default) "Feb 13, 2020" +"hour" => "February 13, 2020, 4:28 PM" +"hourOnly" => "4:28 PM" +"shortWithoutDay" => "Feb 2020" +"numeric" => "2020-02-13" +"numericHour" => "2020-02-13 4:28 PM" +``` + +```js +import { useI18n } from '@scaleway/use-i18n' + +const App = () => { + const { formatDate } = useI18n() + + const units = [ + formatDate(new Date(2020, 1, 13, 16, 28)) // "Feb 13, 2020" + formatDate(1581607680000, 'long') // "February 13, 2020" + formatDate('2020-02-13T15:28:00.000Z', { + day: "numeric", + era: "short", + hour: "2-digit", + minute: "numeric", + month: "narrow", + second: "2-digit", + timeZoneName: "long", + weekday: "long", + year: "2-digit", + }) // "Thursday, F 13, 20 AD, 04:28:00 PM Central European Standard Time"" + ] + + return ( +
+ {units} +
+ ) +} +``` + ### formatUnit This hook also exposes a `formatUnit` function which can be used to format bits/bytes until [ECMA-402 Unit Preferences](https://github.com/tc39/proposal-smart-unit-preferences) is standardised @@ -175,15 +224,14 @@ It accepts an `options` as second parameter: ```js import { useI18n } from '@scaleway/use-i18n' -import { DateInput } from '@scaleway/ui' const App = () => { const { formatUnit } = useI18n() const units = [ - formatUnit(12 { unit: 'kilobyte' }) // "12 KB" or "12 Ko" in fr an ro - formatUnit(10 ** 8 { unit: 'bytes-humanized' }) // "100 MB" or "100 Mo" in fr an ro - formatUnit(10 ** 8 { unit: 'bits-per-second-humanized' }) // "100Mbs" + formatUnit(12, { unit: 'kilobyte' }) // "12 KB" or "12 Ko" in fr an ro + formatUnit(10 ** 8, { unit: 'bytes-humanized' }) // "100 MB" or "100 Mo" in fr an ro + formatUnit(10 ** 8, { unit: 'bits-per-second-humanized' }) // "100Mbs" ] return ( diff --git a/packages/use-i18n/src/__tests__/__snapshots__/formatDate.ts.snap b/packages/use-i18n/src/__tests__/__snapshots__/formatDate.ts.snap new file mode 100644 index 000000000..506ac38db --- /dev/null +++ b/packages/use-i18n/src/__tests__/__snapshots__/formatDate.ts.snap @@ -0,0 +1,247 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`formatDate should return passed object if not valid date 1`] = ` +Object { + "not": "a valid date", +} +`; + +exports[`formatDate should work with custom format and locale de 1`] = `"Donnerstag, 13. F 20 n. Chr., 16:28:00 Koordinierte Weltzeit"`; + +exports[`formatDate should work with custom format and locale de 2`] = `"Donnerstag, 13. F 20 n. Chr., 15:28:00 Koordinierte Weltzeit"`; + +exports[`formatDate should work with custom format and locale de 3`] = `"Donnerstag, 13. F 20 n. Chr., 15:28:00 Koordinierte Weltzeit"`; + +exports[`formatDate should work with custom format and locale en 1`] = `"Thursday, F 13, 20 AD, 04:28:00 PM Coordinated Universal Time"`; + +exports[`formatDate should work with custom format and locale en 2`] = `"Thursday, F 13, 20 AD, 03:28:00 PM Coordinated Universal Time"`; + +exports[`formatDate should work with custom format and locale en 3`] = `"Thursday, F 13, 20 AD, 03:28:00 PM Coordinated Universal Time"`; + +exports[`formatDate should work with custom format and locale es 1`] = `"jueves, 13 F 20 d. C. 16:28:00 (tiempo universal coordinado)"`; + +exports[`formatDate should work with custom format and locale es 2`] = `"jueves, 13 F 20 d. C. 15:28:00 (tiempo universal coordinado)"`; + +exports[`formatDate should work with custom format and locale es 3`] = `"jueves, 13 F 20 d. C. 15:28:00 (tiempo universal coordinado)"`; + +exports[`formatDate should work with custom format and locale fr 1`] = `"jeudi 13 F 20 ap. J.-C., 16:28:00 Temps universel coordonné"`; + +exports[`formatDate should work with custom format and locale fr 2`] = `"jeudi 13 F 20 ap. J.-C., 15:28:00 Temps universel coordonné"`; + +exports[`formatDate should work with custom format and locale fr 3`] = `"jeudi 13 F 20 ap. J.-C., 15:28:00 Temps universel coordonné"`; + +exports[`formatDate should work with custom format and locale ro 1`] = `"joi, 13 F 20 d.Hr., 16:28:00 Timpul universal coordonat"`; + +exports[`formatDate should work with custom format and locale ro 2`] = `"joi, 13 F 20 d.Hr., 15:28:00 Timpul universal coordonat"`; + +exports[`formatDate should work with custom format and locale ro 3`] = `"joi, 13 F 20 d.Hr., 15:28:00 Timpul universal coordonat"`; + +exports[`formatDate should work with format "hour", for date = "2020-02-13T15:28:00.000Z" and locale "de" 1`] = `"13. Februar 2020, 15:28"`; + +exports[`formatDate should work with format "hour", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"February 13, 2020, 3:28 PM"`; + +exports[`formatDate should work with format "hour", for date = "2020-02-13T15:28:00.000Z" and locale "es" 1`] = `"13 de febrero de 2020 15:28"`; + +exports[`formatDate should work with format "hour", for date = "2020-02-13T15:28:00.000Z" and locale "fr" 1`] = `"13 février 2020, 15:28"`; + +exports[`formatDate should work with format "hour", for date = "2020-02-13T15:28:00.000Z" and locale "ro" 1`] = `"13 februarie 2020, 15:28"`; + +exports[`formatDate should work with format "hour", for date = "1581607680000" and locale "de" 1`] = `"13. Februar 2020, 15:28"`; + +exports[`formatDate should work with format "hour", for date = "1581607680000" and locale "en" 1`] = `"February 13, 2020, 3:28 PM"`; + +exports[`formatDate should work with format "hour", for date = "1581607680000" and locale "es" 1`] = `"13 de febrero de 2020 15:28"`; + +exports[`formatDate should work with format "hour", for date = "1581607680000" and locale "fr" 1`] = `"13 février 2020, 15:28"`; + +exports[`formatDate should work with format "hour", for date = "1581607680000" and locale "ro" 1`] = `"13 februarie 2020, 15:28"`; + +exports[`formatDate should work with format "hour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "de" 1`] = `"13. Februar 2020, 16:28"`; + +exports[`formatDate should work with format "hour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"February 13, 2020, 4:28 PM"`; + +exports[`formatDate should work with format "hour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "es" 1`] = `"13 de febrero de 2020 16:28"`; + +exports[`formatDate should work with format "hour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "fr" 1`] = `"13 février 2020, 16:28"`; + +exports[`formatDate should work with format "hour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "ro" 1`] = `"13 februarie 2020, 16:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "2020-02-13T15:28:00.000Z" and locale "de" 1`] = `"15:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"3:28 PM"`; + +exports[`formatDate should work with format "hourOnly", for date = "2020-02-13T15:28:00.000Z" and locale "es" 1`] = `"15:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "2020-02-13T15:28:00.000Z" and locale "fr" 1`] = `"15:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "2020-02-13T15:28:00.000Z" and locale "ro" 1`] = `"15:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "1581607680000" and locale "de" 1`] = `"15:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "1581607680000" and locale "en" 1`] = `"3:28 PM"`; + +exports[`formatDate should work with format "hourOnly", for date = "1581607680000" and locale "es" 1`] = `"15:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "1581607680000" and locale "fr" 1`] = `"15:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "1581607680000" and locale "ro" 1`] = `"15:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "new Date(2020, 1, 13, 16, 28)" and locale "de" 1`] = `"16:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"4:28 PM"`; + +exports[`formatDate should work with format "hourOnly", for date = "new Date(2020, 1, 13, 16, 28)" and locale "es" 1`] = `"16:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "new Date(2020, 1, 13, 16, 28)" and locale "fr" 1`] = `"16:28"`; + +exports[`formatDate should work with format "hourOnly", for date = "new Date(2020, 1, 13, 16, 28)" and locale "ro" 1`] = `"16:28"`; + +exports[`formatDate should work with format "long", for date = "2020-02-13T15:28:00.000Z" and locale "de" 1`] = `"13. Februar 2020"`; + +exports[`formatDate should work with format "long", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"February 13, 2020"`; + +exports[`formatDate should work with format "long", for date = "2020-02-13T15:28:00.000Z" and locale "es" 1`] = `"13 de febrero de 2020"`; + +exports[`formatDate should work with format "long", for date = "2020-02-13T15:28:00.000Z" and locale "fr" 1`] = `"13 février 2020"`; + +exports[`formatDate should work with format "long", for date = "2020-02-13T15:28:00.000Z" and locale "ro" 1`] = `"13 februarie 2020"`; + +exports[`formatDate should work with format "long", for date = "1581607680000" and locale "de" 1`] = `"13. Februar 2020"`; + +exports[`formatDate should work with format "long", for date = "1581607680000" and locale "en" 1`] = `"February 13, 2020"`; + +exports[`formatDate should work with format "long", for date = "1581607680000" and locale "es" 1`] = `"13 de febrero de 2020"`; + +exports[`formatDate should work with format "long", for date = "1581607680000" and locale "fr" 1`] = `"13 février 2020"`; + +exports[`formatDate should work with format "long", for date = "1581607680000" and locale "ro" 1`] = `"13 februarie 2020"`; + +exports[`formatDate should work with format "long", for date = "new Date(2020, 1, 13, 16, 28)" and locale "de" 1`] = `"13. Februar 2020"`; + +exports[`formatDate should work with format "long", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"February 13, 2020"`; + +exports[`formatDate should work with format "long", for date = "new Date(2020, 1, 13, 16, 28)" and locale "es" 1`] = `"13 de febrero de 2020"`; + +exports[`formatDate should work with format "long", for date = "new Date(2020, 1, 13, 16, 28)" and locale "fr" 1`] = `"13 février 2020"`; + +exports[`formatDate should work with format "long", for date = "new Date(2020, 1, 13, 16, 28)" and locale "ro" 1`] = `"13 februarie 2020"`; + +exports[`formatDate should work with format "numeric", for date = "2020-02-13T15:28:00.000Z" and locale "de" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "2020-02-13T15:28:00.000Z" and locale "es" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "2020-02-13T15:28:00.000Z" and locale "fr" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "2020-02-13T15:28:00.000Z" and locale "ro" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "1581607680000" and locale "de" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "1581607680000" and locale "en" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "1581607680000" and locale "es" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "1581607680000" and locale "fr" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "1581607680000" and locale "ro" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "new Date(2020, 1, 13, 16, 28)" and locale "de" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "new Date(2020, 1, 13, 16, 28)" and locale "es" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "new Date(2020, 1, 13, 16, 28)" and locale "fr" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numeric", for date = "new Date(2020, 1, 13, 16, 28)" and locale "ro" 1`] = `"2020-02-13"`; + +exports[`formatDate should work with format "numericHour", for date = "2020-02-13T15:28:00.000Z" and locale "de" 1`] = `"2020-02-13 15:28"`; + +exports[`formatDate should work with format "numericHour", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"2020-02-13 3:28 PM"`; + +exports[`formatDate should work with format "numericHour", for date = "2020-02-13T15:28:00.000Z" and locale "es" 1`] = `"2020-02-13 15:28"`; + +exports[`formatDate should work with format "numericHour", for date = "2020-02-13T15:28:00.000Z" and locale "fr" 1`] = `"2020-02-13 15:28"`; + +exports[`formatDate should work with format "numericHour", for date = "2020-02-13T15:28:00.000Z" and locale "ro" 1`] = `"2020-02-13 15:28"`; + +exports[`formatDate should work with format "numericHour", for date = "1581607680000" and locale "de" 1`] = `"2020-02-13 15:28"`; + +exports[`formatDate should work with format "numericHour", for date = "1581607680000" and locale "en" 1`] = `"2020-02-13 3:28 PM"`; + +exports[`formatDate should work with format "numericHour", for date = "1581607680000" and locale "es" 1`] = `"2020-02-13 15:28"`; + +exports[`formatDate should work with format "numericHour", for date = "1581607680000" and locale "fr" 1`] = `"2020-02-13 15:28"`; + +exports[`formatDate should work with format "numericHour", for date = "1581607680000" and locale "ro" 1`] = `"2020-02-13 15:28"`; + +exports[`formatDate should work with format "numericHour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "de" 1`] = `"2020-02-13 16:28"`; + +exports[`formatDate should work with format "numericHour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"2020-02-13 4:28 PM"`; + +exports[`formatDate should work with format "numericHour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "es" 1`] = `"2020-02-13 16:28"`; + +exports[`formatDate should work with format "numericHour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "fr" 1`] = `"2020-02-13 16:28"`; + +exports[`formatDate should work with format "numericHour", for date = "new Date(2020, 1, 13, 16, 28)" and locale "ro" 1`] = `"2020-02-13 16:28"`; + +exports[`formatDate should work with format "short", for date = "2020-02-13T15:28:00.000Z" and locale "de" 1`] = `"13. Feb. 2020"`; + +exports[`formatDate should work with format "short", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"Feb 13, 2020"`; + +exports[`formatDate should work with format "short", for date = "2020-02-13T15:28:00.000Z" and locale "es" 1`] = `"13 feb 2020"`; + +exports[`formatDate should work with format "short", for date = "2020-02-13T15:28:00.000Z" and locale "fr" 1`] = `"13 févr. 2020"`; + +exports[`formatDate should work with format "short", for date = "2020-02-13T15:28:00.000Z" and locale "ro" 1`] = `"13 feb. 2020"`; + +exports[`formatDate should work with format "short", for date = "1581607680000" and locale "de" 1`] = `"13. Feb. 2020"`; + +exports[`formatDate should work with format "short", for date = "1581607680000" and locale "en" 1`] = `"Feb 13, 2020"`; + +exports[`formatDate should work with format "short", for date = "1581607680000" and locale "es" 1`] = `"13 feb 2020"`; + +exports[`formatDate should work with format "short", for date = "1581607680000" and locale "fr" 1`] = `"13 févr. 2020"`; + +exports[`formatDate should work with format "short", for date = "1581607680000" and locale "ro" 1`] = `"13 feb. 2020"`; + +exports[`formatDate should work with format "short", for date = "new Date(2020, 1, 13, 16, 28)" and locale "de" 1`] = `"13. Feb. 2020"`; + +exports[`formatDate should work with format "short", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"Feb 13, 2020"`; + +exports[`formatDate should work with format "short", for date = "new Date(2020, 1, 13, 16, 28)" and locale "es" 1`] = `"13 feb 2020"`; + +exports[`formatDate should work with format "short", for date = "new Date(2020, 1, 13, 16, 28)" and locale "fr" 1`] = `"13 févr. 2020"`; + +exports[`formatDate should work with format "short", for date = "new Date(2020, 1, 13, 16, 28)" and locale "ro" 1`] = `"13 feb. 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "2020-02-13T15:28:00.000Z" and locale "de" 1`] = `"Feb. 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "2020-02-13T15:28:00.000Z" and locale "en" 1`] = `"Feb 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "2020-02-13T15:28:00.000Z" and locale "es" 1`] = `"feb 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "2020-02-13T15:28:00.000Z" and locale "fr" 1`] = `"févr. 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "2020-02-13T15:28:00.000Z" and locale "ro" 1`] = `"feb. 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "1581607680000" and locale "de" 1`] = `"Feb. 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "1581607680000" and locale "en" 1`] = `"Feb 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "1581607680000" and locale "es" 1`] = `"feb 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "1581607680000" and locale "fr" 1`] = `"févr. 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "1581607680000" and locale "ro" 1`] = `"feb. 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "new Date(2020, 1, 13, 16, 28)" and locale "de" 1`] = `"Feb. 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "new Date(2020, 1, 13, 16, 28)" and locale "en" 1`] = `"Feb 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "new Date(2020, 1, 13, 16, 28)" and locale "es" 1`] = `"feb 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "new Date(2020, 1, 13, 16, 28)" and locale "fr" 1`] = `"févr. 2020"`; + +exports[`formatDate should work with format "shortWithoutDay", for date = "new Date(2020, 1, 13, 16, 28)" and locale "ro" 1`] = `"feb. 2020"`; diff --git a/packages/use-i18n/src/__tests__/formatDate.ts b/packages/use-i18n/src/__tests__/formatDate.ts new file mode 100644 index 000000000..8610ef1f1 --- /dev/null +++ b/packages/use-i18n/src/__tests__/formatDate.ts @@ -0,0 +1,76 @@ +import formatDate, { FormatDateOptions, supportedFormats } from '../formatDate' + +const locales = ['en', 'fr', 'de', 'ro', 'es'] + +const tests = [ + ...locales.map(locale => [ + ...supportedFormats.map(format => [ + format, + 'new Date(2020, 1, 13, 16, 28)', + locale, + new Date(2020, 1, 13, 16, 28), + ]) + ]), + ...locales.map(locale => [ + ...supportedFormats.map(format => [ + format, + '1581607680000', + locale, + 1581607680000, + ]) + ]), + ...locales.map(locale => [ + ...supportedFormats.map(format => [ + format, + '2020-02-13T15:28:00.000Z', + locale, + '2020-02-13T15:28:00.000Z', + ]) + ]), +].flat() + +describe('formatDate', () => { + test.each(tests)('should work with format "%s", for date = "%s" and locale "%s"', (format, _, locale, date) => { + expect( + formatDate(locale as string, date, format as FormatDateOptions), + ).toMatchSnapshot() + }) + + test.each(locales)('should work with custom format and locale %s', (locale) => { + const format = { + day: "numeric", + era: "short", + hour: "2-digit", + minute: "numeric", + month: "narrow", + second: "2-digit", + timeZoneName: "long", + weekday: "long", + year: "2-digit", + } + + expect( + formatDate(locale, new Date(2020, 1, 13, 16, 28), format), + ).toMatchSnapshot() + expect( + formatDate(locale, 1581607680000, format), + ).toMatchSnapshot() + expect( + formatDate(locale, '2020-02-13T15:28:00.000Z', format), + ).toMatchSnapshot() + }) + + test('should return passed object if not valid date', () => { + expect( + // @ts-expect-error we check a failing case + formatDate('fr', { not: 'a valid date' }), + ).toMatchSnapshot() + }) + + test('should throw if shorthand format is invalid', () => { + expect(() => + // @ts-expect-error we check a failing case + formatDate('fr', 1581607680000, 'not a valid format'), + ).toThrowError('format "not a valid format" should be one of hour, hourOnly, long, short, shortWithoutDay, numeric, numericHour or a valid Intl.DateTimeFormat options object') + }) +}) diff --git a/packages/use-i18n/src/__tests__/usei18n.tsx b/packages/use-i18n/src/__tests__/usei18n.tsx index cb7487d07..4b56e868c 100644 --- a/packages/use-i18n/src/__tests__/usei18n.tsx +++ b/packages/use-i18n/src/__tests__/usei18n.tsx @@ -522,6 +522,26 @@ describe('i18n hook', () => { ).toEqual('12 octets') }) + it('should formatDate', async () => { + const { result, waitForNextUpdate } = renderHook(() => useI18n(), { + wrapper: wrapper({ + defaultLocale: 'en', + }), + }) + + expect( + result.current.formatDate(new Date(2020, 1, 13, 16, 28), 'numericHour'), + ).toEqual('2020-02-13 4:28 PM') + act(() => { + result.current.switchLocale('fr') + }) + await waitForNextUpdate() + + expect( + result.current.formatDate(new Date(2020, 1, 13, 16, 28), 'numericHour'), + ).toEqual('2020-02-13 16:28') + }) + it('should load default datefns locales', async () => { const { result, waitForNextUpdate } = renderHook(() => useI18n(), { wrapper: wrapper({ diff --git a/packages/use-i18n/src/formatDate.ts b/packages/use-i18n/src/formatDate.ts new file mode 100644 index 000000000..032c6df6b --- /dev/null +++ b/packages/use-i18n/src/formatDate.ts @@ -0,0 +1,71 @@ +import { formatISO, intlFormat } from 'date-fns' + +const formatOptions = { + hour: { + // Expected output format: February 13, 2020, 4:28 PM + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + month: 'long', + year: 'numeric', + }, + hourOnly: { + // Expected output format: 4:28 PM + hour: 'numeric', + minute: 'numeric', + }, + long: { + // Expected output format: February 13, 2020 + day: 'numeric', + month: 'long', + year: 'numeric', + }, + short: { + // Expected output format: Feb 13, 2020 + day: 'numeric', + month: 'short', + year: 'numeric', + }, + shortWithoutDay: { + // Expected output format: Feb 2020 + month: 'short', + year: 'numeric', + }, +} + +const complexFormatOptions = { + // Expected output format: 2020-02-13 + numeric: (date: Date): string => formatISO(date, { representation: 'date' }), + // Expected output format: 2020-02-13 4:28 PM + numericHour: (date: Date, locale: string): string => `${formatISO(date, { representation: 'date' })} ${intlFormat(date, formatOptions.hourOnly as Intl.DateTimeFormatOptions, { locale })}`, +} + +export const supportedFormats = [...Object.keys(formatOptions), ...Object.keys(complexFormatOptions)] + +export type FormatDateOptions = keyof typeof formatOptions | keyof typeof complexFormatOptions | Intl.DateTimeFormatOptions + +const formatDate = ( + locale: string, + date: Date | number | string, + format: FormatDateOptions = 'short', +): string => { + if (typeof format === 'string' && !((`${format}` in formatOptions) || (`${format}` in complexFormatOptions))) { + throw new Error(`format "${format}" should be one of ${supportedFormats.join(', ')} or a valid Intl.DateTimeFormat options object`) + } + + const properDate: Date = typeof date === 'string' || typeof date === 'number' ? new Date(date) : date + + if (typeof format === 'string' && `${format}` in complexFormatOptions) { + return complexFormatOptions[format as keyof typeof complexFormatOptions](properDate, locale) + } + + const options = typeof format === 'string' ? formatOptions[format as keyof typeof formatOptions] : format + + if (properDate instanceof Date) { + return intlFormat(properDate, options as Intl.DateTimeFormatOptions, { locale }) + } + + return properDate +} + +export default formatDate diff --git a/packages/use-i18n/src/usei18n.tsx b/packages/use-i18n/src/usei18n.tsx index 6e68623e1..1d7dec2f8 100644 --- a/packages/use-i18n/src/usei18n.tsx +++ b/packages/use-i18n/src/usei18n.tsx @@ -14,9 +14,9 @@ import React, { } from 'react' import ReactDOM from 'react-dom' import 'intl-pluralrules' +import dateFormat, { FormatDateOptions } from './formatDate' import unitFormat, { FormatUnitOptions } from './formatUnit' - const LOCALE_ITEM_STORAGE = 'locale' type Translations = Record & { prefix?: string } @@ -59,6 +59,7 @@ interface Context { currentLocale: string dateFnsLocale?: Locale, datetime?: (date: Date | number, options?: Intl.DateTimeFormatOptions) => string, + formatDate?: (value: Date | number | string, options: FormatDateOptions) => string, formatList?: (listFormat: string[], options?: Intl.ListFormatOptions) => string, formatNumber?: (numb: number, options?: Intl.NumberFormatOptions) => string, formatUnit?: (value: number, options: FormatUnitOptions) => string, @@ -238,6 +239,12 @@ const I18nContextProvider = ({ [currentLocale], ) + const formatDate = useCallback( + (value: Date | number | string, options: FormatDateOptions) => + dateFormat(currentLocale, value, options), + [currentLocale], + ) + const datetime = useCallback( // intl-format-chache does not forwrad return types // eslint-disable-next-line @@ -309,6 +316,7 @@ const I18nContextProvider = ({ currentLocale, dateFnsLocale, datetime, + formatDate, formatList, formatNumber, formatUnit, @@ -327,6 +335,7 @@ const I18nContextProvider = ({ currentLocale, dateFnsLocale, datetime, + formatDate, formatList, formatNumber, formatUnit,