Skip to content

Commit

Permalink
feat: Add support for formatting dates and times (#125)
Browse files Browse the repository at this point in the history
* feat: add support for formatting dates and times

* chore: test fallbacks and customisations

* docs: add examples and remarks to doc comments

* docs: specify minimum Node version under compatibility
  • Loading branch information
connor-baer authored Dec 15, 2022
1 parent abc61ce commit f3932b6
Show file tree
Hide file tree
Showing 20 changed files with 918 additions and 27 deletions.
53 changes: 40 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,56 @@
[![Coverage](https://img.shields.io/codecov/c/github/sumup-oss/intl-js)](https://codecov.io/gh/sumup-oss/intl-js) [![License](https://img.shields.io/github/license/sumup-oss/intl-js)](https://github.com/sumup-oss/intl-js/blob/main/LICENSE)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.1%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md)

Format 🔢 numbers and 💱currency values for any locale with the [ECMAScript Internationalization API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl).
Format 🔢 numbers, 💱 currency values, 📅 dates, and 🕘 times for any locale with the [ECMAScript Internationalization API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl).

</div>

## Table of contents

- [Introduction](#introduction)
- [Installation](#installation)
- [Usage](#usage)
- [API reference](https://github.com/sumup-oss/intl-js/wiki/Exports)
- [Code of Conduct](#code-of-conduct)
- [About SumUp](#about-sumup)

## Installation
## Introduction

[`@sumup/intl`](https://www.npmjs.com/package/@sumup/intl) needs to be installed as a dependency via the [Yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) package managers. The npm CLI ships with [Node](https://nodejs.org/en/). You can read how to install the Yarn CLI in [their documentation](https://yarnpkg.com/en/docs/install). `@sumup/intl` requires Node v10+.
`@sumup/intl` is a light abstraction layer on top of the [ECMAScript Internationalization API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl). In addition to a simplified API, it offers the following benefits:

Depending on your preference, run one of the following:
### Performance

```sh
# With Yarn
$ yarn add @sumup/intl
Creating instances of `Intl.*` formatters is an [expensive operation](https://blog.david-reess.de/posts/hBEx9w-on-number-formatting-and-performance). `@sumup/intl` solves this by [memoizing](https://github.com/formatjs/intl-format-cache) the `Intl` formatters with a cache key based on the arguments passed to the constructor.

### Compatibility

`@sumup/intl` works in [modern browsers](https://caniuse.com/mdn-javascript_builtins_intl_numberformat_numberformat,mdn-javascript_builtins_intl_datetimeformat_datetimeformat) as well as server runtimes with support for the `Intl` APIs (such as Node 10+[^1]). When the `Intl` APIs aren't (fully) available, `@sumup/intl` tries to gracefully degrade. Please refer to the [API reference](#api-reference) to learn how certain functions behave when the runtime doesn't support the necessary APIs. If you need to support legacy browsers, consider including [polyfills](https://formatjs.io/docs/polyfills/).

[^1]: [Node](https://nodejs.org/en/) supports the `Intl` APIs since v8, however, it includes only the English localizations up to v12. Node v13 and above support all locales. If you're unable to use Node v13+, you can either include [polyfills](https://formatjs.io/docs/polyfills/) or use a [custom Node build](https://nodejs.org/docs/latest-v8.x/api/intl.html#intl_options_for_building_node_js).

## Installation

[`@sumup/intl`](https://www.npmjs.com/package/@sumup/intl) can be installed as a dependency via the [npm](https://www.npmjs.com) or [Yarn](https://classic.yarnpkg.com) package managers. Depending on your preference, run one of the following:

```sh
# With npm
$ npm install @sumup/intl
```
npm install @sumup/intl

`@sumup/intl` wraps the [ECMAScript Internationalization API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) which is [supported by all modern browsers](https://caniuse.com/#search=NumberFormat) (note that [_formatToParts_](https://caniuse.com/#feat=mdn-javascript_builtins_intl_numberformat_formattoparts) is not supported by IE11). If you need to support older browsers, you need to include [a polyfill for the `Intl.NumberFormat` API](https://formatjs.io/docs/polyfills/intl-numberformat).
# With Yarn v1
yarn add @sumup/intl
```

[Node](https://nodejs.org/en/) supports the `Intl` API since v8, however, it includes only the English localizations up to v12. Node v13 and above support all locales. If you're unable to use Node v13+, you can either include [a polyfill for the `Intl.NumberFormat` API](https://formatjs.io/docs/polyfills/intl-numberformat) or use a [custom Node build](https://nodejs.org/docs/latest-v8.x/api/intl.html#intl_options_for_building_node_js).
`@sumup/intl` requires Node v10+.

## Usage

All functions exported by `@sumup/intl` share a similar interface such as the common [`locales`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locales_argument), [`options`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#options_argument), and [`currency`](https://en.wikipedia.org/wiki/ISO_4217) arguments. These are passed on almost unchanged to the [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) constructor and thus support the same values. If the `locales` argument is not provided or is undefined, the runtime's default locale is used. Please refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) for more details.
The functions exported by `@sumup/intl` share a similar interface such as the common [`locales`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locales_argument), [`options`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#options_argument), and [`currency`](https://en.wikipedia.org/wiki/ISO_4217) arguments. These are passed on almost unchanged to the [`Intl.*`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#constructor_properties) constructors and thus support the same values. If the `locales` argument is not provided or is undefined, the runtime's default locale is used. Please refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) for more details.

Each type of data can be formatted with three increasingly advanced functions:

1. The `format*` functions return the formatted value as a string and cover the most common use cases.
2. The `format*ToParts` functions return an array of objects representing the value in parts that can be used for custom locale-aware formatting. For example, part of the formatted value could be rendered in bold.
3. The `resolve*Format` functions return an object with properties reflecting the locale and collation options computed during initialization of the object. These options can be passed to other functions or libraries to format values, for example [`react-number-format`](https://www.npmjs.com/package/react-number-format).

## API reference

Expand All @@ -53,11 +70,21 @@ All functions exported by `@sumup/intl` share a similar interface such as the co
- [formatCurrencyToParts](https://github.com/sumup-oss/intl-js/wiki/Exports#formatcurrencytoparts)
- [resolveCurrencyFormat](https://github.com/sumup-oss/intl-js/wiki/Exports#resolvecurrencyformat)

### Date & Time Functions

- [formatDate](https://github.com/sumup-oss/intl-js/wiki/Exports#formatdate)
- [formatTime](https://github.com/sumup-oss/intl-js/wiki/Exports#formattime)
- [formatDateTime](https://github.com/sumup-oss/intl-js/wiki/Exports#formatdatetime)
- [formatDateTimeToParts](https://github.com/sumup-oss/intl-js/wiki/Exports#formatdatetimetoparts)
- [resolveDateTimeFormat](https://github.com/sumup-oss/intl-js/wiki/Exports#resolvedatetimeformat)

### Variables

- [CURRENCIES](https://github.com/sumup-oss/intl-js/wiki/Exports#currencies)
- [isNumberFormatSupported](https://github.com/sumup-oss/intl-js/wiki/Exports#isnumberformatsupported)
- [isNumberFormatToPartsSupported](https://github.com/sumup-oss/intl-js/wiki/Exports#isnumberformattopartssupported)
- [isDateTimeFormatSupported](https://github.com/sumup-oss/intl-js/wiki/Exports#isdatetimeformatsupported)
- [isDateTimeFormatToPartsSupported](https://github.com/sumup-oss/intl-js/wiki/Exports#isdatetimeformattopartssupported)

## Code of Conduct

Expand All @@ -74,6 +101,6 @@ If you feel another member of the community violated our CoC or you are experien

![SumUp logo](https://raw.githubusercontent.com/sumup-oss/assets/master/sumup-logo.svg?sanitize=true)

[SumUp](https://sumup.com) is a mobile point-of-sale provider. It is our mission to make easy and fast card payments a reality across the _entire_ world. You can pay with SumUp in more than 30 countries, already. Our engineers work in Berlin, Cologne, Sofia, and Sāo Paulo. They write code in JavaScript, Swift, Ruby, Go, Java, Erlang, Elixir, and more.
[SumUp](https://sumup.com) is a mobile point-of-sale provider. It is our mission to make easy and fast card payments a reality across the _entire_ world. You can pay with SumUp in more than 30 countries, already. Our engineers work in Berlin, Cologne, Sofia, and Sāo Paulo. They write code in TypeScript, Swift, Ruby, Go, Java, Erlang, Elixir, and more.

Want to come work with us? [Head to our careers page](https://sumup.com/careers) to find out more.
4 changes: 3 additions & 1 deletion jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
jest.spyOn(Intl, 'NumberFormat');
jest.spyOn(Intl, 'DateTimeFormat');

// Apparently, Node.js doesn't implement this API.
// Apparently, Node.js doesn't implement these APIs.
Intl.NumberFormat.prototype.formatToParts = jest.fn();
Intl.DateTimeFormat.prototype.formatToParts = jest.fn();
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@sumup/intl",
"version": "0.0.0-semantically-released",
"description": "Format numbers and currency values for any locale with the ECMAScript Internationalization API",
"description": "Format numbers, currency values, dates, and times for any locale with the ECMAScript Internationalization API",
"repository": "[email protected]:sumup-oss/intl-js.git",
"author": "Connor Bär <[email protected]>",
"license": "Apache-2.0",
Expand Down
78 changes: 78 additions & 0 deletions src/data/date-time-styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright 2022, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

type NoUndefined<T> = T extends undefined ? never : T;

/**
* These options are an approximation of their respective `dateStyle` option
* based on the "en-US" locale.
*/
export const DATE_STYLES: Record<
NoUndefined<Intl.DateTimeFormatOptions['dateStyle']>,
Intl.DateTimeFormatOptions
> = {
short: {
day: 'numeric',
month: 'numeric',
year: '2-digit',
},
medium: {
day: 'numeric',
month: 'short',
year: 'numeric',
},
long: {
day: 'numeric',
month: 'long',
year: 'numeric',
},
full: {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
},
};

/**
* These options are an approximation of their respective `timeStyle` option
* based on the "en-US" locale.
*/
export const TIME_STYLES: Record<
NoUndefined<Intl.DateTimeFormatOptions['timeStyle']>,
Intl.DateTimeFormatOptions
> = {
short: {
hour: '2-digit',
minute: '2-digit',
},
medium: {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
},
long: {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short',
},
full: {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'long',
},
};
9 changes: 9 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,13 @@ export {
isNumberFormatToPartsSupported,
isIntlSupported,
} from './lib/number-format';
export {
formatDate,
formatTime,
formatDateTime,
formatDateTimeToParts,
resolveDateTimeFormat,
isDateTimeFormatSupported,
isDateTimeFormatToPartsSupported,
} from './lib/date-time-format';
export { CURRENCIES } from './data/currencies';
Loading

0 comments on commit f3932b6

Please sign in to comment.