2016-06-23 09:47:54 -07:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2015-07-04 22:25:43 +04:30
|
|
|
export enum NumberFormatStyle {
|
2015-08-26 13:40:12 -07:00
|
|
|
Decimal,
|
|
|
|
Percent,
|
2016-10-20 16:05:50 -07:00
|
|
|
Currency,
|
2015-07-04 22:25:43 +04:30
|
|
|
}
|
|
|
|
|
|
|
|
export class NumberFormatter {
|
2016-06-08 16:38:52 -07:00
|
|
|
static format(
|
|
|
|
num: number, locale: string, style: NumberFormatStyle,
|
2016-07-20 18:48:17 -07:00
|
|
|
{minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, currency,
|
2016-06-08 16:38:52 -07:00
|
|
|
currencyAsSymbol = false}: {
|
|
|
|
minimumIntegerDigits?: number,
|
|
|
|
minimumFractionDigits?: number,
|
|
|
|
maximumFractionDigits?: number,
|
2017-03-24 09:54:02 -07:00
|
|
|
currency?: string|null,
|
2016-06-08 16:38:52 -07:00
|
|
|
currencyAsSymbol?: boolean
|
|
|
|
} = {}): string {
|
2016-11-12 14:08:58 +01:00
|
|
|
const options: Intl.NumberFormatOptions = {
|
2016-07-20 18:48:17 -07:00
|
|
|
minimumIntegerDigits,
|
|
|
|
minimumFractionDigits,
|
|
|
|
maximumFractionDigits,
|
|
|
|
style: NumberFormatStyle[style].toLowerCase()
|
2015-07-04 22:25:43 +04:30
|
|
|
};
|
2016-07-20 18:48:17 -07:00
|
|
|
|
2015-08-26 13:40:12 -07:00
|
|
|
if (style == NumberFormatStyle.Currency) {
|
2017-03-24 09:54:02 -07:00
|
|
|
options.currency = typeof currency == 'string' ? currency : undefined;
|
2016-07-20 18:48:17 -07:00
|
|
|
options.currencyDisplay = currencyAsSymbol ? 'symbol' : 'code';
|
2015-07-04 22:25:43 +04:30
|
|
|
}
|
2016-07-20 18:48:17 -07:00
|
|
|
return new Intl.NumberFormat(locale, options).format(num);
|
2015-07-04 22:25:43 +04:30
|
|
|
}
|
|
|
|
}
|
2016-10-20 16:05:50 -07:00
|
|
|
|
|
|
|
type DateFormatterFn = (date: Date, locale: string) => string;
|
|
|
|
|
|
|
|
const DATE_FORMATS_SPLIT =
|
2016-07-07 18:47:30 +03:00
|
|
|
/((?:[^yMLdHhmsazZEwGjJ']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|J+|j+|m+|s+|a|z|Z|G+|w+))(.*)/;
|
2015-07-04 22:34:54 +04:30
|
|
|
|
2016-10-20 16:05:50 -07:00
|
|
|
const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
|
2016-11-27 20:23:14 -05:00
|
|
|
// Keys are quoted so they do not get renamed during closure compilation.
|
2016-11-21 14:45:48 -05:00
|
|
|
'yMMMdjms': datePartGetterFactory(combine([
|
2016-05-26 22:06:29 +03:00
|
|
|
digitCondition('year', 1),
|
|
|
|
nameCondition('month', 3),
|
|
|
|
digitCondition('day', 1),
|
|
|
|
digitCondition('hour', 1),
|
|
|
|
digitCondition('minute', 1),
|
|
|
|
digitCondition('second', 1),
|
|
|
|
])),
|
2016-11-21 14:45:48 -05:00
|
|
|
'yMdjm': datePartGetterFactory(combine([
|
2016-06-08 16:38:52 -07:00
|
|
|
digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1),
|
|
|
|
digitCondition('hour', 1), digitCondition('minute', 1)
|
2016-05-26 22:06:29 +03:00
|
|
|
])),
|
2016-11-21 14:45:48 -05:00
|
|
|
'yMMMMEEEEd': datePartGetterFactory(combine([
|
2016-06-08 16:38:52 -07:00
|
|
|
digitCondition('year', 1), nameCondition('month', 4), nameCondition('weekday', 4),
|
2016-05-26 22:06:29 +03:00
|
|
|
digitCondition('day', 1)
|
|
|
|
])),
|
2016-11-21 14:45:48 -05:00
|
|
|
'yMMMMd': datePartGetterFactory(
|
2016-05-26 22:06:29 +03:00
|
|
|
combine([digitCondition('year', 1), nameCondition('month', 4), digitCondition('day', 1)])),
|
2016-11-21 14:45:48 -05:00
|
|
|
'yMMMd': datePartGetterFactory(
|
2016-05-26 22:06:29 +03:00
|
|
|
combine([digitCondition('year', 1), nameCondition('month', 3), digitCondition('day', 1)])),
|
2016-11-21 14:45:48 -05:00
|
|
|
'yMd': datePartGetterFactory(
|
2016-05-26 22:06:29 +03:00
|
|
|
combine([digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1)])),
|
2016-11-21 14:45:48 -05:00
|
|
|
'jms': datePartGetterFactory(combine(
|
2016-05-26 22:06:29 +03:00
|
|
|
[digitCondition('hour', 1), digitCondition('second', 1), digitCondition('minute', 1)])),
|
2016-11-21 14:45:48 -05:00
|
|
|
'jm': datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
|
2016-05-26 22:06:29 +03:00
|
|
|
};
|
|
|
|
|
2016-10-20 16:05:50 -07:00
|
|
|
const DATE_FORMATS: {[format: string]: DateFormatterFn} = {
|
2016-11-27 20:23:14 -05:00
|
|
|
// Keys are quoted so they do not get renamed.
|
|
|
|
'yyyy': datePartGetterFactory(digitCondition('year', 4)),
|
|
|
|
'yy': datePartGetterFactory(digitCondition('year', 2)),
|
|
|
|
'y': datePartGetterFactory(digitCondition('year', 1)),
|
|
|
|
'MMMM': datePartGetterFactory(nameCondition('month', 4)),
|
|
|
|
'MMM': datePartGetterFactory(nameCondition('month', 3)),
|
|
|
|
'MM': datePartGetterFactory(digitCondition('month', 2)),
|
|
|
|
'M': datePartGetterFactory(digitCondition('month', 1)),
|
|
|
|
'LLLL': datePartGetterFactory(nameCondition('month', 4)),
|
|
|
|
'L': datePartGetterFactory(nameCondition('month', 1)),
|
|
|
|
'dd': datePartGetterFactory(digitCondition('day', 2)),
|
|
|
|
'd': datePartGetterFactory(digitCondition('day', 1)),
|
|
|
|
'HH': digitModifier(
|
2016-10-20 16:05:50 -07:00
|
|
|
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), false)))),
|
2016-11-27 20:23:14 -05:00
|
|
|
'H': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), false))),
|
|
|
|
'hh': digitModifier(
|
2016-10-20 16:05:50 -07:00
|
|
|
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), true)))),
|
2016-11-27 20:23:14 -05:00
|
|
|
'h': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
|
|
|
'jj': datePartGetterFactory(digitCondition('hour', 2)),
|
|
|
|
'j': datePartGetterFactory(digitCondition('hour', 1)),
|
|
|
|
'mm': digitModifier(datePartGetterFactory(digitCondition('minute', 2))),
|
|
|
|
'm': datePartGetterFactory(digitCondition('minute', 1)),
|
|
|
|
'ss': digitModifier(datePartGetterFactory(digitCondition('second', 2))),
|
|
|
|
's': datePartGetterFactory(digitCondition('second', 1)),
|
2016-05-26 22:06:29 +03:00
|
|
|
// while ISO 8601 requires fractions to be prefixed with `.` or `,`
|
|
|
|
// we can be just safely rely on using `sss` since we currently don't support single or two digit
|
|
|
|
// fractions
|
2016-11-27 20:23:14 -05:00
|
|
|
'sss': datePartGetterFactory(digitCondition('second', 3)),
|
|
|
|
'EEEE': datePartGetterFactory(nameCondition('weekday', 4)),
|
|
|
|
'EEE': datePartGetterFactory(nameCondition('weekday', 3)),
|
|
|
|
'EE': datePartGetterFactory(nameCondition('weekday', 2)),
|
|
|
|
'E': datePartGetterFactory(nameCondition('weekday', 1)),
|
|
|
|
'a': hourClockExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
|
|
|
'Z': timeZoneGetter('short'),
|
|
|
|
'z': timeZoneGetter('long'),
|
|
|
|
'ww': datePartGetterFactory({}), // Week of year, padded (00-53). Week 01 is the week with the
|
|
|
|
// first Thursday of the year. not support ?
|
|
|
|
'w':
|
|
|
|
datePartGetterFactory({}), // Week of year (0-53). Week 1 is the week with the first Thursday
|
2016-05-26 22:06:29 +03:00
|
|
|
// of the year not support ?
|
2016-11-27 20:23:14 -05:00
|
|
|
'G': datePartGetterFactory(nameCondition('era', 1)),
|
|
|
|
'GG': datePartGetterFactory(nameCondition('era', 2)),
|
|
|
|
'GGG': datePartGetterFactory(nameCondition('era', 3)),
|
|
|
|
'GGGG': datePartGetterFactory(nameCondition('era', 4))
|
2016-05-26 22:06:29 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2016-10-20 16:05:50 -07:00
|
|
|
function digitModifier(inner: DateFormatterFn): DateFormatterFn {
|
2016-06-18 19:03:58 +03:00
|
|
|
return function(date: Date, locale: string): string {
|
2016-10-20 16:05:50 -07:00
|
|
|
const result = inner(date, locale);
|
2016-06-18 19:03:58 +03:00
|
|
|
return result.length == 1 ? '0' + result : result;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-10-20 16:05:50 -07:00
|
|
|
function hourClockExtractor(inner: DateFormatterFn): DateFormatterFn {
|
|
|
|
return function(date: Date, locale: string): string { return inner(date, locale).split(' ')[1]; };
|
2015-07-04 22:34:54 +04:30
|
|
|
}
|
2016-05-26 22:06:29 +03:00
|
|
|
|
2016-10-20 16:05:50 -07:00
|
|
|
function hourExtractor(inner: DateFormatterFn): DateFormatterFn {
|
|
|
|
return function(date: Date, locale: string): string { return inner(date, locale).split(' ')[0]; };
|
2016-05-26 22:06:29 +03:00
|
|
|
}
|
|
|
|
|
2016-08-18 13:31:33 -07:00
|
|
|
function intlDateFormat(date: Date, locale: string, options: Intl.DateTimeFormatOptions): string {
|
|
|
|
return new Intl.DateTimeFormat(locale, options).format(date).replace(/[\u200e\u200f]/g, '');
|
|
|
|
}
|
|
|
|
|
2016-10-20 16:05:50 -07:00
|
|
|
function timeZoneGetter(timezone: string): DateFormatterFn {
|
2016-07-07 18:47:30 +03:00
|
|
|
// To workaround `Intl` API restriction for single timezone let format with 24 hours
|
2016-08-18 13:31:33 -07:00
|
|
|
const options = {hour: '2-digit', hour12: false, timeZoneName: timezone};
|
2016-07-07 18:47:30 +03:00
|
|
|
return function(date: Date, locale: string): string {
|
2016-08-18 13:31:33 -07:00
|
|
|
const result = intlDateFormat(date, locale, options);
|
2016-07-07 18:47:30 +03:00
|
|
|
// Then extract first 3 letters that related to hours
|
|
|
|
return result ? result.substring(3) : '';
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-06-08 16:38:52 -07:00
|
|
|
function hour12Modify(
|
|
|
|
options: Intl.DateTimeFormatOptions, value: boolean): Intl.DateTimeFormatOptions {
|
2016-05-26 22:06:29 +03:00
|
|
|
options.hour12 = value;
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
|
|
|
function digitCondition(prop: string, len: number): Intl.DateTimeFormatOptions {
|
2016-10-19 20:05:13 +03:00
|
|
|
const result: {[k: string]: string} = {};
|
|
|
|
result[prop] = len === 2 ? '2-digit' : 'numeric';
|
2016-05-26 22:06:29 +03:00
|
|
|
return result;
|
|
|
|
}
|
2016-10-19 20:05:13 +03:00
|
|
|
|
2016-05-26 22:06:29 +03:00
|
|
|
function nameCondition(prop: string, len: number): Intl.DateTimeFormatOptions {
|
2016-10-19 20:05:13 +03:00
|
|
|
const result: {[k: string]: string} = {};
|
|
|
|
if (len < 4) {
|
|
|
|
result[prop] = len > 1 ? 'short' : 'narrow';
|
|
|
|
} else {
|
|
|
|
result[prop] = 'long';
|
|
|
|
}
|
|
|
|
|
2016-05-26 22:06:29 +03:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function combine(options: Intl.DateTimeFormatOptions[]): Intl.DateTimeFormatOptions {
|
2016-10-20 16:05:50 -07:00
|
|
|
return (<any>Object).assign({}, ...options);
|
2015-07-04 22:34:54 +04:30
|
|
|
}
|
2016-05-26 22:06:29 +03:00
|
|
|
|
2016-10-20 16:05:50 -07:00
|
|
|
function datePartGetterFactory(ret: Intl.DateTimeFormatOptions): DateFormatterFn {
|
2016-08-18 13:31:33 -07:00
|
|
|
return (date: Date, locale: string): string => intlDateFormat(date, locale, ret);
|
2016-05-26 22:06:29 +03:00
|
|
|
}
|
|
|
|
|
2016-10-20 16:05:50 -07:00
|
|
|
const DATE_FORMATTER_CACHE = new Map<string, string[]>();
|
2016-05-26 22:06:29 +03:00
|
|
|
|
|
|
|
function dateFormatter(format: string, date: Date, locale: string): string {
|
2016-11-12 14:08:58 +01:00
|
|
|
const fn = PATTERN_ALIASES[format];
|
2016-05-26 22:06:29 +03:00
|
|
|
|
2016-10-20 16:05:50 -07:00
|
|
|
if (fn) return fn(date, locale);
|
2016-05-26 22:06:29 +03:00
|
|
|
|
2016-12-05 16:26:59 -02:00
|
|
|
const cacheKey = format;
|
|
|
|
let parts = DATE_FORMATTER_CACHE.get(cacheKey);
|
2016-10-20 16:05:50 -07:00
|
|
|
|
|
|
|
if (!parts) {
|
|
|
|
parts = [];
|
2017-03-24 09:54:02 -07:00
|
|
|
let match: RegExpExecArray|null;
|
2016-10-20 16:05:50 -07:00
|
|
|
DATE_FORMATS_SPLIT.exec(format);
|
2016-05-26 22:06:29 +03:00
|
|
|
|
2017-03-24 09:54:02 -07:00
|
|
|
let _format: string|null = format;
|
|
|
|
while (_format) {
|
|
|
|
match = DATE_FORMATS_SPLIT.exec(_format);
|
2016-05-26 22:06:29 +03:00
|
|
|
if (match) {
|
2016-10-20 16:05:50 -07:00
|
|
|
parts = parts.concat(match.slice(1));
|
2017-03-24 09:54:02 -07:00
|
|
|
_format = parts.pop() !;
|
2016-05-26 22:06:29 +03:00
|
|
|
} else {
|
2017-03-24 09:54:02 -07:00
|
|
|
parts.push(_format);
|
|
|
|
_format = null;
|
2016-05-26 22:06:29 +03:00
|
|
|
}
|
2015-07-04 22:34:54 +04:30
|
|
|
}
|
2016-05-26 22:06:29 +03:00
|
|
|
|
2016-12-05 16:26:59 -02:00
|
|
|
DATE_FORMATTER_CACHE.set(cacheKey, parts);
|
2015-07-04 22:34:54 +04:30
|
|
|
}
|
2016-05-26 22:06:29 +03:00
|
|
|
|
2016-10-20 16:05:50 -07:00
|
|
|
return parts.reduce((text, part) => {
|
2016-11-12 14:08:58 +01:00
|
|
|
const fn = DATE_FORMATS[part];
|
2016-10-20 16:05:50 -07:00
|
|
|
return text + (fn ? fn(date, locale) : partToTime(part));
|
|
|
|
}, '');
|
2015-07-04 22:34:54 +04:30
|
|
|
}
|
|
|
|
|
2016-10-20 16:05:50 -07:00
|
|
|
function partToTime(part: string): string {
|
|
|
|
return part === '\'\'' ? '\'' : part.replace(/(^'|'$)/g, '').replace(/''/g, '\'');
|
2016-05-26 22:06:29 +03:00
|
|
|
}
|
2015-07-04 22:34:54 +04:30
|
|
|
|
|
|
|
export class DateFormatter {
|
|
|
|
static format(date: Date, locale: string, pattern: string): string {
|
2016-05-26 22:06:29 +03:00
|
|
|
return dateFormatter(pattern, date, locale);
|
2015-07-04 22:34:54 +04:30
|
|
|
}
|
|
|
|
}
|