UNPKG

intl-relativeformat

Version:

Formats JavaScript dates to relative time strings.

205 lines (181 loc) 5.55 kB
/* Copyright (c) 2014, Yahoo! Inc. All rights reserved. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ /* jslint esnext: true */ import { default as IntlRelativeTimeFormat, FormattableUnit } from '@formatjs/intl-relativetimeformat'; import diff from './diff'; import { SUPPORTED_FIELD, STYLE, LocaleData } from './types'; declare global { namespace Intl { var RelativeTimeFormat: typeof IntlRelativeTimeFormat; } } // ----------------------------------------------------------------------------- const SUPPORTED_FIELDS = [ SUPPORTED_FIELD.second, SUPPORTED_FIELD.secondShort, SUPPORTED_FIELD.minute, SUPPORTED_FIELD.minuteShort, SUPPORTED_FIELD.hour, SUPPORTED_FIELD.hourShort, SUPPORTED_FIELD.day, SUPPORTED_FIELD.dayShort, SUPPORTED_FIELD.month, SUPPORTED_FIELD.monthShort, SUPPORTED_FIELD.year, SUPPORTED_FIELD.yearShort ]; // -- RelativeFormat ----------------------------------------------------------- export interface IntlRelativeFormatOptions { style?: STYLE; units?: SUPPORTED_FIELD; } interface IntlRelativeFormat { new ( locales?: string | string[], opts?: IntlRelativeFormatOptions ): IntlRelativeFormat; ( locales?: string | string[], opts?: IntlRelativeFormatOptions ): IntlRelativeFormat; thresholds: Record<string, number>; defaultLocale: string; __localeData__: Record<string, LocaleData>; __addLocaleData(...data: LocaleData[]): void; format( date: ConstructorParameters<typeof Date>[0], opts?: { now?: Date | number | null } ): string; resolvedOptions(): { locale: string; style: STYLE; units?: SUPPORTED_FIELD; }; } function isValidUnits(units?: SUPPORTED_FIELD): units is SUPPORTED_FIELD { if (!units || ~SUPPORTED_FIELDS.indexOf(units)) { return true; } if (typeof units === 'string') { var suggestion = /s$/.test(units) && units.substr(0, units.length - 1); if ( suggestion && ~SUPPORTED_FIELDS.indexOf(suggestion as SUPPORTED_FIELD) ) { throw new Error( `"${units}" is not a valid IntlRelativeFormat 'units' value, did you mean: ${suggestion}` ); } } throw new Error( `"${units}" is not a valid IntlRelativeFormat 'units' value, it must be one of: ${SUPPORTED_FIELDS.join( '", "' )}` ); } function resolveStyle(style?: IntlRelativeFormatOptions['style']) { // Default to "best fit" style. if (!style) { return STYLE.bestFit; } if (style === 'best fit' || style === 'numeric') { return style; } throw new Error( `"${style}" is not a valid IntlRelativeFormat 'style' value, it must be one of: 'best fit' or 'numeric'` ); } function selectUnits(diffReport: Record<SUPPORTED_FIELD, number>) { const fields = SUPPORTED_FIELDS.filter(function(field) { return field.indexOf('-short') < 1; }); let units = fields[0]; for (units of fields) { if (Math.abs(diffReport[units]) < RelativeFormat.thresholds[units]) { break; } } return units; } const RelativeFormat: IntlRelativeFormat = (( locales?: string | string[], options: IntlRelativeFormatOptions = {} ) => { const resolvedOptions = { style: resolveStyle(options.style), units: isValidUnits(options.units) && options.units }; const numeric = resolvedOptions.style === 'best fit' ? 'auto' : 'always'; const rtf = new Intl.RelativeTimeFormat(locales, { numeric }); return { format(date?: Date | number, options?: { now?: number | Date | null }) { const now = options && options.now !== undefined ? options.now === null ? 0 : options.now : Date.now(); if (date === undefined) { date = now; } // Determine if the `date` and optional `now` values are valid, and throw a // similar error to what `Intl.DateTimeFormat#format()` would throw. if (!isFinite(now as number)) { throw new RangeError( 'The `now` option provided to IntlRelativeFormat#format() is not ' + 'in valid range.' ); } if (!isFinite(date as number)) { throw new RangeError( 'The date value provided to IntlRelativeFormat#format() is not ' + 'in valid range.' ); } var diffReport = diff(now, date); var units = resolvedOptions.units || selectUnits(diffReport); var diffInUnits = diffReport[units]; const style = units.substring(units.length - 6, units.length) === '-short' ? 'narrow' : 'long'; const rtfUnit = units.replace('-short', '') as FormattableUnit; return new Intl.RelativeTimeFormat(locales, { numeric, style }).format(diffInUnits, rtfUnit); }, resolvedOptions() { return { locale: rtf.resolvedOptions().locale, style: resolvedOptions.style, units: resolvedOptions.units }; } }; }) as any; // Define public `defaultLocale` property which can be set by the developer, or // it will be set when the first RelativeFormat instance is created by // leveraging the resolved locale from `Intl`. RelativeFormat.defaultLocale = 'en'; RelativeFormat.thresholds = { second: 45, 'second-short': 45, // seconds to minute minute: 45, 'minute-short': 45, // minutes to hour hour: 22, 'hour-short': 22, // hours to day day: 26, 'day-short': 26, // days to month month: 11, 'month-short': 11 // months to year }; export default RelativeFormat;