diff --git a/aio/content/examples/i18n/src/app/app.locale_data.ts b/aio/content/examples/i18n/src/app/app.locale_data.ts
new file mode 100644
index 00000000000000..9129a68200eee2
--- /dev/null
+++ b/aio/content/examples/i18n/src/app/app.locale_data.ts
@@ -0,0 +1,6 @@
+// #docregion import-locale
+import { registerLocaleData } from '@angular/common';
+import localeFr from '@angular/common/i18n_data/locale_fr';
+
+registerLocaleData(localeFr);
+// #enddocregion import-locale
diff --git a/aio/content/examples/i18n/src/app/app.locale_data_extra.ts b/aio/content/examples/i18n/src/app/app.locale_data_extra.ts
new file mode 100644
index 00000000000000..312c73feecb055
--- /dev/null
+++ b/aio/content/examples/i18n/src/app/app.locale_data_extra.ts
@@ -0,0 +1,7 @@
+// #docregion import-locale-extra
+import { registerLocaleData } from '@angular/common';
+import localeEnGB from '@angular/common/i18n_data/locale_en-GB';
+import localeEnGBExtra from '@angular/common/i18n_data/extra/locale_en-GB';
+
+registerLocaleData(localeEnGB, localeEnGBExtra);
+// #enddocregion import-locale-extra
diff --git a/aio/content/examples/pipes/e2e-spec.ts b/aio/content/examples/pipes/e2e-spec.ts
index 0aa5f09a5785a2..9675b66367cc2f 100644
--- a/aio/content/examples/pipes/e2e-spec.ts
+++ b/aio/content/examples/pipes/e2e-spec.ts
@@ -28,7 +28,7 @@ describe('Pipes', function () {
it('should be able to toggle birthday formats', function () {
let birthDayEle = element(by.css('hero-birthday2 > p'));
- expect(birthDayEle.getText()).toEqual(`The hero's birthday is 4/15/1988`);
+ expect(birthDayEle.getText()).toEqual(`The hero's birthday is 4/15/88`);
let buttonEle = element(by.cssContainingText('hero-birthday2 > button', 'Toggle Format'));
expect(buttonEle.isDisplayed()).toBe(true);
buttonEle.click().then(function() {
diff --git a/aio/content/guide/browser-support.md b/aio/content/guide/browser-support.md
index e5e20cfaff00f0..8d796ff576d98b 100644
--- a/aio/content/guide/browser-support.md
+++ b/aio/content/guide/browser-support.md
@@ -347,7 +347,7 @@ Here are the features which may require additional polyfills:
- [Date](api/common/DatePipe), [currency](api/common/CurrencyPipe), [decimal](api/common/DecimalPipe) and [percent](api/common/PercentPipe) pipes
+ If you use the following deprecated i18n pipes: [date](api/common/DeprecatedDatePipe), [currency](api/common/DeprecatedCurrencyPipe), [decimal](api/common/DeprecatedDecimalPipe) and [percent](api/common/DeprecatedPercentPipe)
|
diff --git a/aio/content/guide/i18n.md b/aio/content/guide/i18n.md
index 19da44d0053e47..810bae6bdccfeb 100644
--- a/aio/content/guide/i18n.md
+++ b/aio/content/guide/i18n.md
@@ -40,6 +40,28 @@ You need to build and deploy a separate version of the application for each supp
{@a i18n-attribute}
+## i18n pipes
+
+Angular pipes can help you with internationalization: the `DatePipe`, `CurrencyPipe`, `DecimalPipe`
+and `PercentPipe` use locale data to format your data based on your `LOCALE_ID`.
+
+By default Angular only contains locale data for the language `en-US`, if you set the value of
+`LOCALE_ID` to another locale, you will have to import new locale data for this language:
+
+
+
+
+
+
+Note that the files in `@angular/common/i18n_data` contain most of the locale data that you will
+need, but some advanced formatting options might only be available in the extra dataset that you can
+import from `@angular/common/i18n_data/extra`:
+
+
+
+
+
+
## Mark text with the _i18n_ attribute
The Angular `i18n` attribute is a marker for translatable content.
diff --git a/aio/content/guide/pipes.md b/aio/content/guide/pipes.md
index 4c2a3db8ef2c6b..0445b942f0f7f7 100644
--- a/aio/content/guide/pipes.md
+++ b/aio/content/guide/pipes.md
@@ -46,24 +46,6 @@ Inside the interpolation expression, you flow the component's `birthday` value t
function on the right. All pipes work this way.
-
-
-
-
-The `Date` and `Currency` pipes need the *ECMAScript Internationalization API*.
-Safari and other older browsers don't support it. You can add support with a polyfill.
-
-
-
- <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>
-
-
-
-
-
-
-
-
## Built-in pipes
diff --git a/build.sh b/build.sh
index 71b476bb8d2bfb..293bcf729ee4a8 100755
--- a/build.sh
+++ b/build.sh
@@ -86,7 +86,7 @@ done
#######################################
isIgnoredDirectory() {
name=$(basename ${1})
- if [[ -f "${1}" || "${name}" == "src" || "${name}" == "test" || "${name}" == "integrationtest" ]]; then
+ if [[ -f "${1}" || "${name}" == "src" || "${name}" == "test" || "${name}" == "integrationtest" || "${name}" == "i18n_data" ]]; then
return 0
else
return 1
@@ -470,6 +470,11 @@ do
minify ${BUNDLES_DIR}
) 2>&1 | grep -v "as external dependency"
+
+ if [[ ${PACKAGE} == "common" ]]; then
+ echo "====== Copy i18n locale data"
+ rsync -a --exclude=*.d.ts --exclude=*.metadata.json ${OUT_DIR}/i18n_data/ ${NPM_DIR}/i18n_data
+ fi
else
echo "====== Copy ${PACKAGE} node tool"
rsync -a ${OUT_DIR}/ ${NPM_DIR}
diff --git a/packages/common/BUILD.bazel b/packages/common/BUILD.bazel
index cee67edccd871b..f1dcaea6c68717 100644
--- a/packages/common/BUILD.bazel
+++ b/packages/common/BUILD.bazel
@@ -5,6 +5,7 @@ ts_library(
name = "common",
srcs = glob(["**/*.ts"], exclude=[
"http/**",
+ "i18n/**",
"test/**",
"testing/**",
]),
diff --git a/packages/common/i18n_data/tsconfig-build.json b/packages/common/i18n_data/tsconfig-build.json
new file mode 100644
index 00000000000000..273fbb9b6e5583
--- /dev/null
+++ b/packages/common/i18n_data/tsconfig-build.json
@@ -0,0 +1,20 @@
+{
+"compilerOptions": {
+ "baseUrl": ".",
+ "declaration": true,
+ "stripInternal": true,
+ "experimentalDecorators": true,
+ "module": "es2015",
+ "moduleResolution": "node",
+ "outDir": "../../../dist/packages/common/i18n_data",
+ "paths": {
+ "@angular/common": ["../../../dist/packages/common"],
+ "@angular/core": ["../../../dist/packages/core"]
+ },
+ "sourceMap": true,
+ "inlineSources": true,
+ "target": "es5",
+ "skipLibCheck": true,
+ "lib": ["es2015", "dom"]
+ }
+}
diff --git a/packages/common/src/common.ts b/packages/common/src/common.ts
index 5e79a49ad185be..d6f7158680b9f7 100644
--- a/packages/common/src/common.ts
+++ b/packages/common/src/common.ts
@@ -12,11 +12,16 @@
* Entry point for all public APIs of the common package.
*/
export * from './location/index';
-export {NgLocaleLocalization, NgLocalization} from './localization';
+export {NgLocaleLocalization, NgLocalization} from './i18n/localization';
+export {Plural, LOCALE_DATA} from './i18n/locale_data';
+export {findLocaleData, registerLocaleData, NumberFormatStyle, FormStyle, Time, TranslationWidth, FormatWidth, NumberSymbol, WeekDay, getLocaleDayPeriods, getLocaleDayNames, getLocaleMonthNames, getLocaleId, getLocaleEraNames, getLocaleWeekEndRange, getLocaleFirstDayOfWeek, getLocaleDateFormat, getLocaleDateTimeFormat, getLocaleExtraDayPeriodRules, getLocaleExtraDayPeriods, getLocalePluralCase, getLocaleTimeFormat, getLocaleNumberSymbol, getLocaleNumberFormat, getLocaleCurrencyName, getLocaleCurrencySymbol} from './i18n/locale_data_api';
+export {AVAILABLE_LOCALES} from './i18n/available_locales';
+export {CURRENCIES} from './i18n/currencies';
export {parseCookieValue as ɵparseCookieValue} from './cookie';
export {CommonModule, DeprecatedI18NPipesModule} from './common_module';
export {NgClass, NgFor, NgForOf, NgForOfContext, NgIf, NgIfContext, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
export {DOCUMENT} from './dom_tokens';
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
+export {DeprecatedDatePipe, DeprecatedCurrencyPipe, DeprecatedDecimalPipe, DeprecatedPercentPipe} from './pipes/deprecated/index';
export {PLATFORM_BROWSER_ID as ɵPLATFORM_BROWSER_ID, PLATFORM_SERVER_ID as ɵPLATFORM_SERVER_ID, PLATFORM_WORKER_APP_ID as ɵPLATFORM_WORKER_APP_ID, PLATFORM_WORKER_UI_ID as ɵPLATFORM_WORKER_UI_ID, isPlatformBrowser, isPlatformServer, isPlatformWorkerApp, isPlatformWorkerUi} from './platform_id';
export {VERSION} from './version';
diff --git a/packages/common/src/common_module.ts b/packages/common/src/common_module.ts
index 4a75bb4f3911bb..dd7750f86fa68c 100644
--- a/packages/common/src/common_module.ts
+++ b/packages/common/src/common_module.ts
@@ -8,8 +8,9 @@
import {NgModule} from '@angular/core';
-import {COMMON_DEPRECATED_DIRECTIVES, COMMON_DIRECTIVES} from './directives/index';
-import {NgLocaleLocalization, NgLocalization} from './localization';
+import {COMMON_DIRECTIVES} from './directives/index';
+import {NgLocaleLocalization, NgLocalization} from './i18n/localization';
+import {COMMON_DEPRECATED_I18N_PIPES} from './pipes/deprecated/index';
import {COMMON_PIPES} from './pipes/index';
@@ -31,17 +32,10 @@ export class CommonModule {
}
/**
- * I18N pipes are being changed to move away from using the JS Intl API.
- *
- * The former pipes relying on the Intl API will be moved to this module while the `CommonModule`
- * will contain the new pipes that do not rely on Intl.
- *
- * As a first step this module is created empty to ease the migration.
- *
- * see https://github.com/angular/angular/pull/18284
+ * A module that contains the deprecated i18n pipes.
*
* @deprecated from v5
*/
-@NgModule({declarations: [], exports: []})
+@NgModule({declarations: [COMMON_DEPRECATED_I18N_PIPES], exports: [COMMON_DEPRECATED_I18N_PIPES]})
export class DeprecatedI18NPipesModule {
}
diff --git a/packages/common/src/directives/ng_plural.ts b/packages/common/src/directives/ng_plural.ts
index e014a5e9d22f81..f82479aa5f4b0c 100644
--- a/packages/common/src/directives/ng_plural.ts
+++ b/packages/common/src/directives/ng_plural.ts
@@ -8,7 +8,7 @@
import {Attribute, Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';
-import {NgLocalization, getPluralCategory} from '../localization';
+import {NgLocalization, getPluralCategory} from '../i18n/localization';
import {SwitchView} from './ng_switch';
diff --git a/packages/common/src/i18n/format_date.ts b/packages/common/src/i18n/format_date.ts
new file mode 100644
index 00000000000000..df2294a339ad83
--- /dev/null
+++ b/packages/common/src/i18n/format_date.ts
@@ -0,0 +1,602 @@
+/**
+ * @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
+ */
+
+import {FormStyle, FormatWidth, NumberSymbol, Time, TranslationWidth, getLocaleDateFormat, getLocaleDateTimeFormat, getLocaleDayNames, getLocaleDayPeriods, getLocaleEraNames, getLocaleExtraDayPeriodRules, getLocaleExtraDayPeriods, getLocaleId, getLocaleMonthNames, getLocaleNumberSymbol, getLocaleTimeFormat} from './locale_data_api';
+
+const NAMED_FORMATS: {[localeId: string]: {[format: string]: string}} = {};
+const DATE_FORMATS_SPLIT =
+ /((?:[^GyMLwWdEabBhHmsSzZO']+)|(?:'(?:[^']|'')*')|(?:G{1,5}|y{1,4}|M{1,5}|L{1,5}|w{1,2}|W{1}|d{1,2}|E{1,6}|a{1,5}|b{1,5}|B{1,5}|h{1,2}|H{1,2}|m{1,2}|s{1,2}|S{1,3}|z{1,4}|Z{1,5}|O{1,4}))([\s\S]*)/;
+
+enum ZoneWidth {
+ Short,
+ ShortGMT,
+ Long,
+ Extended
+}
+
+enum DateType {
+ FullYear,
+ Month,
+ Date,
+ Hours,
+ Minutes,
+ Seconds,
+ Milliseconds,
+ Day
+}
+
+enum TranslationType {
+ DayPeriods,
+ Days,
+ Months,
+ Eras
+}
+
+/**
+ * Transforms a date to a locale string based on a pattern and a timezone
+ *
+ * @internal
+ */
+export function formatDate(date: Date, format: string, locale: string, timezone?: string): string {
+ const namedFormat = getNamedFormat(locale, format);
+ format = namedFormat || format;
+
+ let parts: string[] = [];
+ let match;
+ while (format) {
+ match = DATE_FORMATS_SPLIT.exec(format);
+ if (match) {
+ parts = parts.concat(match.slice(1));
+ const part = parts.pop();
+ if (!part) {
+ break;
+ }
+ format = part;
+ } else {
+ parts.push(format);
+ break;
+ }
+ }
+
+ let dateTimezoneOffset = date.getTimezoneOffset();
+ if (timezone) {
+ dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
+ date = convertTimezoneToLocal(date, timezone, true);
+ }
+
+ let text = '';
+ parts.forEach(value => {
+ const dateFormatter = getDateFormatter(value);
+ text += dateFormatter ?
+ dateFormatter(date, locale, dateTimezoneOffset) :
+ value === '\'\'' ? '\'' : value.replace(/(^'|'$)/g, '').replace(/''/g, '\'');
+ });
+
+ return text;
+}
+
+function getNamedFormat(locale: string, format: string): string {
+ const localeId = getLocaleId(locale);
+ NAMED_FORMATS[localeId] = NAMED_FORMATS[localeId] || {};
+
+ if (NAMED_FORMATS[localeId][format]) {
+ return NAMED_FORMATS[localeId][format];
+ }
+
+ let formatValue = '';
+ switch (format) {
+ case 'shortDate':
+ formatValue = getLocaleDateFormat(locale, FormatWidth.Short);
+ break;
+ case 'mediumDate':
+ formatValue = getLocaleDateFormat(locale, FormatWidth.Medium);
+ break;
+ case 'longDate':
+ formatValue = getLocaleDateFormat(locale, FormatWidth.Long);
+ break;
+ case 'fullDate':
+ formatValue = getLocaleDateFormat(locale, FormatWidth.Full);
+ break;
+ case 'shortTime':
+ formatValue = getLocaleTimeFormat(locale, FormatWidth.Short);
+ break;
+ case 'mediumTime':
+ formatValue = getLocaleTimeFormat(locale, FormatWidth.Medium);
+ break;
+ case 'longTime':
+ formatValue = getLocaleTimeFormat(locale, FormatWidth.Long);
+ break;
+ case 'fullTime':
+ formatValue = getLocaleTimeFormat(locale, FormatWidth.Full);
+ break;
+ case 'short':
+ const shortTime = getNamedFormat(locale, 'shortTime');
+ const shortDate = getNamedFormat(locale, 'shortDate');
+ formatValue = formatDateTime(
+ getLocaleDateTimeFormat(locale, FormatWidth.Short), [shortTime, shortDate]);
+ break;
+ case 'medium':
+ const mediumTime = getNamedFormat(locale, 'mediumTime');
+ const mediumDate = getNamedFormat(locale, 'mediumDate');
+ formatValue = formatDateTime(
+ getLocaleDateTimeFormat(locale, FormatWidth.Medium), [mediumTime, mediumDate]);
+ break;
+ case 'long':
+ const longTime = getNamedFormat(locale, 'longTime');
+ const longDate = getNamedFormat(locale, 'longDate');
+ formatValue =
+ formatDateTime(getLocaleDateTimeFormat(locale, FormatWidth.Long), [longTime, longDate]);
+ break;
+ case 'full':
+ const fullTime = getNamedFormat(locale, 'fullTime');
+ const fullDate = getNamedFormat(locale, 'fullDate');
+ formatValue =
+ formatDateTime(getLocaleDateTimeFormat(locale, FormatWidth.Full), [fullTime, fullDate]);
+ break;
+ }
+ if (formatValue) {
+ NAMED_FORMATS[localeId][format] = formatValue;
+ }
+ return formatValue;
+}
+
+function formatDateTime(str: string, opt_values: string[]) {
+ if (opt_values) {
+ str = str.replace(/\{([^}]+)}/g, function(match, key) {
+ return (opt_values != null && key in opt_values) ? opt_values[key] : match;
+ });
+ }
+ return str;
+}
+
+function padNumber(
+ num: number, digits: number, minusSign = '-', trim?: boolean, negWrap?: boolean): string {
+ let neg = '';
+ if (num < 0 || (negWrap && num <= 0)) {
+ if (negWrap) {
+ num = -num + 1;
+ } else {
+ num = -num;
+ neg = minusSign;
+ }
+ }
+ let strNum = '' + num;
+ while (strNum.length < digits) strNum = '0' + strNum;
+ if (trim) {
+ strNum = strNum.substr(strNum.length - digits);
+ }
+ return neg + strNum;
+}
+
+/**
+ * Returns a date formatter that transforms a date into its locale digit representation
+ */
+function dateGetter(
+ name: DateType, size: number, offset: number = 0, trim = false,
+ negWrap = false): DateFormatter {
+ return function(date: Date, locale: string): string {
+ let part = getDatePart(name, date, size);
+ if (offset > 0 || part > -offset) {
+ part += offset;
+ }
+ if (name === DateType.Hours && part === 0 && offset === -12) {
+ part = 12;
+ }
+ return padNumber(
+ part, size, getLocaleNumberSymbol(locale, NumberSymbol.MinusSign), trim, negWrap);
+ };
+}
+
+function getDatePart(name: DateType, date: Date, size: number): number {
+ switch (name) {
+ case DateType.FullYear:
+ return date.getFullYear();
+ case DateType.Month:
+ return date.getMonth();
+ case DateType.Date:
+ return date.getDate();
+ case DateType.Hours:
+ return date.getHours();
+ case DateType.Minutes:
+ return date.getMinutes();
+ case DateType.Seconds:
+ return date.getSeconds();
+ case DateType.Milliseconds:
+ const div = size === 1 ? 100 : (size === 2 ? 10 : 1);
+ return Math.round(date.getMilliseconds() / div);
+ case DateType.Day:
+ return date.getDay();
+ default:
+ throw new Error(`Unknown DateType value "${name}".`);
+ }
+}
+
+/**
+ * Returns a date formatter that transforms a date into its locale string representation
+ */
+function dateStrGetter(
+ name: TranslationType, width: TranslationWidth, form: FormStyle = FormStyle.Format,
+ extended = false): DateFormatter {
+ return function(date: Date, locale: string): string {
+ return getDateTranslation(date, locale, name, width, form, extended);
+ };
+}
+
+/**
+ * Returns the locale translation of a date for a given form, type and width
+ */
+function getDateTranslation(
+ date: Date, locale: string, name: TranslationType, width: TranslationWidth, form: FormStyle,
+ extended: boolean) {
+ switch (name) {
+ case TranslationType.Months:
+ return getLocaleMonthNames(locale, form, width)[date.getMonth()];
+ case TranslationType.Days:
+ return getLocaleDayNames(locale, form, width)[date.getDay()];
+ case TranslationType.DayPeriods:
+ const currentHours = date.getHours();
+ const currentMinutes = date.getMinutes();
+ if (extended) {
+ const rules = getLocaleExtraDayPeriodRules(locale);
+ const dayPeriods = getLocaleExtraDayPeriods(locale, form, width);
+ let result;
+ rules.forEach((rule: Time | [Time, Time], index: number) => {
+ if (Array.isArray(rule)) {
+ // morning, afternoon, evening, night
+ const {hours: hoursFrom, minutes: minutesFrom} = rule[0];
+ const {hours: hoursTo, minutes: minutesTo} = rule[1];
+ if (currentHours >= hoursFrom && currentMinutes >= minutesFrom &&
+ (currentHours < hoursTo ||
+ (currentHours === hoursTo && currentMinutes < minutesTo))) {
+ result = dayPeriods[index];
+ }
+ } else { // noon or midnight
+ const {hours, minutes} = rule;
+ if (hours === currentHours && minutes === currentMinutes) {
+ result = dayPeriods[index];
+ }
+ }
+ });
+ if (result) {
+ return result;
+ }
+ }
+ // if no rules for the day periods, we use am/pm by default
+ return getLocaleDayPeriods(locale, form, width)[currentHours < 12 ? 0 : 1];
+ case TranslationType.Eras:
+ return getLocaleEraNames(locale, width)[date.getFullYear() <= 0 ? 0 : 1];
+ }
+}
+
+/**
+ * Returns a date formatter that transforms a date and an offset into a timezone with ISO8601 or
+ * GMT format depending on the width (eg: short = +0430, short:GMT = GMT+4, long = GMT+04:30,
+ * extended = +04:30)
+ */
+function timeZoneGetter(width: ZoneWidth): DateFormatter {
+ return function(date: Date, locale: string, offset: number) {
+ const zone = -1 * offset;
+ const minusSign = getLocaleNumberSymbol(locale, NumberSymbol.MinusSign);
+ const hours = zone > 0 ? Math.floor(zone / 60) : Math.ceil(zone / 60);
+ switch (width) {
+ case ZoneWidth.Short:
+ return ((zone >= 0) ? '+' : '') + padNumber(hours, 2, minusSign) +
+ padNumber(Math.abs(zone % 60), 2, minusSign);
+ case ZoneWidth.ShortGMT:
+ return 'GMT' + ((zone >= 0) ? '+' : '') + padNumber(hours, 1, minusSign);
+ case ZoneWidth.Long:
+ return 'GMT' + ((zone >= 0) ? '+' : '') + padNumber(hours, 2, minusSign) + ':' +
+ padNumber(Math.abs(zone % 60), 2, minusSign);
+ case ZoneWidth.Extended:
+ if (offset === 0) {
+ return 'Z';
+ } else {
+ return ((zone >= 0) ? '+' : '') + padNumber(hours, 2, minusSign) + ':' +
+ padNumber(Math.abs(zone % 60), 2, minusSign);
+ }
+ default:
+ throw new Error(`Unknown zone width "${width}"`);
+ }
+ };
+}
+
+const JANUARY = 0;
+const THURSDAY = 4;
+function getFirstThursdayOfYear(year: number) {
+ const firstDayOfYear = (new Date(year, JANUARY, 1)).getDay();
+ return new Date(
+ year, 0, 1 + ((firstDayOfYear <= THURSDAY) ? THURSDAY : THURSDAY + 7) - firstDayOfYear);
+}
+
+function getThursdayThisWeek(datetime: Date) {
+ return new Date(
+ datetime.getFullYear(), datetime.getMonth(),
+ datetime.getDate() + (THURSDAY - datetime.getDay()));
+}
+
+function weekGetter(size: number, monthBased = false): DateFormatter {
+ return function(date: Date, locale: string) {
+ let result;
+ if (monthBased) {
+ const nbDaysBefore1stDayOfMonth =
+ new Date(date.getFullYear(), date.getMonth(), 1).getDay() - 1;
+ const today = date.getDate();
+ result = 1 + Math.floor((today + nbDaysBefore1stDayOfMonth) / 7);
+ } else {
+ const firstThurs = getFirstThursdayOfYear(date.getFullYear());
+ const thisThurs = getThursdayThisWeek(date);
+ const diff = thisThurs.getTime() - firstThurs.getTime();
+ result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
+ }
+
+ return padNumber(result, size, getLocaleNumberSymbol(locale, NumberSymbol.MinusSign));
+ };
+}
+
+type DateFormatter = (date: Date, locale: string, offset?: number) => string;
+
+const DATE_FORMATS: {[format: string]: DateFormatter} = {};
+
+// Based on CLDR formats:
+// See complete list: http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
+// See also explanations: http://cldr.unicode.org/translation/date-time
+// TODO(ocombe): support all missing cldr formats: Y, U, Q, D, F, e, c, j, J, C, A, v, V, X, x
+function getDateFormatter(format: string): DateFormatter|null {
+ if (DATE_FORMATS[format]) {
+ return DATE_FORMATS[format];
+ }
+ let formatter;
+ switch (format) {
+ // Era name (AD/BC)
+ case 'G':
+ case 'GG':
+ case 'GGG':
+ formatter = dateStrGetter(TranslationType.Eras, TranslationWidth.Abbreviated);
+ break;
+ case 'GGGG':
+ formatter = dateStrGetter(TranslationType.Eras, TranslationWidth.Wide);
+ break;
+ case 'GGGGG':
+ formatter = dateStrGetter(TranslationType.Eras, TranslationWidth.Narrow);
+ break;
+
+ // 1 digit representation of the year, e.g. (AD 1 => 1, AD 199 => 199)
+ case 'y':
+ formatter = dateGetter(DateType.FullYear, 1, 0, false, true);
+ break;
+ // 2 digit representation of the year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
+ case 'yy':
+ formatter = dateGetter(DateType.FullYear, 2, 0, true, true);
+ break;
+ // 3 digit representation of the year, padded (000-999). (e.g. AD 2001 => 01, AD 2010 => 10)
+ case 'yyy':
+ formatter = dateGetter(DateType.FullYear, 3, 0, false, true);
+ break;
+ // 4 digit representation of the year (e.g. AD 1 => 0001, AD 2010 => 2010)
+ case 'yyyy':
+ formatter = dateGetter(DateType.FullYear, 4, 0, false, true);
+ break;
+
+ // Month of the year (1-12), numeric
+ case 'M':
+ case 'L':
+ formatter = dateGetter(DateType.Month, 1, 1);
+ break;
+ case 'MM':
+ case 'LL':
+ formatter = dateGetter(DateType.Month, 2, 1);
+ break;
+
+ // Month of the year (January, ...), string, format
+ case 'MMM':
+ formatter = dateStrGetter(TranslationType.Months, TranslationWidth.Abbreviated);
+ break;
+ case 'MMMM':
+ formatter = dateStrGetter(TranslationType.Months, TranslationWidth.Wide);
+ break;
+ case 'MMMMM':
+ formatter = dateStrGetter(TranslationType.Months, TranslationWidth.Narrow);
+ break;
+
+ // Month of the year (January, ...), string, standalone
+ case 'LLL':
+ formatter =
+ dateStrGetter(TranslationType.Months, TranslationWidth.Abbreviated, FormStyle.Standalone);
+ break;
+ case 'LLLL':
+ formatter =
+ dateStrGetter(TranslationType.Months, TranslationWidth.Wide, FormStyle.Standalone);
+ break;
+ case 'LLLLL':
+ formatter =
+ dateStrGetter(TranslationType.Months, TranslationWidth.Narrow, FormStyle.Standalone);
+ break;
+
+ // Week of the year (1, ... 52)
+ case 'w':
+ formatter = weekGetter(1);
+ break;
+ case 'ww':
+ formatter = weekGetter(2);
+ break;
+
+ // Week of the month (1, ...)
+ case 'W':
+ formatter = weekGetter(1, true);
+ break;
+
+ // Day of the month (1-31)
+ case 'd':
+ formatter = dateGetter(DateType.Date, 1);
+ break;
+ case 'dd':
+ formatter = dateGetter(DateType.Date, 2);
+ break;
+
+ // Day of the Week
+ case 'E':
+ case 'EE':
+ case 'EEE':
+ formatter = dateStrGetter(TranslationType.Days, TranslationWidth.Abbreviated);
+ break;
+ case 'EEEE':
+ formatter = dateStrGetter(TranslationType.Days, TranslationWidth.Wide);
+ break;
+ case 'EEEEE':
+ formatter = dateStrGetter(TranslationType.Days, TranslationWidth.Narrow);
+ break;
+ case 'EEEEEE':
+ formatter = dateStrGetter(TranslationType.Days, TranslationWidth.Short);
+ break;
+
+ // Generic period of the day (am-pm)
+ case 'a':
+ case 'aa':
+ case 'aaa':
+ formatter = dateStrGetter(TranslationType.DayPeriods, TranslationWidth.Abbreviated);
+ break;
+ case 'aaaa':
+ formatter = dateStrGetter(TranslationType.DayPeriods, TranslationWidth.Wide);
+ break;
+ case 'aaaaa':
+ formatter = dateStrGetter(TranslationType.DayPeriods, TranslationWidth.Narrow);
+ break;
+
+ // Extended period of the day (midnight, at night, ...), standalone
+ case 'b':
+ case 'bb':
+ case 'bbb':
+ formatter = dateStrGetter(
+ TranslationType.DayPeriods, TranslationWidth.Abbreviated, FormStyle.Standalone, true);
+ break;
+ case 'bbbb':
+ formatter = dateStrGetter(
+ TranslationType.DayPeriods, TranslationWidth.Wide, FormStyle.Standalone, true);
+ break;
+ case 'bbbbb':
+ formatter = dateStrGetter(
+ TranslationType.DayPeriods, TranslationWidth.Narrow, FormStyle.Standalone, true);
+ break;
+
+ // Extended period of the day (midnight, night, ...), standalone
+ case 'B':
+ case 'BB':
+ case 'BBB':
+ formatter = dateStrGetter(
+ TranslationType.DayPeriods, TranslationWidth.Abbreviated, FormStyle.Format, true);
+ break;
+ case 'BBBB':
+ formatter =
+ dateStrGetter(TranslationType.DayPeriods, TranslationWidth.Wide, FormStyle.Format, true);
+ break;
+ case 'BBBBB':
+ formatter = dateStrGetter(
+ TranslationType.DayPeriods, TranslationWidth.Narrow, FormStyle.Format, true);
+ break;
+
+ // Hour in AM/PM, (1-12)
+ case 'h':
+ formatter = dateGetter(DateType.Hours, 1, -12);
+ break;
+ case 'hh':
+ formatter = dateGetter(DateType.Hours, 2, -12);
+ break;
+
+ // Hour of the day (0-23)
+ case 'H':
+ formatter = dateGetter(DateType.Hours, 1);
+ break;
+ // Hour in day, padded (00-23)
+ case 'HH':
+ formatter = dateGetter(DateType.Hours, 2);
+ break;
+
+ // Minute of the hour (0-59)
+ case 'm':
+ formatter = dateGetter(DateType.Minutes, 1);
+ break;
+ case 'mm':
+ formatter = dateGetter(DateType.Minutes, 2);
+ break;
+
+ // Second of the minute (0-59)
+ case 's':
+ formatter = dateGetter(DateType.Seconds, 1);
+ break;
+ case 'ss':
+ formatter = dateGetter(DateType.Seconds, 2);
+ break;
+
+ // Fractional second padded (0-9)
+ case 'S':
+ formatter = dateGetter(DateType.Milliseconds, 1);
+ break;
+ case 'SS':
+ formatter = dateGetter(DateType.Milliseconds, 2);
+ break;
+ // = millisecond
+ case 'SSS':
+ formatter = dateGetter(DateType.Milliseconds, 3);
+ break;
+
+
+ // Timezone ISO8601 short format (-0430)
+ case 'Z':
+ case 'ZZ':
+ case 'ZZZ':
+ formatter = timeZoneGetter(ZoneWidth.Short);
+ break;
+ // Timezone ISO8601 extended format (-04:30)
+ case 'ZZZZZ':
+ formatter = timeZoneGetter(ZoneWidth.Extended);
+ break;
+
+ // Timezone GMT short format (GMT+4)
+ case 'O':
+ case 'OO':
+ case 'OOO':
+ // Should be location, but fallback to format O instead because we don't have the data yet
+ case 'z':
+ case 'zz':
+ case 'zzz':
+ formatter = timeZoneGetter(ZoneWidth.ShortGMT);
+ break;
+ // Timezone GMT long format (GMT+0430)
+ case 'OOOO':
+ case 'ZZZZ':
+ // Should be location, but fallback to format O instead because we don't have the data yet
+ case 'zzzz':
+ formatter = timeZoneGetter(ZoneWidth.Long);
+ break;
+ default:
+ return null;
+ }
+ DATE_FORMATS[format] = formatter;
+ return formatter;
+}
+
+function timezoneToOffset(timezone: string, fallback: number): number {
+ // Support: IE 9-11 only, Edge 13-15+
+ // IE/Edge do not "understand" colon (`:`) in timezone
+ timezone = timezone.replace(/:/g, '');
+ const requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
+ return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
+}
+
+function addDateMinutes(date: Date, minutes: number) {
+ date = new Date(date.getTime());
+ date.setMinutes(date.getMinutes() + minutes);
+ return date;
+}
+
+function convertTimezoneToLocal(date: Date, timezone: string, reverse: boolean): Date {
+ const reverseValue = reverse ? -1 : 1;
+ const dateTimezoneOffset = date.getTimezoneOffset();
+ const timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
+ return addDateMinutes(date, reverseValue * (timezoneOffset - dateTimezoneOffset));
+}
diff --git a/packages/common/src/i18n/format_number.ts b/packages/common/src/i18n/format_number.ts
new file mode 100644
index 00000000000000..71a6ff91b0b463
--- /dev/null
+++ b/packages/common/src/i18n/format_number.ts
@@ -0,0 +1,377 @@
+/**
+ * @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
+ */
+
+import {NumberFormatStyle, NumberSymbol, getLocaleNumberFormat, getLocaleNumberSymbol} from './locale_data_api';
+
+export const NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
+const MAX_DIGITS = 22;
+const DECIMAL_SEP = '.';
+const ZERO_CHAR = '0';
+const PATTERN_SEP = ';';
+const GROUP_SEP = ',';
+const DIGIT_CHAR = '#';
+const CURRENCY_CHAR = '¤';
+const PERCENT_CHAR = '%';
+
+/** @internal */
+export type FormatNumberRes = {
+ str: string | null,
+ error?: string
+};
+
+/**
+ * Transform a number to a locale string based on a style and a format
+ *
+ * @internal
+ */
+export function formatNumber(
+ value: number | string, locale: string, style: NumberFormatStyle, digitsInfo?: string | null,
+ currency: string | null = null): FormatNumberRes {
+ const res: FormatNumberRes = {str: null};
+ const format = getLocaleNumberFormat(locale, style);
+ let num;
+
+ // Convert strings to numbers
+ if (typeof value === 'string' && !isNaN(+value - parseFloat(value))) {
+ num = +value;
+ } else if (typeof value !== 'number') {
+ res.error = `${value} is not a number`;
+ return res;
+ } else {
+ num = value;
+ }
+
+ if (style === NumberFormatStyle.Percent) {
+ num = num * 100;
+ }
+
+ const numStr = Math.abs(num) + '';
+ const pattern = parseNumberFormat(format, getLocaleNumberSymbol(locale, NumberSymbol.MinusSign));
+ let formattedText = '';
+ let isZero = false;
+
+ if (!isFinite(num)) {
+ formattedText = getLocaleNumberSymbol(locale, NumberSymbol.Infinity);
+ } else {
+ const parsedNumber = parseNumber(numStr);
+
+ let minInt = pattern.minInt;
+ let minFraction = pattern.minFrac;
+ let maxFraction = pattern.maxFrac;
+
+ if (digitsInfo) {
+ const parts = digitsInfo.match(NUMBER_FORMAT_REGEXP);
+ if (parts === null) {
+ res.error = `${digitsInfo} is not a valid digit info`;
+ return res;
+ }
+ const minIntPart = parts[1];
+ const minFractionPart = parts[3];
+ const maxFractionPart = parts[5];
+ if (minIntPart != null) {
+ minInt = parseIntAutoRadix(minIntPart);
+ }
+ if (minFractionPart != null) {
+ minFraction = parseIntAutoRadix(minFractionPart);
+ }
+ if (maxFractionPart != null) {
+ maxFraction = parseIntAutoRadix(maxFractionPart);
+ } else if (minFractionPart != null && minFraction > maxFraction) {
+ maxFraction = minFraction;
+ }
+ }
+
+ roundNumber(parsedNumber, minFraction, maxFraction);
+
+ let digits = parsedNumber.digits;
+ let integerLen = parsedNumber.integerLen;
+ const exponent = parsedNumber.exponent;
+ let decimals = [];
+ isZero = digits.every(d => !d);
+
+ // pad zeros for small numbers
+ for (; integerLen < minInt; integerLen++) {
+ digits.unshift(0);
+ }
+
+ // pad zeros for small numbers
+ for (; integerLen < 0; integerLen++) {
+ digits.unshift(0);
+ }
+
+ // extract decimals digits
+ if (integerLen > 0) {
+ decimals = digits.splice(integerLen, digits.length);
+ } else {
+ decimals = digits;
+ digits = [0];
+ }
+
+ // format the integer digits with grouping separators
+ const groups = [];
+ if (digits.length >= pattern.lgSize) {
+ groups.unshift(digits.splice(-pattern.lgSize, digits.length).join(''));
+ }
+
+ while (digits.length > pattern.gSize) {
+ groups.unshift(digits.splice(-pattern.gSize, digits.length).join(''));
+ }
+
+ if (digits.length) {
+ groups.unshift(digits.join(''));
+ }
+
+ const currencyGroup = getLocaleNumberSymbol(locale, NumberSymbol.CurrencyGroup);
+ formattedText = groups.join(
+ currency && currencyGroup ? currencyGroup :
+ getLocaleNumberSymbol(locale, NumberSymbol.Group));
+
+ // append the decimal digits
+ if (decimals.length) {
+ formattedText += getLocaleNumberSymbol(locale, NumberSymbol.Decimal) + decimals.join('');
+ }
+
+ if (exponent) {
+ formattedText += getLocaleNumberSymbol(locale, NumberSymbol.Exponential) + '+' + exponent;
+ }
+ }
+
+ if (num < 0 && !isZero) {
+ formattedText = pattern.negPre + formattedText + pattern.negSuf;
+ } else {
+ formattedText = pattern.posPre + formattedText + pattern.posSuf;
+ }
+
+ if (style === NumberFormatStyle.Currency && currency !== null) {
+ res.str = formattedText.replace(new RegExp(CURRENCY_CHAR, 'g'), currency);
+ return res;
+ }
+
+ if (style === NumberFormatStyle.Percent) {
+ res.str = formattedText.replace(
+ new RegExp(PERCENT_CHAR, 'g'), getLocaleNumberSymbol(locale, NumberSymbol.PercentSign));
+ return res;
+ }
+
+ res.str = formattedText;
+ return res;
+}
+
+interface ParsedNumberFormat {
+ minInt: number;
+ // the minimum number of digits required in the fraction part of the number
+ minFrac: number;
+ // the maximum number of digits required in the fraction part of the number
+ maxFrac: number;
+ // the prefix for a positive number
+ posPre: string;
+ // the suffix for a positive number
+ posSuf: string;
+ // the prefix for a negative number (e.g. `-` or `(`))
+ negPre: string;
+ // the suffix for a negative number (e.g. `)`)
+ negSuf: string;
+ // number of digits in each group of separated digits
+ gSize: number;
+ // number of digits in the last group of digits before the decimal separator
+ lgSize: number;
+}
+
+function parseNumberFormat(format: string, minusSign = '-'): ParsedNumberFormat {
+ const p = {
+ minInt: 1,
+ minFrac: 0,
+ maxFrac: 0,
+ posPre: '',
+ posSuf: '',
+ negPre: '',
+ negSuf: '',
+ gSize: 0,
+ lgSize: 0
+ };
+
+ const patternParts = format.split(PATTERN_SEP);
+ const positive = patternParts[0];
+ const negative = patternParts[1];
+
+ const positiveParts = positive.indexOf(DECIMAL_SEP) !== -1 ?
+ positive.split(DECIMAL_SEP) :
+ [
+ positive.substring(0, positive.lastIndexOf(ZERO_CHAR) + 1),
+ positive.substring(positive.lastIndexOf(ZERO_CHAR) + 1)
+ ],
+ integer = positiveParts[0], fraction = positiveParts[1] || '';
+
+ p.posPre = integer.substr(0, integer.indexOf(DIGIT_CHAR));
+
+ for (let i = 0; i < fraction.length; i++) {
+ const ch = fraction.charAt(i);
+ if (ch === ZERO_CHAR) {
+ p.minFrac = p.maxFrac = i + 1;
+ } else if (ch === DIGIT_CHAR) {
+ p.maxFrac = i + 1;
+ } else {
+ p.posSuf += ch;
+ }
+ }
+
+ const groups = integer.split(GROUP_SEP);
+ p.gSize = groups[1] ? groups[1].length : 0;
+ p.lgSize = (groups[2] || groups[1]) ? (groups[2] || groups[1]).length : 0;
+
+ if (negative) {
+ const trunkLen = positive.length - p.posPre.length - p.posSuf.length,
+ pos = negative.indexOf(DIGIT_CHAR);
+
+ p.negPre = negative.substr(0, pos).replace(/'/g, '');
+ p.negSuf = negative.substr(pos + trunkLen).replace(/'/g, '');
+ } else {
+ p.negPre = minusSign + p.posPre;
+ p.negSuf = p.posSuf;
+ }
+
+ return p;
+}
+
+interface ParsedNumber {
+ // an array of digits containing leading zeros as necessary
+ digits: number[];
+ // the exponent for numbers that would need more than `MAX_DIGITS` digits in `d`
+ exponent: number;
+ // the number of the digits in `d` that are to the left of the decimal point
+ integerLen: number;
+}
+
+/**
+ * Parse a number (as a string)
+ * Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/
+ */
+function parseNumber(numStr: string): ParsedNumber {
+ let exponent = 0, digits, integerLen;
+ let i, j, zeros;
+
+ // Decimal point?
+ if ((integerLen = numStr.indexOf(DECIMAL_SEP)) > -1) {
+ numStr = numStr.replace(DECIMAL_SEP, '');
+ }
+
+ // Exponential form?
+ if ((i = numStr.search(/e/i)) > 0) {
+ // Work out the exponent.
+ if (integerLen < 0) integerLen = i;
+ integerLen += +numStr.slice(i + 1);
+ numStr = numStr.substring(0, i);
+ } else if (integerLen < 0) {
+ // There was no decimal point or exponent so it is an integer.
+ integerLen = numStr.length;
+ }
+
+ // Count the number of leading zeros.
+ for (i = 0; numStr.charAt(i) === ZERO_CHAR; i++) { /* empty */
+ }
+
+ if (i === (zeros = numStr.length)) {
+ // The digits are all zero.
+ digits = [0];
+ integerLen = 1;
+ } else {
+ // Count the number of trailing zeros
+ zeros--;
+ while (numStr.charAt(zeros) === ZERO_CHAR) zeros--;
+
+ // Trailing zeros are insignificant so ignore them
+ integerLen -= i;
+ digits = [];
+ // Convert string to array of digits without leading/trailing zeros.
+ for (j = 0; i <= zeros; i++, j++) {
+ digits[j] = +numStr.charAt(i);
+ }
+ }
+
+ // If the number overflows the maximum allowed digits then use an exponent.
+ if (integerLen > MAX_DIGITS) {
+ digits = digits.splice(0, MAX_DIGITS - 1);
+ exponent = integerLen - 1;
+ integerLen = 1;
+ }
+
+ return {digits, exponent, integerLen};
+}
+
+/**
+ * Round the parsed number to the specified number of decimal places
+ * This function changes the parsedNumber in-place
+ */
+function roundNumber(parsedNumber: ParsedNumber, minFrac: number, maxFrac: number) {
+ if (minFrac > maxFrac) {
+ throw new Error(
+ `The minimum number of digits after fraction (${minFrac}) is higher than the maximum (${maxFrac}).`);
+ }
+
+ let digits = parsedNumber.digits;
+ let fractionLen = digits.length - parsedNumber.integerLen;
+ const fractionSize = Math.min(Math.max(minFrac, fractionLen), maxFrac);
+
+ // The index of the digit to where rounding is to occur
+ let roundAt = fractionSize + parsedNumber.integerLen;
+ let digit = digits[roundAt];
+
+ if (roundAt > 0) {
+ // Drop fractional digits beyond `roundAt`
+ digits.splice(Math.max(parsedNumber.integerLen, roundAt));
+
+ // Set non-fractional digits beyond `roundAt` to 0
+ for (let j = roundAt; j < digits.length; j++) {
+ digits[j] = 0;
+ }
+ } else {
+ // We rounded to zero so reset the parsedNumber
+ fractionLen = Math.max(0, fractionLen);
+ parsedNumber.integerLen = 1;
+ digits.length = Math.max(1, roundAt = fractionSize + 1);
+ digits[0] = 0;
+ for (let i = 1; i < roundAt; i++) digits[i] = 0;
+ }
+
+ if (digit >= 5) {
+ if (roundAt - 1 < 0) {
+ for (let k = 0; k > roundAt; k--) {
+ digits.unshift(0);
+ parsedNumber.integerLen++;
+ }
+ digits.unshift(1);
+ parsedNumber.integerLen++;
+ } else {
+ digits[roundAt - 1]++;
+ }
+ }
+
+ // Pad out with zeros to get the required fraction length
+ for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0);
+
+
+ // Do any carrying, e.g. a digit was rounded up to 10
+ const carry = digits.reduceRight(function(carry, d, i, digits) {
+ d = d + carry;
+ digits[i] = d % 10;
+ return Math.floor(d / 10);
+ }, 0);
+ if (carry) {
+ digits.unshift(carry);
+ parsedNumber.integerLen++;
+ }
+}
+
+/** @internal */
+export function parseIntAutoRadix(text: string): number {
+ const result: number = parseInt(text);
+ if (isNaN(result)) {
+ throw new Error('Invalid integer literal when parsing ' + text);
+ }
+ return result;
+}
diff --git a/packages/common/src/i18n/locale_data.ts b/packages/common/src/i18n/locale_data.ts
new file mode 100644
index 00000000000000..cbcbaf1afe61d6
--- /dev/null
+++ b/packages/common/src/i18n/locale_data.ts
@@ -0,0 +1,22 @@
+/**
+ * @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
+ */
+
+/** @experimental */
+export enum Plural {
+ Zero,
+ One,
+ Two,
+ Few,
+ Many,
+ Other,
+}
+
+/**
+ * @experimental i18n support is experimental.
+ */
+export const LOCALE_DATA: {[localeId: string]: any} = {};
diff --git a/packages/common/src/i18n/locale_data_api.ts b/packages/common/src/i18n/locale_data_api.ts
new file mode 100644
index 00000000000000..cae3b8b3659d84
--- /dev/null
+++ b/packages/common/src/i18n/locale_data_api.ts
@@ -0,0 +1,594 @@
+/**
+ * @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
+ */
+
+import {AVAILABLE_LOCALES} from './available_locales';
+import {CURRENCIES} from './currencies';
+import localeEn from './locale_en';
+import {LOCALE_DATA, Plural} from './locale_data';
+
+/**
+ * The different format styles that can be used to represent numbers.
+ * Used by the function {@link getLocaleNumberFormat}.
+ *
+ * @experimental i18n support is experimental.
+ */
+export enum NumberFormatStyle {
+ Decimal,
+ Percent,
+ Currency,
+ Scientific
+}
+
+/**
+ * Some languages use two different forms of strings (standalone and format) depending on the
+ * context.
+ * Typically the standalone version is the nominative form of the word, and the format version is in
+ * the genitive.
+ * See [the CLDR website](http://cldr.unicode.org/translation/date-time) for more information.
+ *
+ * @experimental i18n support is experimental.
+ */
+export enum FormStyle {
+ Format,
+ Standalone
+}
+
+/**
+ * Multiple widths are available for translations: narrow (1 character), abbreviated (3 characters),
+ * wide (full length), and short (2 characters, only for days).
+ *
+ * For example the day `Sunday` will be:
+ * - Narrow: `S`
+ * - Short: `Su`
+ * - Abbreviated: `Sun`
+ * - Wide: `Sunday`
+ *
+ * @experimental i18n support is experimental.
+ */
+export enum TranslationWidth {
+ Narrow,
+ Abbreviated,
+ Wide,
+ Short
+}
+
+/**
+ * Multiple widths are available for formats: short (minimal amount of data), medium (small amount
+ * of data), long (complete amount of data), full (complete amount of data and extra information).
+ *
+ * For example the date-time formats for the english locale will be:
+ * - `'short'`: `'M/d/yy, h:mm a'` (e.g. `6/15/15, 9:03 AM`)
+ * - `'medium'`: `'MMM d, y, h:mm:ss a'` (e.g. `Jun 15, 2015, 9:03:01 AM`)
+ * - `'long'`: `'MMMM d, y, h:mm:ss a z'` (e.g. `June 15, 2015 at 9:03:01 AM GMT+1`)
+ * - `'full'`: `'EEEE, MMMM d, y, h:mm:ss a zzzz'` (e.g. `Monday, June 15, 2015 at
+ * 9:03:01 AM GMT+01:00`)
+ *
+ * @experimental i18n support is experimental.
+ */
+export enum FormatWidth {
+ Short,
+ Medium,
+ Long,
+ Full
+}
+
+/**
+ * Number symbol that can be used to replace placeholders in number patterns.
+ * The placeholders are based on english values:
+ *
+ * | Name | Example for en-US | Meaning |
+ * |------------------------|-------------------|---------------------------------------------|
+ * | decimal | 2,345`.`67 | decimal separator |
+ * | group | 2`,`345.67 | grouping separator, typically for thousands |
+ * | plusSign | `+`23 | the plus sign used with numbers |
+ * | minusSign | `-`23 | the minus sign used with numbers |
+ * | percentSign | 23.4`%` | the percent sign (out of 100) |
+ * | perMille | 234`‰` | the permille sign (out of 1000) |
+ * | exponential | 1.2`E`3 | used in computers for 1.2×10³. |
+ * | superscriptingExponent | 1.2`×`103 | human-readable format of exponential |
+ * | infinity | `∞` | used in +∞ and -∞. |
+ * | nan | `NaN` | "not a number". |
+ *
+ * @experimental i18n support is experimental.
+ */
+export enum NumberSymbol {
+ Decimal,
+ Group,
+ List,
+ PercentSign,
+ PlusSign,
+ MinusSign,
+ Exponential,
+ SuperscriptingExponent,
+ PerMille,
+ Infinity,
+ NaN,
+ TimeSeparator,
+ CurrencyGroup
+}
+
+/**
+ * The value for each day of the week, based on the en-US locale
+ *
+ * @experimental
+ */
+export enum WeekDay {
+ Sunday = 0,
+ Monday,
+ Tuesday,
+ Wednesday,
+ Thursday,
+ Friday,
+ Saturday
+}
+
+/**
+ * Use this enum to find the index of each type of locale data from the locale data array
+ */
+enum LocaleDataIndex {
+ LocaleId = 0,
+ DayPeriodsFormat,
+ DayPeriodsStandalone,
+ DaysFormat,
+ DaysStandalone,
+ MonthsFormat,
+ MonthsStandalone,
+ Eras,
+ FirstDayOfWeek,
+ WeekendRange,
+ DateFormat,
+ TimeFormat,
+ DateTimeFormat,
+ NumberSymbols,
+ NumberFormats,
+ CurrencySymbol,
+ CurrencyName,
+ PluralCase,
+ ExtraData
+}
+
+/**
+ * Use this enum to find the index of each type of locale data from the extra locale data array
+ */
+enum ExtraLocaleDataIndex {
+ ExtraDayPeriodFormats = 0,
+ ExtraDayPeriodStandalone,
+ ExtraDayPeriodsRules
+}
+
+/**
+ * The locale id for the chosen locale (e.g `en-GB`).
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleId(locale: string): string {
+ return findLocaleData(locale)[LocaleDataIndex.LocaleId];
+}
+
+/**
+ * Periods of the day (e.g. `[AM, PM]` for en-US).
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleDayPeriods(
+ locale: string, formStyle: FormStyle, width: TranslationWidth): [string, string] {
+ const data = findLocaleData(locale);
+ const amPmData = <[
+ string, string
+ ][][]>[data[LocaleDataIndex.DayPeriodsFormat], data[LocaleDataIndex.DayPeriodsStandalone]];
+ const amPm = getLastDefinedValue(amPmData, formStyle);
+ return getLastDefinedValue(amPm, width);
+}
+
+/**
+ * Days of the week for the Gregorian calendar (e.g. `[Sunday, Monday, ... Saturday]` for en-US).
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleDayNames(
+ locale: string, formStyle: FormStyle, width: TranslationWidth): string[] {
+ const data = findLocaleData(locale);
+ const daysData =
+ [data[LocaleDataIndex.DaysFormat], data[LocaleDataIndex.DaysStandalone]];
+ const days = getLastDefinedValue(daysData, formStyle);
+ return getLastDefinedValue(days, width);
+}
+
+/**
+ * Months of the year for the Gregorian calendar (e.g. `[January, February, ...]` for en-US).
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleMonthNames(
+ locale: string, formStyle: FormStyle, width: TranslationWidth): string[] {
+ const data = findLocaleData(locale);
+ const monthsData =
+ [data[LocaleDataIndex.MonthsFormat], data[LocaleDataIndex.MonthsStandalone]];
+ const months = getLastDefinedValue(monthsData, formStyle);
+ return getLastDefinedValue(months, width);
+}
+
+/**
+ * Eras for the Gregorian calendar (e.g. AD/BC).
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleEraNames(locale: string, width: TranslationWidth): [string, string] {
+ const data = findLocaleData(locale);
+ const erasData = <[string, string][]>data[LocaleDataIndex.Eras];
+ return getLastDefinedValue(erasData, width);
+}
+
+/**
+ * First day of the week for this locale, based on english days (Sunday = 0, Monday = 1, ...).
+ * For example in french the value would be 1 because the first day of the week is Monday.
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleFirstDayOfWeek(locale: string): WeekDay {
+ const data = findLocaleData(locale);
+ return data[LocaleDataIndex.FirstDayOfWeek];
+}
+
+/**
+ * Range of days in the week that represent the week-end for this locale, based on english days
+ * (Sunday = 0, Monday = 1, ...).
+ * For example in english the value would be [6,0] for Saturday to Sunday.
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleWeekEndRange(locale: string): [WeekDay, WeekDay] {
+ const data = findLocaleData(locale);
+ return data[LocaleDataIndex.WeekendRange];
+}
+
+/**
+ * Date format that depends on the locale.
+ *
+ * There are four basic date formats:
+ * - `full` should contain long-weekday (EEEE), year (y), long-month (MMMM), day (d).
+ *
+ * For example, English uses `EEEE, MMMM d, y`, corresponding to a date like
+ * "Tuesday, September 14, 1999".
+ *
+ * - `long` should contain year, long-month, day.
+ *
+ * For example, `MMMM d, y`, corresponding to a date like "September 14, 1999".
+ *
+ * - `medium` should contain year, abbreviated-month (MMM), day.
+ *
+ * For example, `MMM d, y`, corresponding to a date like "Sep 14, 1999".
+ * For languages that do not use abbreviated months, use the numeric month (MM/M). For example,
+ * `y/MM/dd`, corresponding to a date like "1999/09/14".
+ *
+ * - `short` should contain year, numeric-month (MM/M), and day.
+ *
+ * For example, `M/d/yy`, corresponding to a date like "9/14/99".
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleDateFormat(locale: string, width: FormatWidth): string {
+ const data = findLocaleData(locale);
+ return data[LocaleDataIndex.DateFormat][width];
+}
+
+/**
+ * Time format that depends on the locale.
+ *
+ * The standard formats include four basic time formats:
+ * - `full` should contain hour (h/H), minute (mm), second (ss), and zone (zzzz).
+ * - `long` should contain hour, minute, second, and zone (z)
+ * - `medium` should contain hour, minute, second.
+ * - `short` should contain hour, minute.
+ *
+ * Note: The patterns depend on whether the main country using your language uses 12-hour time or
+ * not:
+ * - For 12-hour time, use a pattern like `hh:mm a` using h to mean a 12-hour clock cycle running
+ * 1 through 12 (midnight plus 1 minute is 12:01), or using K to mean a 12-hour clock cycle
+ * running 0 through 11 (midnight plus 1 minute is 0:01).
+ * - For 24-hour time, use a pattern like `HH:mm` using H to mean a 24-hour clock cycle running 0
+ * through 23 (midnight plus 1 minute is 0:01), or using k to mean a 24-hour clock cycle running
+ * 1 through 24 (midnight plus 1 minute is 24:01).
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleTimeFormat(locale: string, width: FormatWidth): string {
+ const data = findLocaleData(locale);
+ return data[LocaleDataIndex.TimeFormat][width];
+}
+
+/**
+ * Date-time format that depends on the locale.
+ *
+ * The date-time pattern shows how to combine separate patterns for date (represented by {1})
+ * and time (represented by {0}) into a single pattern. It usually doesn't need to be changed.
+ * What you want to pay attention to are:
+ * - possibly removing a space for languages that don't use it, such as many East Asian languages
+ * - possibly adding a comma, other punctuation, or a combining word
+ *
+ * For example:
+ * - English uses `{1} 'at' {0}` or `{1}, {0}` (depending on date style), while Japanese uses
+ * `{1}{0}`.
+ * - An English formatted date-time using the combining pattern `{1}, {0}` could be
+ * `Dec 10, 2010, 3:59:49 PM`. Notice the comma and space between the date portion and the time
+ * portion.
+ *
+ * There are four formats (`full`, `long`, `medium`, `short`); the determination of which to use
+ * is normally based on the date style. For example, if the date has a full month and weekday
+ * name, the full combining pattern will be used to combine that with a time. If the date has
+ * numeric month, the short version of the combining pattern will be used to combine that with a
+ * time. English uses `{1} 'at' {0}` for full and long styles, and `{1}, {0}` for medium and short
+ * styles.
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleDateTimeFormat(locale: string, width: FormatWidth): string {
+ const data = findLocaleData(locale);
+ const dateTimeFormatData = data[LocaleDataIndex.DateTimeFormat];
+ return getLastDefinedValue(dateTimeFormatData, width);
+}
+
+/**
+ * Number symbol that can be used to replace placeholders in number formats.
+ * See {@link NumberSymbol} for more information.
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleNumberSymbol(locale: string, symbol: NumberSymbol): string {
+ const data = findLocaleData(locale);
+ return data[LocaleDataIndex.NumberSymbols][symbol];
+}
+
+/**
+ * Number format that depends on the locale.
+ *
+ * Numbers are formatted using patterns, like `#,###.00`. For example, the pattern `#,###.00`
+ * when used to format the number 12345.678 could result in "12'345,67". That would happen if the
+ * grouping separator for your language is an apostrophe, and the decimal separator is a comma.
+ *
+ * Important: The characters `.` `,` `0` `#` (and others below) are special placeholders;
+ * they stand for the decimal separator, and so on, and are NOT real characters.
+ * You must NOT "translate" the placeholders; for example, don't change `.` to `,` even though in
+ * your language the decimal point is written with a comma. The symbols should be replaced by the
+ * local equivalents, using the Number Symbols for your language.
+ *
+ * Here are the special characters used in number patterns:
+ *
+ * | Symbol | Meaning |
+ * |--------|---------|
+ * | . | Replaced automatically by the character used for the decimal point. |
+ * | , | Replaced by the "grouping" (thousands) separator. |
+ * | 0 | Replaced by a digit (or zero if there aren't enough digits). |
+ * | # | Replaced by a digit (or nothing if there aren't enough). |
+ * | ¤ | This will be replaced by a currency symbol, such as $ or USD. |
+ * | % | This marks a percent format. The % symbol may change position, but must be retained. |
+ * | E | This marks a scientific format. The E symbol may change position, but must be retained. |
+ * | ' | Special characters used as literal characters are quoted with ASCII single quotes. |
+ *
+ * You can find more information
+ * [on the CLDR website](http://cldr.unicode.org/translation/number-patterns)
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleNumberFormat(locale: string, type: NumberFormatStyle): string {
+ const data = findLocaleData(locale);
+ return data[LocaleDataIndex.NumberFormats][type];
+}
+
+/**
+ * The symbol used to represent the currency for the main country using this locale (e.g. $ for
+ * the locale en-US).
+ * The symbol will be `null` if the main country cannot be determined.
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleCurrencySymbol(locale: string): string|null {
+ const data = findLocaleData(locale);
+ return data[LocaleDataIndex.CurrencySymbol] || null;
+}
+
+/**
+ * The name of the currency for the main country using this locale (e.g. USD for the locale
+ * en-US).
+ * The name will be `null` if the main country cannot be determined.
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleCurrencyName(locale: string): string|null {
+ const data = findLocaleData(locale);
+ return data[LocaleDataIndex.CurrencyName] || null;
+}
+
+/**
+ * The locale plural function used by ICU expressions to determine the plural case to use.
+ * See {@link NgPlural} for more information.
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocalePluralCase(locale: string): (value: number) => Plural {
+ const data = findLocaleData(locale);
+ return data[LocaleDataIndex.PluralCase];
+}
+
+function checkFullData(data: any) {
+ if (!data[LocaleDataIndex.ExtraData]) {
+ throw new Error(
+ `Missing extra locale data for the locale "${data[LocaleDataIndex.LocaleId]}". Use "registerLocaleData" to load new data. See the "I18n guide" on angular.io to know more.`);
+ }
+}
+
+/**
+ * Rules used to determine which day period to use (See `dayPeriods` below).
+ * The rules can either be an array or a single value. If it's an array, consider it as "from"
+ * and "to". If it's a single value then it means that the period is only valid at this exact
+ * value.
+ * There is always the same number of rules as the number of day periods, which means that the
+ * first rule is applied to the first day period and so on.
+ * You should fallback to AM/PM when there are no rules available.
+ *
+ * Note: this is only available if you load the full locale data.
+ * See the {@linkDocs guide/i18n#i18n-pipes "I18n guide"} to know how to import additional locale
+ * data.
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleExtraDayPeriodRules(locale: string): (Time | [Time, Time])[] {
+ const data = findLocaleData(locale);
+ checkFullData(data);
+ const rules = data[LocaleDataIndex.ExtraData][ExtraLocaleDataIndex.ExtraDayPeriodsRules] || [];
+ return rules.map((rule: string | [string, string]) => {
+ if (typeof rule === 'string') {
+ return extractTime(rule);
+ }
+ return [extractTime(rule[0]), extractTime(rule[1])];
+ });
+}
+
+/**
+ * Day Periods indicate roughly how the day is broken up in different languages (e.g. morning,
+ * noon, afternoon, midnight, ...).
+ * You should use the function {@link getLocaleExtraDayPeriodRules} to determine which period to
+ * use.
+ * You should fallback to AM/PM when there are no day periods available.
+ *
+ * Note: this is only available if you load the full locale data.
+ * See the {@linkDocs guide/i18n#i18n-pipes "I18n guide"} to know how to import additional locale
+ * data.
+ *
+ * @experimental i18n support is experimental.
+ */
+export function getLocaleExtraDayPeriods(
+ locale: string, formStyle: FormStyle, width: TranslationWidth): string[] {
+ const data = findLocaleData(locale);
+ checkFullData(data);
+ const dayPeriodsData = [
+ data[LocaleDataIndex.ExtraData][ExtraLocaleDataIndex.ExtraDayPeriodFormats],
+ data[LocaleDataIndex.ExtraData][ExtraLocaleDataIndex.ExtraDayPeriodStandalone]
+ ];
+ const dayPeriods = getLastDefinedValue(dayPeriodsData, formStyle) || [];
+ return getLastDefinedValue(dayPeriods, width) || [];
+}
+
+/**
+ * Returns the first value that is defined in an array, going backwards.
+ *
+ * To avoid repeating the same data (e.g. when "format" and "standalone" are the same) we only
+ * add the first one to the locale data arrays, the other ones are only defined when different.
+ * We use this function to retrieve the first defined value.
+ *
+ * @experimental i18n support is experimental.
+ */
+function getLastDefinedValue(data: T[], index: number): T {
+ for (let i = index; i > -1; i--) {
+ if (typeof data[i] !== 'undefined') {
+ return data[i];
+ }
+ }
+ throw new Error('Locale data API: locale data undefined');
+}
+
+/**
+ * A representation of the time with hours and minutes
+ *
+ * @experimental i18n support is experimental.
+ */
+export type Time = {
+ hours: number,
+ minutes: number
+};
+
+/**
+ * Extract the hours and minutes from a string like "15:45"
+ */
+function extractTime(time: string): Time {
+ const [h, m] = time.split(':');
+ return {hours: +h, minutes: +m};
+}
+
+/**
+ * Finds the locale data for a locale id
+ *
+ * @experimental i18n support is experimental.
+ */
+export function findLocaleData(locale: string): any {
+ const normalizedLocale = getNormalizedLocale(locale);
+
+ if (normalizedLocale === 'en') {
+ return LOCALE_DATA['en'] || localeEn;
+ }
+
+ const match = LOCALE_DATA[toCamelCase(normalizedLocale)];
+ if (match) {
+ return match;
+ }
+
+ throw new Error(
+ `Missing locale data for the locale "${locale}". Use "registerLocaleData" to load new data. See the "I18n guide" on angular.io to know more.`);
+}
+
+const NORMALIZED_LOCALES: any = {};
+
+/**
+ * Returns the closest matching locale that exists or throw
+ * e.g.: "en-US" will return "en", and "fr_ca" will return "fr-CA"
+ * Rules for locale id equivalences are defined in
+ * http://cldr.unicode.org/index/cldr-spec/language-tag-equivalences
+ * and in https://tools.ietf.org/html/rfc4647#section-3.4
+ */
+function getNormalizedLocale(locale: string): string {
+ if (NORMALIZED_LOCALES[locale]) {
+ return NORMALIZED_LOCALES[locale];
+ }
+
+ const normalizedLocale = locale.toLowerCase().replace(/_/g, '-');
+ const match = AVAILABLE_LOCALES.find((l: string) => l.toLowerCase() === normalizedLocale);
+
+ if (match) {
+ NORMALIZED_LOCALES[locale] = match;
+ return match;
+ }
+
+ const parentLocale = normalizedLocale.split('-')[0];
+ if (AVAILABLE_LOCALES.find((l: string) => l.toLowerCase() === parentLocale)) {
+ NORMALIZED_LOCALES[locale] = parentLocale;
+ return parentLocale;
+ }
+
+ throw new Error(
+ `"${locale}" is not a valid LOCALE_ID value. See https://github.com/unicode-cldr/cldr-core/blob/master/availableLocales.json for a list of valid locales`);
+}
+
+function toCamelCase(str: string): string {
+ return str.replace(/-+([a-z0-9A-Z])/g, (...m: string[]) => m[1].toUpperCase());
+}
+
+/**
+ * Return the currency symbol for a given currency code, or the code if no symbol available
+ * (e.g.: $, US$, or USD)
+ *
+ * @internal
+ */
+export function findCurrencySymbol(code: string, format: 'wide' | 'narrow') {
+ const currency = CURRENCIES[code] || {};
+ const symbol = currency[0] || code;
+ return format === 'wide' ? symbol : currency[1] || symbol;
+}
+
+/**
+ * Register global data to be used internally by Angular. See the
+ * {@linkDocs guide/i18n#i18n-pipes "I18n guide"} to know how to import additional locale data.
+ *
+ * @experimental i18n support is experimental.
+ */
+export function registerLocaleData(data: any, extraData?: any) {
+ const localeId = toCamelCase(data[LocaleDataIndex.LocaleId]);
+ LOCALE_DATA[localeId] = data;
+ if (extraData) {
+ LOCALE_DATA[localeId][LocaleDataIndex.ExtraData] = extraData;
+ }
+}
diff --git a/packages/common/src/i18n/localization.ts b/packages/common/src/i18n/localization.ts
new file mode 100644
index 00000000000000..2ac5a1a0e1139e
--- /dev/null
+++ b/packages/common/src/i18n/localization.ts
@@ -0,0 +1,76 @@
+/**
+ * @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
+ */
+
+import {Inject, Injectable, LOCALE_ID} from '@angular/core';
+import {Plural} from './locale_data';
+import {getLocalePluralCase} from './locale_data_api';
+
+/**
+ * @experimental
+ */
+export abstract class NgLocalization {
+ abstract getPluralCategory(value: any, locale?: string): string;
+}
+
+
+/**
+ * Returns the plural category for a given value.
+ * - "=value" when the case exists,
+ * - the plural category otherwise
+ *
+ * @internal
+ */
+export function getPluralCategory(
+ value: number, cases: string[], ngLocalization: NgLocalization, locale?: string): string {
+ let key = `=${value}`;
+
+ if (cases.indexOf(key) > -1) {
+ return key;
+ }
+
+ key = ngLocalization.getPluralCategory(value, locale);
+
+ if (cases.indexOf(key) > -1) {
+ return key;
+ }
+
+ if (cases.indexOf('other') > -1) {
+ return 'other';
+ }
+
+ throw new Error(`No plural message found for value "${value}"`);
+}
+
+/**
+ * Returns the plural case based on the locale
+ *
+ * @experimental
+ */
+@Injectable()
+export class NgLocaleLocalization extends NgLocalization {
+ constructor(@Inject(LOCALE_ID) protected locale: string) { super(); }
+
+ getPluralCategory(value: any, locale?: string): string {
+ const plural = getLocalePluralCase(locale || this.locale)(value);
+
+ switch (plural) {
+ case Plural.Zero:
+ return 'zero';
+ case Plural.One:
+ return 'one';
+ case Plural.Two:
+ return 'two';
+ case Plural.Few:
+ return 'few';
+ case Plural.Many:
+ return 'many';
+ default:
+ return 'other';
+ }
+ }
+}
diff --git a/packages/common/src/localization.ts b/packages/common/src/localization.ts
deleted file mode 100644
index c2deb745b524c9..00000000000000
--- a/packages/common/src/localization.ts
+++ /dev/null
@@ -1,401 +0,0 @@
-/**
- * @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
- */
-
-import {Inject, Injectable, LOCALE_ID} from '@angular/core';
-
-/**
- * @experimental
- */
-export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
-
-
-/**
- * Returns the plural category for a given value.
- * - "=value" when the case exists,
- * - the plural category otherwise
- *
- * @internal
- */
-export function getPluralCategory(
- value: number, cases: string[], ngLocalization: NgLocalization): string {
- let key = `=${value}`;
-
- if (cases.indexOf(key) > -1) {
- return key;
- }
-
- key = ngLocalization.getPluralCategory(value);
-
- if (cases.indexOf(key) > -1) {
- return key;
- }
-
- if (cases.indexOf('other') > -1) {
- return 'other';
- }
-
- throw new Error(`No plural message found for value "${value}"`);
-}
-
-/**
- * Returns the plural case based on the locale
- *
- * @experimental
- */
-@Injectable()
-export class NgLocaleLocalization extends NgLocalization {
- constructor(@Inject(LOCALE_ID) protected locale: string) { super(); }
-
- getPluralCategory(value: any): string {
- const plural = getPluralCase(this.locale, value);
-
- switch (plural) {
- case Plural.Zero:
- return 'zero';
- case Plural.One:
- return 'one';
- case Plural.Two:
- return 'two';
- case Plural.Few:
- return 'few';
- case Plural.Many:
- return 'many';
- default:
- return 'other';
- }
- }
-}
-
-// This is generated code DO NOT MODIFY
-// see angular/script/cldr/gen_plural_rules.js
-
-/** @experimental */
-export enum Plural {
- Zero,
- One,
- Two,
- Few,
- Many,
- Other,
-}
-
-/**
- * Returns the plural case based on the locale
- *
- * @experimental
- */
-export function getPluralCase(locale: string, nLike: number | string): Plural {
- // TODO(vicb): lazy compute
- if (typeof nLike === 'string') {
- nLike = parseInt(nLike, 10);
- }
- const n: number = nLike as number;
- const nDecimal = n.toString().replace(/^[^.]*\.?/, '');
- const i = Math.floor(Math.abs(n));
- const v = nDecimal.length;
- const f = parseInt(nDecimal, 10);
- const t = parseInt(n.toString().replace(/^[^.]*\.?|0+$/g, ''), 10) || 0;
-
- const lang = locale.split('-')[0].toLowerCase();
-
- switch (lang) {
- case 'af':
- case 'asa':
- case 'az':
- case 'bem':
- case 'bez':
- case 'bg':
- case 'brx':
- case 'ce':
- case 'cgg':
- case 'chr':
- case 'ckb':
- case 'ee':
- case 'el':
- case 'eo':
- case 'es':
- case 'eu':
- case 'fo':
- case 'fur':
- case 'gsw':
- case 'ha':
- case 'haw':
- case 'hu':
- case 'jgo':
- case 'jmc':
- case 'ka':
- case 'kk':
- case 'kkj':
- case 'kl':
- case 'ks':
- case 'ksb':
- case 'ky':
- case 'lb':
- case 'lg':
- case 'mas':
- case 'mgo':
- case 'ml':
- case 'mn':
- case 'nb':
- case 'nd':
- case 'ne':
- case 'nn':
- case 'nnh':
- case 'nyn':
- case 'om':
- case 'or':
- case 'os':
- case 'ps':
- case 'rm':
- case 'rof':
- case 'rwk':
- case 'saq':
- case 'seh':
- case 'sn':
- case 'so':
- case 'sq':
- case 'ta':
- case 'te':
- case 'teo':
- case 'tk':
- case 'tr':
- case 'ug':
- case 'uz':
- case 'vo':
- case 'vun':
- case 'wae':
- case 'xog':
- if (n === 1) return Plural.One;
- return Plural.Other;
- case 'ak':
- case 'ln':
- case 'mg':
- case 'pa':
- case 'ti':
- if (n === Math.floor(n) && n >= 0 && n <= 1) return Plural.One;
- return Plural.Other;
- case 'am':
- case 'as':
- case 'bn':
- case 'fa':
- case 'gu':
- case 'hi':
- case 'kn':
- case 'mr':
- case 'zu':
- if (i === 0 || n === 1) return Plural.One;
- return Plural.Other;
- case 'ar':
- if (n === 0) return Plural.Zero;
- if (n === 1) return Plural.One;
- if (n === 2) return Plural.Two;
- if (n % 100 === Math.floor(n % 100) && n % 100 >= 3 && n % 100 <= 10) return Plural.Few;
- if (n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 99) return Plural.Many;
- return Plural.Other;
- case 'ast':
- case 'ca':
- case 'de':
- case 'en':
- case 'et':
- case 'fi':
- case 'fy':
- case 'gl':
- case 'it':
- case 'nl':
- case 'sv':
- case 'sw':
- case 'ur':
- case 'yi':
- if (i === 1 && v === 0) return Plural.One;
- return Plural.Other;
- case 'be':
- if (n % 10 === 1 && !(n % 100 === 11)) return Plural.One;
- if (n % 10 === Math.floor(n % 10) && n % 10 >= 2 && n % 10 <= 4 &&
- !(n % 100 >= 12 && n % 100 <= 14))
- return Plural.Few;
- if (n % 10 === 0 || n % 10 === Math.floor(n % 10) && n % 10 >= 5 && n % 10 <= 9 ||
- n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 14)
- return Plural.Many;
- return Plural.Other;
- case 'br':
- if (n % 10 === 1 && !(n % 100 === 11 || n % 100 === 71 || n % 100 === 91)) return Plural.One;
- if (n % 10 === 2 && !(n % 100 === 12 || n % 100 === 72 || n % 100 === 92)) return Plural.Two;
- if (n % 10 === Math.floor(n % 10) && (n % 10 >= 3 && n % 10 <= 4 || n % 10 === 9) &&
- !(n % 100 >= 10 && n % 100 <= 19 || n % 100 >= 70 && n % 100 <= 79 ||
- n % 100 >= 90 && n % 100 <= 99))
- return Plural.Few;
- if (!(n === 0) && n % 1e6 === 0) return Plural.Many;
- return Plural.Other;
- case 'bs':
- case 'hr':
- case 'sr':
- if (v === 0 && i % 10 === 1 && !(i % 100 === 11) || f % 10 === 1 && !(f % 100 === 11))
- return Plural.One;
- if (v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 2 && i % 10 <= 4 &&
- !(i % 100 >= 12 && i % 100 <= 14) ||
- f % 10 === Math.floor(f % 10) && f % 10 >= 2 && f % 10 <= 4 &&
- !(f % 100 >= 12 && f % 100 <= 14))
- return Plural.Few;
- return Plural.Other;
- case 'cs':
- case 'sk':
- if (i === 1 && v === 0) return Plural.One;
- if (i === Math.floor(i) && i >= 2 && i <= 4 && v === 0) return Plural.Few;
- if (!(v === 0)) return Plural.Many;
- return Plural.Other;
- case 'cy':
- if (n === 0) return Plural.Zero;
- if (n === 1) return Plural.One;
- if (n === 2) return Plural.Two;
- if (n === 3) return Plural.Few;
- if (n === 6) return Plural.Many;
- return Plural.Other;
- case 'da':
- if (n === 1 || !(t === 0) && (i === 0 || i === 1)) return Plural.One;
- return Plural.Other;
- case 'dsb':
- case 'hsb':
- if (v === 0 && i % 100 === 1 || f % 100 === 1) return Plural.One;
- if (v === 0 && i % 100 === 2 || f % 100 === 2) return Plural.Two;
- if (v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 3 && i % 100 <= 4 ||
- f % 100 === Math.floor(f % 100) && f % 100 >= 3 && f % 100 <= 4)
- return Plural.Few;
- return Plural.Other;
- case 'ff':
- case 'fr':
- case 'hy':
- case 'kab':
- if (i === 0 || i === 1) return Plural.One;
- return Plural.Other;
- case 'fil':
- if (v === 0 && (i === 1 || i === 2 || i === 3) ||
- v === 0 && !(i % 10 === 4 || i % 10 === 6 || i % 10 === 9) ||
- !(v === 0) && !(f % 10 === 4 || f % 10 === 6 || f % 10 === 9))
- return Plural.One;
- return Plural.Other;
- case 'ga':
- if (n === 1) return Plural.One;
- if (n === 2) return Plural.Two;
- if (n === Math.floor(n) && n >= 3 && n <= 6) return Plural.Few;
- if (n === Math.floor(n) && n >= 7 && n <= 10) return Plural.Many;
- return Plural.Other;
- case 'gd':
- if (n === 1 || n === 11) return Plural.One;
- if (n === 2 || n === 12) return Plural.Two;
- if (n === Math.floor(n) && (n >= 3 && n <= 10 || n >= 13 && n <= 19)) return Plural.Few;
- return Plural.Other;
- case 'gv':
- if (v === 0 && i % 10 === 1) return Plural.One;
- if (v === 0 && i % 10 === 2) return Plural.Two;
- if (v === 0 &&
- (i % 100 === 0 || i % 100 === 20 || i % 100 === 40 || i % 100 === 60 || i % 100 === 80))
- return Plural.Few;
- if (!(v === 0)) return Plural.Many;
- return Plural.Other;
- case 'he':
- if (i === 1 && v === 0) return Plural.One;
- if (i === 2 && v === 0) return Plural.Two;
- if (v === 0 && !(n >= 0 && n <= 10) && n % 10 === 0) return Plural.Many;
- return Plural.Other;
- case 'is':
- if (t === 0 && i % 10 === 1 && !(i % 100 === 11) || !(t === 0)) return Plural.One;
- return Plural.Other;
- case 'ksh':
- if (n === 0) return Plural.Zero;
- if (n === 1) return Plural.One;
- return Plural.Other;
- case 'kw':
- case 'naq':
- case 'se':
- case 'smn':
- if (n === 1) return Plural.One;
- if (n === 2) return Plural.Two;
- return Plural.Other;
- case 'lag':
- if (n === 0) return Plural.Zero;
- if ((i === 0 || i === 1) && !(n === 0)) return Plural.One;
- return Plural.Other;
- case 'lt':
- if (n % 10 === 1 && !(n % 100 >= 11 && n % 100 <= 19)) return Plural.One;
- if (n % 10 === Math.floor(n % 10) && n % 10 >= 2 && n % 10 <= 9 &&
- !(n % 100 >= 11 && n % 100 <= 19))
- return Plural.Few;
- if (!(f === 0)) return Plural.Many;
- return Plural.Other;
- case 'lv':
- case 'prg':
- if (n % 10 === 0 || n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 19 ||
- v === 2 && f % 100 === Math.floor(f % 100) && f % 100 >= 11 && f % 100 <= 19)
- return Plural.Zero;
- if (n % 10 === 1 && !(n % 100 === 11) || v === 2 && f % 10 === 1 && !(f % 100 === 11) ||
- !(v === 2) && f % 10 === 1)
- return Plural.One;
- return Plural.Other;
- case 'mk':
- if (v === 0 && i % 10 === 1 || f % 10 === 1) return Plural.One;
- return Plural.Other;
- case 'mt':
- if (n === 1) return Plural.One;
- if (n === 0 || n % 100 === Math.floor(n % 100) && n % 100 >= 2 && n % 100 <= 10)
- return Plural.Few;
- if (n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 19) return Plural.Many;
- return Plural.Other;
- case 'pl':
- if (i === 1 && v === 0) return Plural.One;
- if (v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 2 && i % 10 <= 4 &&
- !(i % 100 >= 12 && i % 100 <= 14))
- return Plural.Few;
- if (v === 0 && !(i === 1) && i % 10 === Math.floor(i % 10) && i % 10 >= 0 && i % 10 <= 1 ||
- v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 5 && i % 10 <= 9 ||
- v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 12 && i % 100 <= 14)
- return Plural.Many;
- return Plural.Other;
- case 'pt':
- if (n === Math.floor(n) && n >= 0 && n <= 2 && !(n === 2)) return Plural.One;
- return Plural.Other;
- case 'ro':
- if (i === 1 && v === 0) return Plural.One;
- if (!(v === 0) || n === 0 ||
- !(n === 1) && n % 100 === Math.floor(n % 100) && n % 100 >= 1 && n % 100 <= 19)
- return Plural.Few;
- return Plural.Other;
- case 'ru':
- case 'uk':
- if (v === 0 && i % 10 === 1 && !(i % 100 === 11)) return Plural.One;
- if (v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 2 && i % 10 <= 4 &&
- !(i % 100 >= 12 && i % 100 <= 14))
- return Plural.Few;
- if (v === 0 && i % 10 === 0 ||
- v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 5 && i % 10 <= 9 ||
- v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 11 && i % 100 <= 14)
- return Plural.Many;
- return Plural.Other;
- case 'shi':
- if (i === 0 || n === 1) return Plural.One;
- if (n === Math.floor(n) && n >= 2 && n <= 10) return Plural.Few;
- return Plural.Other;
- case 'si':
- if (n === 0 || n === 1 || i === 0 && f === 1) return Plural.One;
- return Plural.Other;
- case 'sl':
- if (v === 0 && i % 100 === 1) return Plural.One;
- if (v === 0 && i % 100 === 2) return Plural.Two;
- if (v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 3 && i % 100 <= 4 || !(v === 0))
- return Plural.Few;
- return Plural.Other;
- case 'tzm':
- if (n === Math.floor(n) && n >= 0 && n <= 1 || n === Math.floor(n) && n >= 11 && n <= 99)
- return Plural.One;
- return Plural.Other;
- // When there is no specification, the default is always "other"
- // Spec: http://cldr.unicode.org/index/cldr-spec/plural-rules
- // > other (required—general plural form — also used if the language only has a single form)
- default:
- return Plural.Other;
- }
-}
diff --git a/packages/common/src/pipes/date_pipe.ts b/packages/common/src/pipes/date_pipe.ts
index aec4cee8355e7c..2129c06d08a597 100644
--- a/packages/common/src/pipes/date_pipe.ts
+++ b/packages/common/src/pipes/date_pipe.ts
@@ -7,66 +7,118 @@
*/
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
-import {DateFormatter} from './intl';
+import {formatDate} from '../i18n/format_date';
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
-import {isNumeric} from './number_pipe';
-const ISO8601_DATE_REGEX =
+export const ISO8601_DATE_REGEX =
/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
// 1 2 3 4 5 6 7 8 9 10 11
+// clang-format off
/**
* @ngModule CommonModule
* @whatItDoes Formats a date according to locale rules.
- * @howToUse `date_expression | date[:format]`
+ * @howToUse `date_expression | date[:format[:timezone[:locale]]]`
* @description
*
* Where:
* - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
* (https://www.w3.org/TR/NOTE-datetime).
* - `format` indicates which date/time components to include. The format can be predefined as
- * shown below or custom as shown in the table.
- * - `'medium'`: equivalent to `'yMMMdjms'` (e.g. `Sep 3, 2010, 12:05:08 PM` for `en-US`)
- * - `'short'`: equivalent to `'yMdjm'` (e.g. `9/3/2010, 12:05 PM` for `en-US`)
- * - `'fullDate'`: equivalent to `'yMMMMEEEEd'` (e.g. `Friday, September 3, 2010` for `en-US`)
- * - `'longDate'`: equivalent to `'yMMMMd'` (e.g. `September 3, 2010` for `en-US`)
- * - `'mediumDate'`: equivalent to `'yMMMd'` (e.g. `Sep 3, 2010` for `en-US`)
- * - `'shortDate'`: equivalent to `'yMd'` (e.g. `9/3/2010` for `en-US`)
- * - `'mediumTime'`: equivalent to `'jms'` (e.g. `12:05:08 PM` for `en-US`)
- * - `'shortTime'`: equivalent to `'jm'` (e.g. `12:05 PM` for `en-US`)
+ * shown below (all examples are given for `en-US`) or custom as shown in the table.
+ * - `'short'`: equivalent to `'M/d/yy, h:mm a'` (e.g. `6/15/15, 9:03 AM`)
+ * - `'medium'`: equivalent to `'MMM d, y, h:mm:ss a'` (e.g. `Jun 15, 2015, 9:03:01 AM`)
+ * - `'long'`: equivalent to `'MMMM d, y, h:mm:ss a z'` (e.g. `June 15, 2015 at 9:03:01 AM GMT+1`)
+ * - `'full'`: equivalent to `'EEEE, MMMM d, y, h:mm:ss a zzzz'` (e.g. `Monday, June 15, 2015 at
+ * 9:03:01 AM GMT+01:00`)
+ * - `'shortDate'`: equivalent to `'M/d/yy'` (e.g. `6/15/15`)
+ * - `'mediumDate'`: equivalent to `'MMM d, y'` (e.g. `Jun 15, 2015`)
+ * - `'longDate'`: equivalent to `'MMMM d, y'` (e.g. `June 15, 2015`)
+ * - `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` (e.g. `Monday, June 15, 2015`)
+ * - `'shortTime'`: equivalent to `'h:mm a'` (e.g. `9:03 AM`)
+ * - `'mediumTime'`: equivalent to `'h:mm:ss a'` (e.g. `9:03:01 AM`)
+ * - `'longTime'`: equivalent to `'h:mm:ss a z'` (e.g. `9:03:01 AM GMT+1`)
+ * - `'fullTime'`: equivalent to `'h:mm:ss a zzzz'` (e.g. `9:03:01 AM GMT+01:00`)
+ * - `timezone` to be used for formatting. It understands UTC/GMT and the continental US time zone
+ * abbreviations, but for general use, use a time zone offset, for example,
+ * `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
+ * If not specified, the local system timezone of the end-user's browser will be used.
+ * - `locale` is a `string` defining the locale to use (uses the current {@link LOCALE_ID} by
+ * default)
*
*
- * | Component | Symbol | Narrow | Short Form | Long Form | Numeric | 2-digit |
- * |-----------|:------:|--------|--------------|-------------------|-----------|-----------|
- * | era | G | G (A) | GGG (AD) | GGGG (Anno Domini)| - | - |
- * | year | y | - | - | - | y (2015) | yy (15) |
- * | month | M | L (S) | MMM (Sep) | MMMM (September) | M (9) | MM (09) |
- * | day | d | - | - | - | d (3) | dd (03) |
- * | weekday | E | E (S) | EEE (Sun) | EEEE (Sunday) | - | - |
- * | hour | j | - | - | - | j (1 PM) | jj (1 PM) |
- * | hour12 | h | - | - | - | h (1) | hh (01) |
- * | hour24 | H | - | - | - | H (13) | HH (13) |
- * | minute | m | - | - | - | m (5) | mm (05) |
- * | second | s | - | - | - | s (9) | ss (09) |
- * | timezone | z | - | - | z (Pacific Standard Time)| - | - |
- * | timezone | Z | - | Z (GMT-8:00) | - | - | - |
- * | timezone | a | - | a (PM) | - | - | - |
+ * | Field Type | Format | Description | Example Value |
+ * |--------------------|-------------|---------------------------------------------------------------|------------------------------------------------------------|
+ * | Era | G, GG & GGG | Abbreviated | AD |
+ * | | GGGG | Wide | Anno Domini |
+ * | | GGGGG | Narrow | A |
+ * | Year | y | Numeric: minimum digits | 2, 20, 201, 2017, 20173 |
+ * | | yy | Numeric: 2 digits + zero padded | 02, 20, 01, 17, 73 |
+ * | | yyy | Numeric: 3 digits + zero padded | 002, 020, 201, 2017, 20173 |
+ * | | yyyy | Numeric: 4 digits or more + zero padded | 0002, 0020, 0201, 2017, 20173 |
+ * | Month | M | Numeric: 1 digit | 9, 12 |
+ * | | MM | Numeric: 2 digits + zero padded | 09, 12 |
+ * | | MMM | Abbreviated | Sep |
+ * | | MMMM | Wide | September |
+ * | | MMMMM | Narrow | S |
+ * | Month standalone | L | Numeric: 1 digit | 9, 12 |
+ * | | LL | Numeric: 2 digits + zero padded | 09, 12 |
+ * | | LLL | Abbreviated | Sep |
+ * | | LLLL | Wide | September |
+ * | | LLLLL | Narrow | S |
+ * | Week of year | w | Numeric: minimum digits | 1... 53 |
+ * | | ww | Numeric: 2 digits + zero padded | 01... 53 |
+ * | Week of month | W | Numeric: 1 digit | 1... 5 |
+ * | Day of month | d | Numeric: minimum digits | 1 |
+ * | | dd | Numeric: 2 digits + zero padded | 1 |
+ * | Week day | E, EE & EEE | Abbreviated | Tue |
+ * | | EEEE | Wide | Tuesday |
+ * | | EEEEE | Narrow | T |
+ * | | EEEEEE | Short | Tu |
+ * | Period | a, aa & aaa | Abbreviated | am/pm or AM/PM |
+ * | | aaaa | Wide (fallback to `a` when missing) | ante meridiem/post meridiem |
+ * | | aaaaa | Narrow | a/p |
+ * | Period* | B, BB & BBB | Abbreviated | mid. |
+ * | | BBBB | Wide | am, pm, midnight, noon, morning, afternoon, evening, night |
+ * | | BBBBB | Narrow | md |
+ * | Period standalone* | b, bb & bbb | Abbreviated | mid. |
+ * | | bbbb | Wide | am, pm, midnight, noon, morning, afternoon, evening, night |
+ * | | bbbbb | Narrow | md |
+ * | Hour 1-12 | h | Numeric: minimum digits | 1, 12 |
+ * | | hh | Numeric: 2 digits + zero padded | 01, 12 |
+ * | Hour 0-23 | H | Numeric: minimum digits | 0, 23 |
+ * | | HH | Numeric: 2 digits + zero padded | 00, 23 |
+ * | Minute | m | Numeric: minimum digits | 8, 59 |
+ * | | mm | Numeric: 2 digits + zero padded | 08, 59 |
+ * | Second | s | Numeric: minimum digits | 0... 59 |
+ * | | ss | Numeric: 2 digits + zero padded | 00... 59 |
+ * | Fractional seconds | S | Numeric: 1 digit | 0... 9 |
+ * | | SS | Numeric: 2 digits + zero padded | 00... 99 |
+ * | | SSS | Numeric: 3 digits + zero padded (= milliseconds) | 000... 999 |
+ * | Zone | z, zz & zzz | Short specific non location format (fallback to O) | GMT-8 |
+ * | | zzzz | Long specific non location format (fallback to OOOO) | GMT-08:00 |
+ * | | Z, ZZ & ZZZ | ISO8601 basic format | -0800 |
+ * | | ZZZZ | Long localized GMT format | GMT-8:00 |
+ * | | ZZZZZ | ISO8601 extended format + Z indicator for offset 0 (= XXXXX) | -08:00 |
+ * | | O, OO & OOO | Short localized GMT format | GMT-8 |
+ * | | OOOO | Long localized GMT format | GMT-08:00 |
*
- * In javascript, only the components specified will be respected (not the ordering,
- * punctuations, ...) and details of the formatting will be dependent on the locale.
- *
- * Timezone of the formatted text will be the local system timezone of the end-user's machine.
*
* When the expression is a ISO string without time (e.g. 2016-09-19) the time zone offset is not
* applied and the formatted text will have the same day, month and year of the expression.
*
* WARNINGS:
+ * - this pipe has only access to en-US locale data by default. If you want to localize the dates
+ * in another language, you will have to import data for other locales.
+ * See the {@linkDocs guide/i18n#i18n-pipes "I18n guide"} to know how to import additional locale
+ * data.
+ * - Fields suffixed with * are only available in the extra dataset.
+ * See the {@linkDocs guide/i18n#i18n-pipes "I18n guide"} to know how to import extra locale
+ * data.
* - this pipe is marked as pure hence it will not be re-evaluated when the input is mutated.
* Instead users should treat the date as an immutable object and change the reference when the
* pipe needs to re-run (this is to avoid reformatting the date on every change detection run
* which would be an expensive operation).
- * - this pipe uses the Internationalization API. Therefore it is only reliable in Chrome and Opera
- * browsers.
*
* ### Examples
*
@@ -77,41 +129,29 @@ const ISO8601_DATE_REGEX =
* {{ dateObj | date }} // output is 'Jun 15, 2015'
* {{ dateObj | date:'medium' }} // output is 'Jun 15, 2015, 9:43:11 PM'
* {{ dateObj | date:'shortTime' }} // output is '9:43 PM'
- * {{ dateObj | date:'mmss' }} // output is '43:11'
+ * {{ dateObj | date:'hh:mm:ss a' }} // output is '09:43:11 PM'
* ```
*
* {@example common/pipes/ts/date_pipe.ts region='DatePipe'}
*
* @stable
*/
+// clang-format on
@Pipe({name: 'date', pure: true})
export class DatePipe implements PipeTransform {
- /** @internal */
- static _ALIASES: {[key: string]: string} = {
- 'medium': 'yMMMdjms',
- 'short': 'yMdjm',
- 'fullDate': 'yMMMMEEEEd',
- 'longDate': 'yMMMMd',
- 'mediumDate': 'yMMMd',
- 'shortDate': 'yMd',
- 'mediumTime': 'jms',
- 'shortTime': 'jm'
- };
-
- constructor(@Inject(LOCALE_ID) private _locale: string) {}
+ constructor(@Inject(LOCALE_ID) private locale: string) {}
- transform(value: any, pattern: string = 'mediumDate'): string|null {
- let date: Date;
-
- if (isBlank(value) || value !== value) return null;
+ transform(value: any, format = 'mediumDate', timezone?: string, locale?: string): string|null {
+ if (value == null || value === '' || value !== value) return null;
if (typeof value === 'string') {
value = value.trim();
}
+ let date: Date;
if (isDate(value)) {
date = value;
- } else if (isNumeric(value)) {
+ } else if (!isNaN(value - parseFloat(value))) {
date = new Date(parseFloat(value));
} else if (typeof value === 'string' && /^(\d{4}-\d{1,2}-\d{1,2})$/.test(value)) {
/**
@@ -123,7 +163,7 @@ export class DatePipe implements PipeTransform {
* is applied
* Note: ISO months are 0 for January, 1 for February, ...
*/
- const [y, m, d] = value.split('-').map((val: string) => parseInt(val, 10));
+ const [y, m, d] = value.split('-').map((val: string) => +val);
date = new Date(y, m - 1, d);
} else {
date = new Date(value);
@@ -138,19 +178,12 @@ export class DatePipe implements PipeTransform {
}
}
- return DateFormatter.format(date, this._locale, DatePipe._ALIASES[pattern] || pattern);
+ return formatDate(date, format, locale || this.locale, timezone);
}
}
-function isBlank(obj: any): boolean {
- return obj == null || obj === '';
-}
-
-function isDate(obj: any): obj is Date {
- return obj instanceof Date && !isNaN(obj.valueOf());
-}
-
-function isoStringToDate(match: RegExpMatchArray): Date {
+/** @internal */
+export function isoStringToDate(match: RegExpMatchArray): Date {
const date = new Date(0);
let tzHour = 0;
let tzMin = 0;
@@ -158,18 +191,18 @@ function isoStringToDate(match: RegExpMatchArray): Date {
const timeSetter = match[8] ? date.setUTCHours : date.setHours;
if (match[9]) {
- tzHour = toInt(match[9] + match[10]);
- tzMin = toInt(match[9] + match[11]);
+ tzHour = +(match[9] + match[10]);
+ tzMin = +(match[9] + match[11]);
}
- dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
- const h = toInt(match[4] || '0') - tzHour;
- const m = toInt(match[5] || '0') - tzMin;
- const s = toInt(match[6] || '0');
+ dateSetter.call(date, +(match[1]), +(match[2]) - 1, +(match[3]));
+ const h = +(match[4] || '0') - tzHour;
+ const m = +(match[5] || '0') - tzMin;
+ const s = +(match[6] || '0');
const ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
timeSetter.call(date, h, m, s, ms);
return date;
}
-function toInt(str: string): number {
- return parseInt(str, 10);
+function isDate(value: any): value is Date {
+ return value instanceof Date && !isNaN(value.valueOf());
}
diff --git a/packages/common/src/pipes/deprecated/date_pipe.ts b/packages/common/src/pipes/deprecated/date_pipe.ts
new file mode 100644
index 00000000000000..2b500897cac5f0
--- /dev/null
+++ b/packages/common/src/pipes/deprecated/date_pipe.ts
@@ -0,0 +1,145 @@
+/**
+* @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
+ */
+
+import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
+import {ISO8601_DATE_REGEX, isoStringToDate} from '../date_pipe';
+import {invalidPipeArgumentError} from '../invalid_pipe_argument_error';
+import {DateFormatter} from './intl';
+
+/**
+ * @ngModule CommonModule
+ * @whatItDoes Formats a date according to locale rules.
+ * @howToUse `date_expression | date[:format]`
+ * @description
+ *
+ * Where:
+ * - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
+ * (https://www.w3.org/TR/NOTE-datetime).
+ * - `format` indicates which date/time components to include. The format can be predefined as
+ * shown below or custom as shown in the table.
+ * - `'medium'`: equivalent to `'yMMMdjms'` (e.g. `Sep 3, 2010, 12:05:08 PM` for `en-US`)
+ * - `'short'`: equivalent to `'yMdjm'` (e.g. `9/3/2010, 12:05 PM` for `en-US`)
+ * - `'fullDate'`: equivalent to `'yMMMMEEEEd'` (e.g. `Friday, September 3, 2010` for `en-US`)
+ * - `'longDate'`: equivalent to `'yMMMMd'` (e.g. `September 3, 2010` for `en-US`)
+ * - `'mediumDate'`: equivalent to `'yMMMd'` (e.g. `Sep 3, 2010` for `en-US`)
+ * - `'shortDate'`: equivalent to `'yMd'` (e.g. `9/3/2010` for `en-US`)
+ * - `'mediumTime'`: equivalent to `'jms'` (e.g. `12:05:08 PM` for `en-US`)
+ * - `'shortTime'`: equivalent to `'jm'` (e.g. `12:05 PM` for `en-US`)
+ *
+ *
+ * | Component | Symbol | Narrow | Short Form | Long Form | Numeric | 2-digit |
+ * |-----------|:------:|--------|--------------|-------------------|-----------|-----------|
+ * | era | G | G (A) | GGG (AD) | GGGG (Anno Domini)| - | - |
+ * | year | y | - | - | - | y (2015) | yy (15) |
+ * | month | M | L (S) | MMM (Sep) | MMMM (September) | M (9) | MM (09) |
+ * | day | d | - | - | - | d (3) | dd (03) |
+ * | weekday | E | E (S) | EEE (Sun) | EEEE (Sunday) | - | - |
+ * | hour | j | - | - | - | j (13) | jj (13) |
+ * | hour12 | h | - | - | - | h (1 PM) | hh (01 PM)|
+ * | hour24 | H | - | - | - | H (13) | HH (13) |
+ * | minute | m | - | - | - | m (5) | mm (05) |
+ * | second | s | - | - | - | s (9) | ss (09) |
+ * | timezone | z | - | - | z (Pacific Standard Time)| - | - |
+ * | timezone | Z | - | Z (GMT-8:00) | - | - | - |
+ * | timezone | a | - | a (PM) | - | - | - |
+ *
+ * In javascript, only the components specified will be respected (not the ordering,
+ * punctuations, ...) and details of the formatting will be dependent on the locale.
+ *
+ * Timezone of the formatted text will be the local system timezone of the end-user's machine.
+ *
+ * When the expression is a ISO string without time (e.g. 2016-09-19) the time zone offset is not
+ * applied and the formatted text will have the same day, month and year of the expression.
+ *
+ * WARNINGS:
+ * - this pipe is marked as pure hence it will not be re-evaluated when the input is mutated.
+ * Instead users should treat the date as an immutable object and change the reference when the
+ * pipe needs to re-run (this is to avoid reformatting the date on every change detection run
+ * which would be an expensive operation).
+ * - this pipe uses the Internationalization API. Therefore it is only reliable in Chrome and Opera
+ * browsers.
+ *
+ * ### Examples
+ *
+ * Assuming `dateObj` is (year: 2015, month: 6, day: 15, hour: 21, minute: 43, second: 11)
+ * in the _local_ time and locale is 'en-US':
+ *
+ * ```
+ * {{ dateObj | date }} // output is 'Jun 15, 2015'
+ * {{ dateObj | date:'medium' }} // output is 'Jun 15, 2015, 9:43:11 PM'
+ * {{ dateObj | date:'shortTime' }} // output is '9:43 PM'
+ * {{ dateObj | date:'mmss' }} // output is '43:11'
+ * ```
+ *
+ * {@example common/pipes/ts/date_pipe.ts region='DatePipe'}
+ *
+ * @stable
+ */
+@Pipe({name: 'date', pure: true})
+export class DeprecatedDatePipe implements PipeTransform {
+ /** @internal */
+ static _ALIASES: {[key: string]: string} = {
+ 'medium': 'yMMMdjms',
+ 'short': 'yMdjm',
+ 'fullDate': 'yMMMMEEEEd',
+ 'longDate': 'yMMMMd',
+ 'mediumDate': 'yMMMd',
+ 'shortDate': 'yMd',
+ 'mediumTime': 'jms',
+ 'shortTime': 'jm'
+ };
+
+ constructor(@Inject(LOCALE_ID) private _locale: string) {}
+
+ transform(value: any, pattern: string = 'mediumDate'): string|null {
+ if (value == null || value === '' || value !== value) return null;
+
+ let date: Date;
+
+ if (typeof value === 'string') {
+ value = value.trim();
+ }
+
+ if (isDate(value)) {
+ date = value;
+ } else if (!isNaN(value - parseFloat(value))) {
+ date = new Date(parseFloat(value));
+ } else if (typeof value === 'string' && /^(\d{4}-\d{1,2}-\d{1,2})$/.test(value)) {
+ /**
+ * For ISO Strings without time the day, month and year must be extracted from the ISO String
+ * before Date creation to avoid time offset and errors in the new Date.
+ * If we only replace '-' with ',' in the ISO String ("2015,01,01"), and try to create a new
+ * date, some browsers (e.g. IE 9) will throw an invalid Date error
+ * If we leave the '-' ("2015-01-01") and try to create a new Date("2015-01-01") the
+ * timeoffset
+ * is applied
+ * Note: ISO months are 0 for January, 1 for February, ...
+ */
+ const [y, m, d] = value.split('-').map((val: string) => parseInt(val, 10));
+ date = new Date(y, m - 1, d);
+ } else {
+ date = new Date(value);
+ }
+
+ if (!isDate(date)) {
+ let match: RegExpMatchArray|null;
+ if ((typeof value === 'string') && (match = value.match(ISO8601_DATE_REGEX))) {
+ date = isoStringToDate(match);
+ } else {
+ throw invalidPipeArgumentError(DeprecatedDatePipe, value);
+ }
+ }
+
+ return DateFormatter.format(
+ date, this._locale, DeprecatedDatePipe._ALIASES[pattern] || pattern);
+ }
+}
+
+function isDate(value: any): value is Date {
+ return value instanceof Date && !isNaN(value.valueOf());
+}
diff --git a/packages/common/src/pipes/deprecated/index.ts b/packages/common/src/pipes/deprecated/index.ts
new file mode 100644
index 00000000000000..935583234bcbc1
--- /dev/null
+++ b/packages/common/src/pipes/deprecated/index.ts
@@ -0,0 +1,27 @@
+/**
+ * @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
+ */
+
+import {Provider} from '@angular/core';
+import {DeprecatedDatePipe} from './date_pipe';
+import {DeprecatedCurrencyPipe, DeprecatedDecimalPipe, DeprecatedPercentPipe} from './number_pipe';
+
+export {
+ DeprecatedCurrencyPipe,
+ DeprecatedDatePipe,
+ DeprecatedDecimalPipe,
+ DeprecatedPercentPipe,
+};
+
+
+/**
+ * A collection of deprecated i18n pipes that require intl api
+ *
+ * @deprecated
+ */
+export const COMMON_DEPRECATED_I18N_PIPES: Provider[] =
+ [DeprecatedDecimalPipe, DeprecatedPercentPipe, DeprecatedCurrencyPipe, DeprecatedDatePipe];
diff --git a/packages/common/src/pipes/intl.ts b/packages/common/src/pipes/deprecated/intl.ts
similarity index 99%
rename from packages/common/src/pipes/intl.ts
rename to packages/common/src/pipes/deprecated/intl.ts
index 483e89fd8c84c8..ff7fa9808a9ed5 100644
--- a/packages/common/src/pipes/intl.ts
+++ b/packages/common/src/pipes/deprecated/intl.ts
@@ -5,12 +5,7 @@
* 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
*/
-
-export enum NumberFormatStyle {
- Decimal,
- Percent,
- Currency,
-}
+import {NumberFormatStyle} from '../../i18n/locale_data_api';
export class NumberFormatter {
static format(num: number, locale: string, style: NumberFormatStyle, opts: {
diff --git a/packages/common/src/pipes/deprecated/number_pipe.ts b/packages/common/src/pipes/deprecated/number_pipe.ts
new file mode 100644
index 00000000000000..c2b2aa62a86b8d
--- /dev/null
+++ b/packages/common/src/pipes/deprecated/number_pipe.ts
@@ -0,0 +1,164 @@
+/**
+ * @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
+ */
+
+import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
+import {NUMBER_FORMAT_REGEXP, parseIntAutoRadix} from '../../i18n/format_number';
+import {NumberFormatStyle} from '../../i18n/locale_data_api';
+import {invalidPipeArgumentError} from '../invalid_pipe_argument_error';
+import {NumberFormatter} from './intl';
+
+function formatNumber(
+ pipe: Type, locale: string, value: number | string, style: NumberFormatStyle,
+ digits?: string | null, currency: string | null = null,
+ currencyAsSymbol: boolean = false): string|null {
+ if (value == null) return null;
+
+ // Convert strings to numbers
+ value = typeof value === 'string' && !isNaN(+value - parseFloat(value)) ? +value : value;
+ if (typeof value !== 'number') {
+ throw invalidPipeArgumentError(pipe, value);
+ }
+
+ let minInt: number|undefined;
+ let minFraction: number|undefined;
+ let maxFraction: number|undefined;
+ if (style !== NumberFormatStyle.Currency) {
+ // rely on Intl default for currency
+ minInt = 1;
+ minFraction = 0;
+ maxFraction = 3;
+ }
+
+ if (digits) {
+ const parts = digits.match(NUMBER_FORMAT_REGEXP);
+ if (parts === null) {
+ throw new Error(`${digits} is not a valid digit info for number pipes`);
+ }
+ if (parts[1] != null) { // min integer digits
+ minInt = parseIntAutoRadix(parts[1]);
+ }
+ if (parts[3] != null) { // min fraction digits
+ minFraction = parseIntAutoRadix(parts[3]);
+ }
+ if (parts[5] != null) { // max fraction digits
+ maxFraction = parseIntAutoRadix(parts[5]);
+ }
+ }
+
+ return NumberFormatter.format(value as number, locale, style, {
+ minimumIntegerDigits: minInt,
+ minimumFractionDigits: minFraction,
+ maximumFractionDigits: maxFraction,
+ currency: currency,
+ currencyAsSymbol: currencyAsSymbol,
+ });
+}
+
+/**
+ * @ngModule CommonModule
+ * @whatItDoes Formats a number according to locale rules.
+ * @howToUse `number_expression | number[:digitInfo]`
+ *
+ * Formats a number as text. Group sizing and separator and other locale-specific
+ * configurations are based on the active locale.
+ *
+ * where `expression` is a number:
+ * - `digitInfo` is a `string` which has a following format:
+ * {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
+ * - `minIntegerDigits` is the minimum number of integer digits to use. Defaults to `1`.
+ * - `minFractionDigits` is the minimum number of digits after fraction. Defaults to `0`.
+ * - `maxFractionDigits` is the maximum number of digits after fraction. Defaults to `3`.
+ *
+ * For more information on the acceptable range for each of these numbers and other
+ * details see your native internationalization library.
+ *
+ * WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
+ * and may require a polyfill. See [Browser Support](guide/browser-support) for details.
+ *
+ * ### Example
+ *
+ * {@example common/pipes/ts/number_pipe.ts region='NumberPipe'}
+ *
+ * @stable
+ */
+@Pipe({name: 'number'})
+export class DeprecatedDecimalPipe implements PipeTransform {
+ constructor(@Inject(LOCALE_ID) private _locale: string) {}
+
+ transform(value: any, digits?: string): string|null {
+ return formatNumber(
+ DeprecatedDecimalPipe, this._locale, value, NumberFormatStyle.Decimal, digits);
+ }
+}
+
+/**
+ * @ngModule CommonModule
+ * @whatItDoes Formats a number as a percentage according to locale rules.
+ * @howToUse `number_expression | percent[:digitInfo]`
+ *
+ * @description
+ *
+ * Formats a number as percentage.
+ *
+ * - `digitInfo` See {@link DecimalPipe} for detailed description.
+ *
+ * WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
+ * and may require a polyfill. See [Browser Support](guide/browser-support) for details.
+ *
+ * ### Example
+ *
+ * {@example common/pipes/ts/number_pipe.ts region='PercentPipe'}
+ *
+ * @stable
+ */
+@Pipe({name: 'percent'})
+export class DeprecatedPercentPipe implements PipeTransform {
+ constructor(@Inject(LOCALE_ID) private _locale: string) {}
+
+ transform(value: any, digits?: string): string|null {
+ return formatNumber(
+ DeprecatedPercentPipe, this._locale, value, NumberFormatStyle.Percent, digits);
+ }
+}
+
+/**
+ * @ngModule CommonModule
+ * @whatItDoes Formats a number as currency using locale rules.
+ * @howToUse `number_expression | currency[:currencyCode[:symbolDisplay[:digitInfo]]]`
+ * @description
+ *
+ * Use `currency` to format a number as currency.
+ *
+ * - `currencyCode` is the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code, such
+ * as `USD` for the US dollar and `EUR` for the euro.
+ * - `symbolDisplay` is a boolean indicating whether to use the currency symbol or code.
+ * - `true`: use symbol (e.g. `$`).
+ * - `false`(default): use code (e.g. `USD`).
+ * - `digitInfo` See {@link DecimalPipe} for detailed description.
+ *
+ * WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
+ * and may require a polyfill. See [Browser Support](guide/browser-support) for details.
+ *
+ * ### Example
+ *
+ * {@example common/pipes/ts/number_pipe.ts region='CurrencyPipe'}
+ *
+ * @stable
+ */
+@Pipe({name: 'currency'})
+export class DeprecatedCurrencyPipe implements PipeTransform {
+ constructor(@Inject(LOCALE_ID) private _locale: string) {}
+
+ transform(
+ value: any, currencyCode: string = 'USD', symbolDisplay: boolean = false,
+ digits?: string): string|null {
+ return formatNumber(
+ DeprecatedCurrencyPipe, this._locale, value, NumberFormatStyle.Currency, digits,
+ currencyCode, symbolDisplay);
+ }
+}
diff --git a/packages/common/src/pipes/i18n_plural_pipe.ts b/packages/common/src/pipes/i18n_plural_pipe.ts
index dc0909d1a5e16e..0d3ca1b65ea065 100644
--- a/packages/common/src/pipes/i18n_plural_pipe.ts
+++ b/packages/common/src/pipes/i18n_plural_pipe.ts
@@ -6,8 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Pipe, PipeTransform} from '@angular/core';
-import {NgLocalization, getPluralCategory} from '../localization';
+import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
+
+import {NgLocalization, getPluralCategory} from '../i18n/localization';
+
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
const _INTERPOLATION_REGEXP: RegExp = /#/g;
@@ -15,13 +17,15 @@ const _INTERPOLATION_REGEXP: RegExp = /#/g;
/**
* @ngModule CommonModule
* @whatItDoes Maps a value to a string that pluralizes the value according to locale rules.
- * @howToUse `expression | i18nPlural:mapping`
+ * @howToUse `expression | i18nPlural:mapping[:locale]`
* @description
*
* Where:
* - `expression` is a number.
* - `mapping` is an object that mimics the ICU format, see
* http://userguide.icu-project.org/formatparse/messages
+ * - `locale` is a `string` defining the locale to use (uses the current {@link LOCALE_ID} by
+ * default)
*
* ## Example
*
@@ -33,14 +37,14 @@ const _INTERPOLATION_REGEXP: RegExp = /#/g;
export class I18nPluralPipe implements PipeTransform {
constructor(private _localization: NgLocalization) {}
- transform(value: number, pluralMap: {[count: string]: string}): string {
+ transform(value: number, pluralMap: {[count: string]: string}, locale?: string): string {
if (value == null) return '';
if (typeof pluralMap !== 'object' || pluralMap === null) {
throw invalidPipeArgumentError(I18nPluralPipe, pluralMap);
}
- const key = getPluralCategory(value, Object.keys(pluralMap), this._localization);
+ const key = getPluralCategory(value, Object.keys(pluralMap), this._localization, locale);
return pluralMap[key].replace(_INTERPOLATION_REGEXP, value.toString());
}
diff --git a/packages/common/src/pipes/number_pipe.ts b/packages/common/src/pipes/number_pipe.ts
index ae170ddc5f6eb7..1738a269794973 100644
--- a/packages/common/src/pipes/number_pipe.ts
+++ b/packages/common/src/pipes/number_pipe.ts
@@ -6,63 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
-import {NumberFormatStyle, NumberFormatter} from './intl';
+import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
+import {formatNumber} from '../i18n/format_number';
+import {NumberFormatStyle, findCurrencySymbol, getLocaleCurrencyName, getLocaleCurrencySymbol} from '../i18n/locale_data_api';
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
-const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
-
-function formatNumber(
- pipe: Type, locale: string, value: number | string, style: NumberFormatStyle,
- digits?: string | null, currency: string | null = null,
- currencyAsSymbol: boolean = false): string|null {
- if (value == null) return null;
-
- // Convert strings to numbers
- value = typeof value === 'string' && isNumeric(value) ? +value : value;
- if (typeof value !== 'number') {
- throw invalidPipeArgumentError(pipe, value);
- }
-
- let minInt: number|undefined = undefined;
- let minFraction: number|undefined = undefined;
- let maxFraction: number|undefined = undefined;
- if (style !== NumberFormatStyle.Currency) {
- // rely on Intl default for currency
- minInt = 1;
- minFraction = 0;
- maxFraction = 3;
- }
-
- if (digits) {
- const parts = digits.match(_NUMBER_FORMAT_REGEXP);
- if (parts === null) {
- throw new Error(`${digits} is not a valid digit info for number pipes`);
- }
- if (parts[1] != null) { // min integer digits
- minInt = parseIntAutoRadix(parts[1]);
- }
- if (parts[3] != null) { // min fraction digits
- minFraction = parseIntAutoRadix(parts[3]);
- }
- if (parts[5] != null) { // max fraction digits
- maxFraction = parseIntAutoRadix(parts[5]);
- }
- }
-
- return NumberFormatter.format(value as number, locale, style, {
- minimumIntegerDigits: minInt,
- minimumFractionDigits: minFraction,
- maximumFractionDigits: maxFraction,
- currency: currency,
- currencyAsSymbol: currencyAsSymbol,
- });
-}
-
/**
* @ngModule CommonModule
* @whatItDoes Formats a number according to locale rules.
- * @howToUse `number_expression | number[:digitInfo]`
+ * @howToUse `number_expression | number[:digitInfo[:locale]]`
*
* Formats a number as text. Group sizing and separator and other locale-specific
* configurations are based on the active locale.
@@ -73,13 +25,12 @@ function formatNumber(
* - `minIntegerDigits` is the minimum number of integer digits to use. Defaults to `1`.
* - `minFractionDigits` is the minimum number of digits after fraction. Defaults to `0`.
* - `maxFractionDigits` is the maximum number of digits after fraction. Defaults to `3`.
+ * - `locale` is a `string` defining the locale to use (uses the current {@link LOCALE_ID} by
+ * default)
*
* For more information on the acceptable range for each of these numbers and other
* details see your native internationalization library.
*
- * WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
- * and may require a polyfill. See [Browser Support](guide/browser-support) for details.
- *
* ### Example
*
* {@example common/pipes/ts/number_pipe.ts region='NumberPipe'}
@@ -90,24 +41,33 @@ function formatNumber(
export class DecimalPipe implements PipeTransform {
constructor(@Inject(LOCALE_ID) private _locale: string) {}
- transform(value: any, digits?: string): string|null {
- return formatNumber(DecimalPipe, this._locale, value, NumberFormatStyle.Decimal, digits);
+ transform(value: any, digits?: string, locale?: string): string|null {
+ if (isEmpty(value)) return null;
+
+ locale = locale || this._locale;
+
+ const {str, error} = formatNumber(value, locale, NumberFormatStyle.Decimal, digits);
+
+ if (error) {
+ throw invalidPipeArgumentError(CurrencyPipe, error);
+ }
+
+ return str;
}
}
/**
* @ngModule CommonModule
* @whatItDoes Formats a number as a percentage according to locale rules.
- * @howToUse `number_expression | percent[:digitInfo]`
+ * @howToUse `number_expression | percent[:digitInfo[:locale]]`
*
* @description
*
* Formats a number as percentage.
*
* - `digitInfo` See {@link DecimalPipe} for detailed description.
- *
- * WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
- * and may require a polyfill. See [Browser Support](guide/browser-support) for details.
+ * - `locale` is a `string` defining the locale to use (uses the current {@link LOCALE_ID} by
+ * default)
*
* ### Example
*
@@ -119,28 +79,40 @@ export class DecimalPipe implements PipeTransform {
export class PercentPipe implements PipeTransform {
constructor(@Inject(LOCALE_ID) private _locale: string) {}
- transform(value: any, digits?: string): string|null {
- return formatNumber(PercentPipe, this._locale, value, NumberFormatStyle.Percent, digits);
+ transform(value: any, digits?: string, locale?: string): string|null {
+ if (isEmpty(value)) return null;
+
+ locale = locale || this._locale;
+
+ const {str, error} = formatNumber(value, locale, NumberFormatStyle.Percent, digits);
+
+ if (error) {
+ throw invalidPipeArgumentError(CurrencyPipe, error);
+ }
+
+ return str;
}
}
/**
* @ngModule CommonModule
* @whatItDoes Formats a number as currency using locale rules.
- * @howToUse `number_expression | currency[:currencyCode[:symbolDisplay[:digitInfo]]]`
+ * @howToUse `number_expression | currency[:currencyCode[:symbolDisplay[:digitInfo[:locale]]]]`
* @description
*
* Use `currency` to format a number as currency.
*
* - `currencyCode` is the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code, such
* as `USD` for the US dollar and `EUR` for the euro.
- * - `symbolDisplay` is a boolean indicating whether to use the currency symbol or code.
- * - `true`: use symbol (e.g. `$`).
- * - `false`(default): use code (e.g. `USD`).
+ * - `display` indicates whether to show the currency symbol or the code.
+ * - `code`(default): use code (e.g. `USD`).
+ * - `symbol`: use symbol (e.g. `$`).
+ * - `symbol-narrow`: some countries have two symbols for their currency, one regular and one
+ * narrow (e.g. the canadian dollar CAD has the symbol `CA$` and the symbol-narrow `$`).
+ * If there is no narrow symbol for the chosen currency, the regular symbol will be used.
* - `digitInfo` See {@link DecimalPipe} for detailed description.
- *
- * WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
- * and may require a polyfill. See [Browser Support](guide/browser-support) for details.
+ * - `locale` is a `string` defining the locale to use (uses the current {@link LOCALE_ID} by
+ * default)
*
* ### Example
*
@@ -153,22 +125,35 @@ export class CurrencyPipe implements PipeTransform {
constructor(@Inject(LOCALE_ID) private _locale: string) {}
transform(
- value: any, currencyCode: string = 'USD', symbolDisplay: boolean = false,
- digits?: string): string|null {
- return formatNumber(
- CurrencyPipe, this._locale, value, NumberFormatStyle.Currency, digits, currencyCode,
- symbolDisplay);
- }
-}
+ value: any, currencyCode?: string, symbolDisplay: 'code'|'symbol'|'symbol-narrow' = 'symbol',
+ digits?: string, locale?: string): string|null {
+ if (isEmpty(value)) return null;
+
+ locale = locale || this._locale;
+
+ if (typeof symbolDisplay === 'boolean') {
+ if (console && console.warn) {
+ console.warn(
+ `Warning: the currency pipe has been changed in Angular v5. The symbolDisplay option (third parameter) is now a string instead of a boolean. The accepted values are "code", "symbol" or "symbol-narrow".`);
+ }
+ symbolDisplay = symbolDisplay ? 'symbol' : 'code';
+ }
+
+ let currency = currencyCode || 'USD';
+ if (symbolDisplay !== 'code') {
+ currency = findCurrencySymbol(currency, symbolDisplay === 'symbol' ? 'wide' : 'narrow');
+ }
+
+ const {str, error} = formatNumber(value, locale, NumberFormatStyle.Currency, digits, currency);
+
+ if (error) {
+ throw invalidPipeArgumentError(CurrencyPipe, error);
+ }
-function parseIntAutoRadix(text: string): number {
- const result: number = parseInt(text);
- if (isNaN(result)) {
- throw new Error('Invalid integer literal when parsing ' + text);
+ return str;
}
- return result;
}
-export function isNumeric(value: any): boolean {
- return !isNaN(value - parseFloat(value));
+function isEmpty(value: any): boolean {
+ return value == null || value === '' || value !== value;
}
diff --git a/packages/common/test/i18n/locale_data_api_spec.ts b/packages/common/test/i18n/locale_data_api_spec.ts
new file mode 100644
index 00000000000000..44620c022ef842
--- /dev/null
+++ b/packages/common/test/i18n/locale_data_api_spec.ts
@@ -0,0 +1,51 @@
+/**
+ * @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
+ */
+
+import localeCaESVALENCIA from '../../i18n_data/locale_ca-ES-VALENCIA';
+import localeEn from '../../i18n_data/locale_en';
+import localeFr from '../../i18n_data/locale_fr';
+import localeFrCA from '../../i18n_data/locale_fr-CA';
+import {registerLocaleData, findLocaleData} from '../../src/i18n/locale_data_api';
+
+export function main() {
+ describe('locale data api', () => {
+ beforeAll(() => {
+ registerLocaleData(localeCaESVALENCIA);
+ registerLocaleData(localeEn);
+ registerLocaleData(localeFr);
+ registerLocaleData(localeFrCA);
+ });
+
+ describe('findLocaleData', () => {
+ it('should throw if the locale provided is not a valid LOCALE_ID', () => {
+ expect(() => findLocaleData('invalid'))
+ .toThrow(new Error(
+ `"invalid" is not a valid LOCALE_ID value. See https://github.com/unicode-cldr/cldr-core/blob/master/availableLocales.json for a list of valid locales`));
+ });
+
+ it('should throw if the LOCALE_DATA for the chosen locale if not available', () => {
+ expect(() => findLocaleData('fr-BE'))
+ .toThrowError(/Missing locale data for the locale "fr-BE"/);
+ });
+
+ it('should return english data if the locale is en-US',
+ () => { expect(findLocaleData('en-US')).toEqual(localeEn); });
+
+ it('should return the exact LOCALE_DATA if it is available',
+ () => { expect(findLocaleData('fr-CA')).toEqual(localeFrCA); });
+
+ it('should return the parent LOCALE_DATA if it exists and exact locale is not available',
+ () => { expect(findLocaleData('fr-FR')).toEqual(localeFr); });
+
+ it(`should find the LOCALE_DATA even if the locale id is badly formatted`, () => {
+ expect(findLocaleData('ca-ES-VALENCIA')).toEqual(localeCaESVALENCIA);
+ expect(findLocaleData('CA_es_Valencia')).toEqual(localeCaESVALENCIA);
+ });
+ });
+ });
+}
diff --git a/packages/common/test/localization_spec.ts b/packages/common/test/i18n/localization_spec.ts
similarity index 93%
rename from packages/common/test/localization_spec.ts
rename to packages/common/test/i18n/localization_spec.ts
index 08e7792e8e4777..069b963492a7b7 100644
--- a/packages/common/test/localization_spec.ts
+++ b/packages/common/test/i18n/localization_spec.ts
@@ -6,13 +6,23 @@
* found in the LICENSE file at https://angular.io/license
*/
+import localeRo from '../../i18n_data/locale_ro';
+import localeSr from '../../i18n_data/locale_sr';
+import localeZgh from '../../i18n_data/locale_zgh';
+import localeFr from '../../i18n_data/locale_fr';
import {LOCALE_ID} from '@angular/core';
import {TestBed, inject} from '@angular/core/testing';
-
-import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../src/localization';
+import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../../src/i18n/localization';
+import {registerLocaleData} from '../../src/i18n/locale_data_api';
export function main() {
describe('l10n', () => {
+ beforeAll(() => {
+ registerLocaleData(localeRo);
+ registerLocaleData(localeSr);
+ registerLocaleData(localeZgh);
+ registerLocaleData(localeFr);
+ });
describe('NgLocalization', () => {
describe('ro', () => {
diff --git a/packages/common/test/pipes/date_pipe_spec.ts b/packages/common/test/pipes/date_pipe_spec.ts
index 85a997bcc13c0c..2094d35bd8b854 100644
--- a/packages/common/test/pipes/date_pipe_spec.ts
+++ b/packages/common/test/pipes/date_pipe_spec.ts
@@ -6,10 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {DatePipe} from '@angular/common';
+import {DatePipe, registerLocaleData} from '@angular/common';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector';
-import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
+import localeEn from '../../i18n_data/locale_en';
+import localeEnExtra from '../../i18n_data/extra/locale_en';
+import localeDe from '../../i18n_data/locale_de';
+import localeHu from '../../i18n_data/locale_hu';
+import localeSr from '../../i18n_data/locale_sr';
+import localeTh from '../../i18n_data/locale_th';
export function main() {
describe('DatePipe', () => {
@@ -22,16 +27,16 @@ export function main() {
expect(pipe.transform(date, pattern)).toEqual(output);
}
- // TODO: reactivate the disabled expectations once emulators are fixed in SauceLabs
- // In some old versions of Chrome in Android emulators, time formatting returns dates in the
- // timezone of the VM host,
- // instead of the device timezone. Same symptoms as
- // https://bugs.chromium.org/p/chromium/issues/detail?id=406382
- // This happens locally and in SauceLabs, so some checks are disabled to avoid failures.
- // Tracking issue: https://github.com/angular/angular/issues/11187
+ beforeAll(() => {
+ registerLocaleData(localeEn, localeEnExtra);
+ registerLocaleData(localeDe);
+ registerLocaleData(localeHu);
+ registerLocaleData(localeSr);
+ registerLocaleData(localeTh);
+ });
beforeEach(() => {
- date = new Date(2015, 5, 15, 9, 3, 1);
+ date = new Date(2015, 5, 15, 9, 3, 1, 550);
pipe = new DatePipe('en-US');
});
@@ -67,71 +72,149 @@ export function main() {
describe('transform', () => {
it('should format each component correctly', () => {
const dateFixtures: any = {
- 'y': '2015',
- 'yy': '15',
- 'M': '6',
- 'MM': '06',
- 'MMM': 'Jun',
- 'MMMM': 'June',
- 'd': '15',
- 'dd': '15',
- 'EEE': 'Mon',
- 'EEEE': 'Monday'
+ G: 'AD',
+ GG: 'AD',
+ GGG: 'AD',
+ GGGG: 'Anno Domini',
+ GGGGG: 'A',
+ y: '2015',
+ yy: '15',
+ yyy: '2015',
+ yyyy: '2015',
+ M: '6',
+ MM: '06',
+ MMM: 'Jun',
+ MMMM: 'June',
+ MMMMM: 'J',
+ L: '6',
+ LL: '06',
+ LLL: 'Jun',
+ LLLL: 'June',
+ LLLLL: 'J',
+ w: '25',
+ ww: '25',
+ W: '3',
+ d: '15',
+ dd: '15',
+ E: 'Mon',
+ EE: 'Mon',
+ EEE: 'Mon',
+ EEEE: 'Monday',
+ EEEEEE: 'Mo',
+ h: '9',
+ hh: '09',
+ H: '9',
+ HH: '09',
+ m: '3',
+ mm: '03',
+ s: '1',
+ ss: '01',
+ S: '6',
+ SS: '55',
+ SSS: '550',
+ a: 'AM',
+ aa: 'AM',
+ aaa: 'AM',
+ aaaa: 'AM',
+ aaaaa: 'a',
+ b: 'morning',
+ bb: 'morning',
+ bbb: 'morning',
+ bbbb: 'morning',
+ bbbbb: 'morning',
+ B: 'in the morning',
+ BB: 'in the morning',
+ BBB: 'in the morning',
+ BBBB: 'in the morning',
+ BBBBB: 'in the morning',
};
const isoStringWithoutTimeFixtures: any = {
- 'y': '2015',
- 'yy': '15',
- 'M': '1',
- 'MM': '01',
- 'MMM': 'Jan',
- 'MMMM': 'January',
- 'd': '1',
- 'dd': '01',
- 'EEE': 'Thu',
- 'EEEE': 'Thursday'
+ G: 'AD',
+ GG: 'AD',
+ GGG: 'AD',
+ GGGG: 'Anno Domini',
+ GGGGG: 'A',
+ y: '2015',
+ yy: '15',
+ yyy: '2015',
+ yyyy: '2015',
+ M: '1',
+ MM: '01',
+ MMM: 'Jan',
+ MMMM: 'January',
+ MMMMM: 'J',
+ L: '1',
+ LL: '01',
+ LLL: 'Jan',
+ LLLL: 'January',
+ LLLLL: 'J',
+ w: '1',
+ ww: '01',
+ W: '1',
+ d: '1',
+ dd: '01',
+ E: 'Thu',
+ EE: 'Thu',
+ EEE: 'Thu',
+ EEEE: 'Thursday',
+ EEEEE: 'T',
+ EEEEEE: 'Th',
+ h: '12',
+ hh: '12',
+ H: '0',
+ HH: '00',
+ m: '0',
+ mm: '00',
+ s: '0',
+ ss: '00',
+ S: '0',
+ SS: '00',
+ SSS: '000',
+ a: 'AM',
+ aa: 'AM',
+ aaa: 'AM',
+ aaaa: 'AM',
+ aaaaa: 'a',
+ b: 'midnight',
+ bb: 'midnight',
+ bbb: 'midnight',
+ bbbb: 'midnight',
+ bbbbb: 'midnight',
+ B: 'midnight',
+ BB: 'midnight',
+ BBB: 'midnight',
+ BBBB: 'midnight',
+ BBBBB: 'mi',
};
- if (!browserDetection.isOldChrome) {
- dateFixtures['h'] = '9';
- dateFixtures['hh'] = '09';
- dateFixtures['j'] = '9 AM';
- isoStringWithoutTimeFixtures['h'] = '12';
- isoStringWithoutTimeFixtures['hh'] = '12';
- isoStringWithoutTimeFixtures['j'] = '12 AM';
- }
-
- // IE and Edge can't format a date to minutes and seconds without hours
- if (!browserDetection.isEdge && !browserDetection.isIE ||
- !browserDetection.supportsNativeIntlApi) {
- if (!browserDetection.isOldChrome) {
- dateFixtures['HH'] = '09';
- isoStringWithoutTimeFixtures['HH'] = '00';
- }
- dateFixtures['E'] = 'M';
- dateFixtures['L'] = 'J';
- dateFixtures['m'] = '3';
- dateFixtures['s'] = '1';
- dateFixtures['mm'] = '03';
- dateFixtures['ss'] = '01';
- isoStringWithoutTimeFixtures['m'] = '0';
- isoStringWithoutTimeFixtures['s'] = '0';
- isoStringWithoutTimeFixtures['mm'] = '00';
- isoStringWithoutTimeFixtures['ss'] = '00';
- }
-
Object.keys(dateFixtures).forEach((pattern: string) => {
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
});
- if (!browserDetection.isOldChrome) {
- Object.keys(isoStringWithoutTimeFixtures).forEach((pattern: string) => {
- expectDateFormatAs(
- isoStringWithoutTime, pattern, isoStringWithoutTimeFixtures[pattern]);
- });
- }
+ Object.keys(isoStringWithoutTimeFixtures).forEach((pattern: string) => {
+ expectDateFormatAs(isoStringWithoutTime, pattern, isoStringWithoutTimeFixtures[pattern]);
+ });
+ });
- expect(pipe.transform(date, 'Z')).toBeDefined();
+ it('should format with timezones', () => {
+ const dateFixtures: any = {
+ z: /GMT(\+|-)\d/,
+ zz: /GMT(\+|-)\d/,
+ zzz: /GMT(\+|-)\d/,
+ zzzz: /GMT(\+|-)\d{2}\:30/,
+ Z: /(\+|-)\d{2}30/,
+ ZZ: /(\+|-)\d{2}30/,
+ ZZZ: /(\+|-)\d{2}30/,
+ ZZZZ: /GMT(\+|-)\d{2}\:30/,
+ ZZZZZ: /(\+|-)\d{2}\:30/,
+ O: /GMT(\+|-)\d/,
+ OOOO: /GMT(\+|-)\d{2}\:30/,
+ };
+
+ Object.keys(dateFixtures).forEach((pattern: string) => {
+ expect(pipe.transform(date, pattern, '+0430')).toMatch(dateFixtures[pattern]);
+ });
});
it('should format common multi component patterns', () => {
@@ -144,19 +227,13 @@ export function main() {
'yMEEEd': '20156Mon15',
'MEEEd': '6Mon15',
'MMMd': 'Jun15',
- 'yMMMMEEEEd': 'Monday, June 15, 2015'
+ 'EEEE, MMMM d, y': 'Monday, June 15, 2015',
+ 'H:mm a': '9:03 AM',
+ 'ms': '31',
+ 'MM/dd/yy hh:mm': '06/15/15 09:03',
+ 'MM/dd/y': '06/15/2015'
};
- // IE and Edge can't format a date to minutes and seconds without hours
- if (!browserDetection.isEdge && !browserDetection.isIE ||
- !browserDetection.supportsNativeIntlApi) {
- dateFixtures['ms'] = '31';
- }
-
- if (!browserDetection.isOldChrome) {
- dateFixtures['jm'] = '9:03 AM';
- }
-
Object.keys(dateFixtures).forEach((pattern: string) => {
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
});
@@ -166,33 +243,23 @@ export function main() {
it('should format with pattern aliases', () => {
const dateFixtures: any = {
'MM/dd/yyyy': '06/15/2015',
- 'fullDate': 'Monday, June 15, 2015',
- 'longDate': 'June 15, 2015',
- 'mediumDate': 'Jun 15, 2015',
- 'shortDate': '6/15/2015'
+ shortDate: '6/15/15',
+ mediumDate: 'Jun 15, 2015',
+ longDate: 'June 15, 2015',
+ fullDate: 'Monday, June 15, 2015',
+ short: '6/15/15, 9:03 AM',
+ medium: 'Jun 15, 2015, 9:03:01 AM',
+ long: /June 15, 2015 at 9:03:01 AM GMT(\+|-)\d/,
+ full: /Monday, June 15, 2015 at 9:03:01 AM GMT(\+|-)\d{2}:\d{2}/,
+ shortTime: '9:03 AM',
+ mediumTime: '9:03:01 AM',
+ longTime: /9:03:01 AM GMT(\+|-)\d/,
+ fullTime: /9:03:01 AM GMT(\+|-)\d{2}:\d{2}/,
};
- if (!browserDetection.isOldChrome) {
- // IE and Edge do not add a coma after the year in these 2 cases
- if ((browserDetection.isEdge || browserDetection.isIE) &&
- browserDetection.supportsNativeIntlApi) {
- dateFixtures['medium'] = 'Jun 15, 2015 9:03:01 AM';
- dateFixtures['short'] = '6/15/2015 9:03 AM';
- } else {
- dateFixtures['medium'] = 'Jun 15, 2015, 9:03:01 AM';
- dateFixtures['short'] = '6/15/2015, 9:03 AM';
- }
- }
-
- if (!browserDetection.isOldChrome) {
- dateFixtures['mediumTime'] = '9:03:01 AM';
- dateFixtures['shortTime'] = '9:03 AM';
- }
-
Object.keys(dateFixtures).forEach((pattern: string) => {
- expectDateFormatAs(date, pattern, dateFixtures[pattern]);
+ expect(pipe.transform(date, pattern)).toMatch(dateFixtures[pattern]);
});
-
});
it('should format invalid in IE ISO date',
@@ -201,8 +268,39 @@ export function main() {
it('should format invalid in Safari ISO date',
() => expect(pipe.transform('2017-01-20T19:00:00+0000')).toEqual('Jan 20, 2017'));
+ // test for the following bugs:
+ // https://github.com/angular/angular/issues/9524
+ // https://github.com/angular/angular/issues/9524
+ it('should format correctly with iso strings that contain time',
+ () => expect(pipe.transform('2017-05-07T22:14:39', 'dd-MM-yyyy HH:mm'))
+ .toMatch(/07-05-2017 \d{2}:\d{2}/));
+
+ // test for the following bugs:
+ // https://github.com/angular/angular/issues/16624
+ // https://github.com/angular/angular/issues/17478
+ it('should show the correct time when the timezone is fixed', () => {
+ expect(pipe.transform('2017-06-13T10:14:39+0000', 'shortTime', '+0000'))
+ .toEqual('10:14 AM');
+ expect(pipe.transform('2017-06-13T10:14:39+0000', 'h:mm a', '+0000')).toEqual('10:14 AM');
+ });
+
it('should remove bidi control characters',
() => expect(pipe.transform(date, 'MM/dd/yyyy') !.length).toEqual(10));
+
+ it(`should format the date correctly in various locales`, () => {
+ expect(new DatePipe('de').transform(date, 'short')).toEqual('15.06.15, 09:03');
+ expect(new DatePipe('th').transform(date, 'dd-MM-yy')).toEqual('15-06-15');
+ expect(new DatePipe('hu').transform(date, 'a')).toEqual('de.');
+ expect(new DatePipe('sr').transform(date, 'a')).toEqual('пре подне');
+
+ // TODO(ocombe): activate this test when we support local numbers
+ // expect(new DatePipe('mr', [localeMr]).transform(date, 'hh')).toEqual('०९');
+ });
+
+ it('should throw if we use getExtraDayPeriods without loading extra locale data', () => {
+ expect(() => new DatePipe('de').transform(date, 'b'))
+ .toThrowError(/Missing extra locale data for the locale "de"/);
+ });
});
});
}
diff --git a/packages/common/test/pipes/deprecated/date_pipe_spec.ts b/packages/common/test/pipes/deprecated/date_pipe_spec.ts
new file mode 100644
index 00000000000000..d1bbc586ac7238
--- /dev/null
+++ b/packages/common/test/pipes/deprecated/date_pipe_spec.ts
@@ -0,0 +1,208 @@
+/**
+ * @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
+ */
+
+import {DeprecatedDatePipe} from '@angular/common';
+import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
+import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector';
+import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
+
+export function main() {
+ describe('DeprecatedDatePipe', () => {
+ let date: Date;
+ const isoStringWithoutTime = '2015-01-01';
+ let pipe: DeprecatedDatePipe;
+
+ // Check the transformation of a date into a pattern
+ function expectDateFormatAs(date: Date | string, pattern: any, output: string): void {
+ expect(pipe.transform(date, pattern)).toEqual(output);
+ }
+
+ // TODO: reactivate the disabled expectations once emulators are fixed in SauceLabs
+ // In some old versions of Chrome in Android emulators, time formatting returns dates in the
+ // timezone of the VM host,
+ // instead of the device timezone. Same symptoms as
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=406382
+ // This happens locally and in SauceLabs, so some checks are disabled to avoid failures.
+ // Tracking issue: https://github.com/angular/angular/issues/11187
+
+ beforeEach(() => {
+ date = new Date(2015, 5, 15, 9, 3, 1);
+ pipe = new DeprecatedDatePipe('en-US');
+ });
+
+ it('should be marked as pure', () => {
+ expect(new PipeResolver(new JitReflector()).resolve(DeprecatedDatePipe) !.pure).toEqual(true);
+ });
+
+ describe('supports', () => {
+ it('should support date', () => { expect(() => pipe.transform(date)).not.toThrow(); });
+
+ it('should support int', () => { expect(() => pipe.transform(123456789)).not.toThrow(); });
+
+ it('should support numeric strings',
+ () => { expect(() => pipe.transform('123456789')).not.toThrow(); });
+
+ it('should support decimal strings',
+ () => { expect(() => pipe.transform('123456789.11')).not.toThrow(); });
+
+ it('should support ISO string',
+ () => expect(() => pipe.transform('2015-06-15T21:43:11Z')).not.toThrow());
+
+ it('should return null for empty string', () => expect(pipe.transform('')).toEqual(null));
+
+ it('should return null for NaN', () => expect(pipe.transform(Number.NaN)).toEqual(null));
+
+ it('should support ISO string without time',
+ () => { expect(() => pipe.transform(isoStringWithoutTime)).not.toThrow(); });
+
+ it('should not support other objects',
+ () => expect(() => pipe.transform({})).toThrowError(/InvalidPipeArgument/));
+ });
+
+ describe('transform', () => {
+ it('should format each component correctly', () => {
+ const dateFixtures: any = {
+ 'y': '2015',
+ 'yy': '15',
+ 'M': '6',
+ 'MM': '06',
+ 'MMM': 'Jun',
+ 'MMMM': 'June',
+ 'd': '15',
+ 'dd': '15',
+ 'EEE': 'Mon',
+ 'EEEE': 'Monday'
+ };
+
+ const isoStringWithoutTimeFixtures: any = {
+ 'y': '2015',
+ 'yy': '15',
+ 'M': '1',
+ 'MM': '01',
+ 'MMM': 'Jan',
+ 'MMMM': 'January',
+ 'd': '1',
+ 'dd': '01',
+ 'EEE': 'Thu',
+ 'EEEE': 'Thursday'
+ };
+
+ if (!browserDetection.isOldChrome) {
+ dateFixtures['h'] = '9';
+ dateFixtures['hh'] = '09';
+ dateFixtures['j'] = '9 AM';
+ isoStringWithoutTimeFixtures['h'] = '12';
+ isoStringWithoutTimeFixtures['hh'] = '12';
+ isoStringWithoutTimeFixtures['j'] = '12 AM';
+ }
+
+ // IE and Edge can't format a date to minutes and seconds without hours
+ if (!browserDetection.isEdge && !browserDetection.isIE ||
+ !browserDetection.supportsNativeIntlApi) {
+ if (!browserDetection.isOldChrome) {
+ dateFixtures['HH'] = '09';
+ isoStringWithoutTimeFixtures['HH'] = '00';
+ }
+ dateFixtures['E'] = 'M';
+ dateFixtures['L'] = 'J';
+ dateFixtures['m'] = '3';
+ dateFixtures['s'] = '1';
+ dateFixtures['mm'] = '03';
+ dateFixtures['ss'] = '01';
+ isoStringWithoutTimeFixtures['m'] = '0';
+ isoStringWithoutTimeFixtures['s'] = '0';
+ isoStringWithoutTimeFixtures['mm'] = '00';
+ isoStringWithoutTimeFixtures['ss'] = '00';
+ }
+
+ Object.keys(dateFixtures).forEach((pattern: string) => {
+ expectDateFormatAs(date, pattern, dateFixtures[pattern]);
+ });
+
+ if (!browserDetection.isOldChrome) {
+ Object.keys(isoStringWithoutTimeFixtures).forEach((pattern: string) => {
+ expectDateFormatAs(
+ isoStringWithoutTime, pattern, isoStringWithoutTimeFixtures[pattern]);
+ });
+ }
+
+ expect(pipe.transform(date, 'Z')).toBeDefined();
+ });
+
+ it('should format common multi component patterns', () => {
+ const dateFixtures: any = {
+ 'EEE, M/d/y': 'Mon, 6/15/2015',
+ 'EEE, M/d': 'Mon, 6/15',
+ 'MMM d': 'Jun 15',
+ 'dd/MM/yyyy': '15/06/2015',
+ 'MM/dd/yyyy': '06/15/2015',
+ 'yMEEEd': '20156Mon15',
+ 'MEEEd': '6Mon15',
+ 'MMMd': 'Jun15',
+ 'yMMMMEEEEd': 'Monday, June 15, 2015'
+ };
+
+ // IE and Edge can't format a date to minutes and seconds without hours
+ if (!browserDetection.isEdge && !browserDetection.isIE ||
+ !browserDetection.supportsNativeIntlApi) {
+ dateFixtures['ms'] = '31';
+ }
+
+ if (!browserDetection.isOldChrome) {
+ dateFixtures['jm'] = '9:03 AM';
+ }
+
+ Object.keys(dateFixtures).forEach((pattern: string) => {
+ expectDateFormatAs(date, pattern, dateFixtures[pattern]);
+ });
+
+ });
+
+ it('should format with pattern aliases', () => {
+ const dateFixtures: any = {
+ 'MM/dd/yyyy': '06/15/2015',
+ 'fullDate': 'Monday, June 15, 2015',
+ 'longDate': 'June 15, 2015',
+ 'mediumDate': 'Jun 15, 2015',
+ 'shortDate': '6/15/2015'
+ };
+
+ if (!browserDetection.isOldChrome) {
+ // IE and Edge do not add a coma after the year in these 2 cases
+ if ((browserDetection.isEdge || browserDetection.isIE) &&
+ browserDetection.supportsNativeIntlApi) {
+ dateFixtures['medium'] = 'Jun 15, 2015 9:03:01 AM';
+ dateFixtures['short'] = '6/15/2015 9:03 AM';
+ } else {
+ dateFixtures['medium'] = 'Jun 15, 2015, 9:03:01 AM';
+ dateFixtures['short'] = '6/15/2015, 9:03 AM';
+ }
+ }
+
+ if (!browserDetection.isOldChrome) {
+ dateFixtures['mediumTime'] = '9:03:01 AM';
+ dateFixtures['shortTime'] = '9:03 AM';
+ }
+
+ Object.keys(dateFixtures).forEach((pattern: string) => {
+ expectDateFormatAs(date, pattern, dateFixtures[pattern]);
+ });
+
+ });
+
+ it('should format invalid in IE ISO date',
+ () => expect(pipe.transform('2017-01-11T09:25:14.014-0500')).toEqual('Jan 11, 2017'));
+
+ it('should format invalid in Safari ISO date',
+ () => expect(pipe.transform('2017-01-20T19:00:00+0000')).toEqual('Jan 20, 2017'));
+
+ it('should remove bidi control characters',
+ () => expect(pipe.transform(date, 'MM/dd/yyyy') !.length).toEqual(10));
+ });
+ });
+}
diff --git a/packages/common/test/pipes/deprecated/number_pipe_spec.ts b/packages/common/test/pipes/deprecated/number_pipe_spec.ts
new file mode 100644
index 00000000000000..a0fb5edfc7cad1
--- /dev/null
+++ b/packages/common/test/pipes/deprecated/number_pipe_spec.ts
@@ -0,0 +1,109 @@
+/**
+ * @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
+ */
+
+import {DeprecatedCurrencyPipe, DeprecatedDecimalPipe, DeprecatedPercentPipe} from '@angular/common';
+import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
+import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
+
+export function main() {
+ function isNumeric(value: any): boolean { return !isNaN(value - parseFloat(value)); }
+
+ // Between the symbol and the number, Edge adds a no breaking space and IE11 adds a standard space
+ function normalize(s: string): string { return s.replace(/\u00A0| /g, ''); }
+
+ describe('Number pipes', () => {
+ describe('DeprecatedDecimalPipe', () => {
+ let pipe: DeprecatedDecimalPipe;
+
+ beforeEach(() => { pipe = new DeprecatedDecimalPipe('en-US'); });
+
+ describe('transform', () => {
+ it('should return correct value for numbers', () => {
+ expect(pipe.transform(12345)).toEqual('12,345');
+ expect(pipe.transform(123, '.2')).toEqual('123.00');
+ expect(pipe.transform(1, '3.')).toEqual('001');
+ expect(pipe.transform(1.1, '3.4-5')).toEqual('001.1000');
+ expect(pipe.transform(1.123456, '3.4-5')).toEqual('001.12346');
+ expect(pipe.transform(1.1234)).toEqual('1.123');
+ });
+
+ it('should support strings', () => {
+ expect(pipe.transform('12345')).toEqual('12,345');
+ expect(pipe.transform('123', '.2')).toEqual('123.00');
+ expect(pipe.transform('1', '3.')).toEqual('001');
+ expect(pipe.transform('1.1', '3.4-5')).toEqual('001.1000');
+ expect(pipe.transform('1.123456', '3.4-5')).toEqual('001.12346');
+ expect(pipe.transform('1.1234')).toEqual('1.123');
+ });
+
+ it('should not support other objects', () => {
+ expect(() => pipe.transform(new Object())).toThrowError();
+ expect(() => pipe.transform('123abc')).toThrowError();
+ });
+ });
+ });
+
+ describe('DeprecatedPercentPipe', () => {
+ let pipe: DeprecatedPercentPipe;
+
+ beforeEach(() => { pipe = new DeprecatedPercentPipe('en-US'); });
+
+ describe('transform', () => {
+ it('should return correct value for numbers', () => {
+ expect(normalize(pipe.transform(1.23) !)).toEqual('123%');
+ expect(normalize(pipe.transform(1.2, '.2') !)).toEqual('120.00%');
+ });
+
+ it('should not support other objects',
+ () => { expect(() => pipe.transform(new Object())).toThrowError(); });
+ });
+ });
+
+ describe('DeprecatedCurrencyPipe', () => {
+ let pipe: DeprecatedCurrencyPipe;
+
+ beforeEach(() => { pipe = new DeprecatedCurrencyPipe('en-US'); });
+
+ describe('transform', () => {
+ it('should return correct value for numbers', () => {
+ // In old Chrome, default formatiing for USD is different
+ if (browserDetection.isOldChrome) {
+ expect(normalize(pipe.transform(123) !)).toEqual('USD123');
+ } else {
+ expect(normalize(pipe.transform(123) !)).toEqual('USD123.00');
+ }
+ expect(normalize(pipe.transform(12, 'EUR', false, '.1') !)).toEqual('EUR12.0');
+ expect(normalize(pipe.transform(5.1234, 'USD', false, '.0-3') !)).toEqual('USD5.123');
+ });
+
+ it('should not support other objects',
+ () => { expect(() => pipe.transform(new Object())).toThrowError(); });
+ });
+ });
+
+ describe('isNumeric', () => {
+ it('should return true when passing correct numeric string',
+ () => { expect(isNumeric('2')).toBe(true); });
+
+ it('should return true when passing correct double string',
+ () => { expect(isNumeric('1.123')).toBe(true); });
+
+ it('should return true when passing correct negative string',
+ () => { expect(isNumeric('-2')).toBe(true); });
+
+ it('should return true when passing correct scientific notation string',
+ () => { expect(isNumeric('1e5')).toBe(true); });
+
+ it('should return false when passing incorrect numeric',
+ () => { expect(isNumeric('a')).toBe(false); });
+
+ it('should return false when passing parseable but non numeric',
+ () => { expect(isNumeric('2a')).toBe(false); });
+ });
+ });
+}
diff --git a/packages/common/test/pipes/number_pipe_spec.ts b/packages/common/test/pipes/number_pipe_spec.ts
index 85cd2515278c23..9721613b5eb3e3 100644
--- a/packages/common/test/pipes/number_pipe_spec.ts
+++ b/packages/common/test/pipes/number_pipe_spec.ts
@@ -6,19 +6,25 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {CurrencyPipe, DecimalPipe, PercentPipe} from '@angular/common';
-import {isNumeric} from '@angular/common/src/pipes/number_pipe';
+import localeEn from '../../i18n_data/locale_en';
+import localeEsUS from '../../i18n_data/locale_es-US';
+import {registerLocaleData, CurrencyPipe, DecimalPipe, PercentPipe} from '@angular/common';
import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
-import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
export function main() {
describe('Number pipes', () => {
- describe('DecimalPipe', () => {
- let pipe: DecimalPipe;
+ beforeAll(() => {
+ registerLocaleData(localeEn);
+ registerLocaleData(localeEsUS);
+ });
- beforeEach(() => { pipe = new DecimalPipe('en-US'); });
+ function isNumeric(value: any): boolean { return !isNaN(value - parseFloat(value)); }
+ describe('DecimalPipe', () => {
describe('transform', () => {
+ let pipe: DecimalPipe;
+ beforeEach(() => { pipe = new DecimalPipe('en-US'); });
+
it('should return correct value for numbers', () => {
expect(pipe.transform(12345)).toEqual('12,345');
expect(pipe.transform(123, '.2')).toEqual('123.00');
@@ -26,6 +32,8 @@ export function main() {
expect(pipe.transform(1.1, '3.4-5')).toEqual('001.1000');
expect(pipe.transform(1.123456, '3.4-5')).toEqual('001.12346');
expect(pipe.transform(1.1234)).toEqual('1.123');
+ expect(pipe.transform(1.123456, '.2')).toEqual('1.123');
+ expect(pipe.transform(1.123456, '.4')).toEqual('1.1235');
});
it('should support strings', () => {
@@ -38,9 +46,20 @@ export function main() {
});
it('should not support other objects', () => {
- expect(() => pipe.transform(new Object())).toThrowError();
+ expect(() => pipe.transform({})).toThrowError();
expect(() => pipe.transform('123abc')).toThrowError();
});
+
+ it('should throw if minFractionDigits is explicitly higher than maxFractionDigits', () => {
+ expect(() => pipe.transform('1.1', '3.4-2')).toThrowError(/is higher than the maximum/);
+ });
+ });
+
+ describe('transform with custom locales', () => {
+ it('should return the correct format for es-US in IE11', () => {
+ const pipe = new DecimalPipe('es-US');
+ expect(pipe.transform('9999999.99', '1.2-2')).toEqual('9,999,999.99');
+ });
});
});
@@ -51,12 +70,12 @@ export function main() {
describe('transform', () => {
it('should return correct value for numbers', () => {
- expect(normalize(pipe.transform(1.23) !)).toEqual('123%');
- expect(normalize(pipe.transform(1.2, '.2') !)).toEqual('120.00%');
+ expect(pipe.transform(1.23)).toEqual('123%');
+ expect(pipe.transform(1.2, '.2')).toEqual('120.00%');
});
it('should not support other objects',
- () => { expect(() => pipe.transform(new Object())).toThrowError(); });
+ () => { expect(() => pipe.transform({})).toThrowError(); });
});
});
@@ -67,18 +86,24 @@ export function main() {
describe('transform', () => {
it('should return correct value for numbers', () => {
- // In old Chrome, default formatiing for USD is different
- if (browserDetection.isOldChrome) {
- expect(normalize(pipe.transform(123) !)).toEqual('USD123');
- } else {
- expect(normalize(pipe.transform(123) !)).toEqual('USD123.00');
- }
- expect(normalize(pipe.transform(12, 'EUR', false, '.1') !)).toEqual('EUR12.0');
- expect(normalize(pipe.transform(5.1234, 'USD', false, '.0-3') !)).toEqual('USD5.123');
+ expect(pipe.transform(123)).toEqual('$123.00');
+ expect(pipe.transform(12, 'EUR', 'code', '.1')).toEqual('EUR12.0');
+ expect(pipe.transform(5.1234, 'USD', 'code', '.0-3')).toEqual('USD5.123');
+ expect(pipe.transform(5.1234, 'USD', 'code')).toEqual('USD5.12');
+ expect(pipe.transform(5.1234, 'USD', 'symbol')).toEqual('$5.12');
+ expect(pipe.transform(5.1234, 'CAD', 'symbol')).toEqual('CA$5.12');
+ expect(pipe.transform(5.1234, 'CAD', 'symbol-narrow')).toEqual('$5.12');
});
it('should not support other objects',
- () => { expect(() => pipe.transform(new Object())).toThrowError(); });
+ () => { expect(() => pipe.transform({})).toThrowError(); });
+
+ it('should warn if you are using the v4 signature', () => {
+ const warnSpy = spyOn(console, 'warn');
+ pipe.transform(123, 'USD', true);
+ expect(warnSpy).toHaveBeenCalledWith(
+ `Warning: the currency pipe has been changed in Angular v5. The symbolDisplay option (third parameter) is now a string instead of a boolean. The accepted values are "code", "symbol" or "symbol-narrow".`);
+ });
});
});
@@ -103,8 +128,3 @@ export function main() {
});
});
}
-
-// Between the symbol and the number, Edge adds a no breaking space and IE11 adds a standard space
-function normalize(s: string): string {
- return s.replace(/\u00A0| /g, '');
-}
diff --git a/packages/examples/common/pipes/ts/date_pipe.ts b/packages/examples/common/pipes/ts/date_pipe.ts
index 5b518524ce58c8..d88b6da9a1c5a3 100644
--- a/packages/examples/common/pipes/ts/date_pipe.ts
+++ b/packages/examples/common/pipes/ts/date_pipe.ts
@@ -14,7 +14,8 @@ import {Component} from '@angular/core';
template: `
Today is {{today | date}}
Or if you prefer, {{today | date:'fullDate'}}
- The time is {{today | date:'jmZ'}}
+ The time is {{today | date:'shortTime'}}
+ The custom date is {{today | date:'yyyy-mm-dd HH:mm'}}
`
})
export class DatePipeComponent {
diff --git a/packages/examples/common/pipes/ts/number_pipe.ts b/packages/examples/common/pipes/ts/number_pipe.ts
index aa9d595db1cb10..96056b9ffdcb19 100644
--- a/packages/examples/common/pipes/ts/number_pipe.ts
+++ b/packages/examples/common/pipes/ts/number_pipe.ts
@@ -42,8 +42,9 @@ export class PercentPipeComponent {
@Component({
selector: 'currency-pipe',
template: `
- A: {{a | currency:'USD':false}}
- B: {{b | currency:'USD':true:'4.2-2'}}
+ A: {{a | currency:'CAD'}}
+ B: {{b | currency:'CAD':'symbol':'4.2-2'}}
+ B: {{b | currency:'CAD':'symbol-narrow':'4.2-2'}}
`
})
export class CurrencyPipeComponent {
diff --git a/packages/tsconfig.json b/packages/tsconfig.json
index 10ec247ee07d15..d951d649f12e3f 100644
--- a/packages/tsconfig.json
+++ b/packages/tsconfig.json
@@ -27,6 +27,7 @@
"exclude": [
"compiler-cli/integrationtest",
"platform-server/integrationtest",
- "tsc-wrapped"
+ "tsc-wrapped",
+ "common/i18n_data"
]
}
diff --git a/tools/public_api_guard/common/common.d.ts b/tools/public_api_guard/common/common.d.ts
index cdefdf3d960e5e..f41ae58da9919b 100644
--- a/tools/public_api_guard/common/common.d.ts
+++ b/tools/public_api_guard/common/common.d.ts
@@ -11,24 +11,60 @@ export declare class AsyncPipe implements OnDestroy, PipeTransform {
transform(obj: null): null;
}
+/** @experimental */
+export declare const AVAILABLE_LOCALES: string[];
+
/** @stable */
export declare class CommonModule {
}
+/** @experimental */
+export declare const CURRENCIES: {
+ [code: string]: (string | undefined)[];
+};
+
/** @stable */
export declare class CurrencyPipe implements PipeTransform {
constructor(_locale: string);
- transform(value: any, currencyCode?: string, symbolDisplay?: boolean, digits?: string): string | null;
+ transform(value: any, currencyCode?: string, symbolDisplay?: 'code' | 'symbol' | 'symbol-narrow', digits?: string, locale?: string): string | null;
}
/** @stable */
export declare class DatePipe implements PipeTransform {
+ constructor(locale: string);
+ transform(value: any, format?: string, timezone?: string, locale?: string): string | null;
+}
+
+/** @stable */
+export declare class DecimalPipe implements PipeTransform {
+ constructor(_locale: string);
+ transform(value: any, digits?: string, locale?: string): string | null;
+}
+
+/** @stable */
+export declare class DeprecatedCurrencyPipe implements PipeTransform {
+ constructor(_locale: string);
+ transform(value: any, currencyCode?: string, symbolDisplay?: boolean, digits?: string): string | null;
+}
+
+/** @stable */
+export declare class DeprecatedDatePipe implements PipeTransform {
constructor(_locale: string);
transform(value: any, pattern?: string): string | null;
}
/** @stable */
-export declare class DecimalPipe implements PipeTransform {
+export declare class DeprecatedDecimalPipe implements PipeTransform {
+ constructor(_locale: string);
+ transform(value: any, digits?: string): string | null;
+}
+
+/** @deprecated */
+export declare class DeprecatedI18NPipesModule {
+}
+
+/** @stable */
+export declare class DeprecatedPercentPipe implements PipeTransform {
constructor(_locale: string);
transform(value: any, digits?: string): string | null;
}
@@ -40,6 +76,74 @@ export declare class DeprecatedI18NPipesModule {
/** @stable */
export declare const DOCUMENT: InjectionToken;
+/** @experimental */
+export declare function findLocaleData(locale: string): any;
+
+/** @experimental */
+export declare enum FormatWidth {
+ Short = 0,
+ Medium = 1,
+ Long = 2,
+ Full = 3,
+}
+
+/** @experimental */
+export declare enum FormStyle {
+ Format = 0,
+ Standalone = 1,
+}
+
+/** @experimental */
+export declare function getLocaleCurrencyName(locale: string): string | null;
+
+/** @experimental */
+export declare function getLocaleCurrencySymbol(locale: string): string | null;
+
+/** @experimental */
+export declare function getLocaleDateFormat(locale: string, width: FormatWidth): string;
+
+/** @experimental */
+export declare function getLocaleDateTimeFormat(locale: string, width: FormatWidth): string;
+
+/** @experimental */
+export declare function getLocaleDayNames(locale: string, formStyle: FormStyle, width: TranslationWidth): string[];
+
+/** @experimental */
+export declare function getLocaleDayPeriods(locale: string, formStyle: FormStyle, width: TranslationWidth): [string, string];
+
+/** @experimental */
+export declare function getLocaleEraNames(locale: string, width: TranslationWidth): [string, string];
+
+/** @experimental */
+export declare function getLocaleExtraDayPeriodRules(locale: string): (Time | [Time, Time])[];
+
+/** @experimental */
+export declare function getLocaleExtraDayPeriods(locale: string, formStyle: FormStyle, width: TranslationWidth): string[];
+
+/** @experimental */
+export declare function getLocaleFirstDayOfWeek(locale: string): WeekDay;
+
+/** @experimental */
+export declare function getLocaleId(locale: string): string;
+
+/** @experimental */
+export declare function getLocaleMonthNames(locale: string, formStyle: FormStyle, width: TranslationWidth): string[];
+
+/** @experimental */
+export declare function getLocaleNumberFormat(locale: string, type: NumberFormatStyle): string;
+
+/** @experimental */
+export declare function getLocaleNumberSymbol(locale: string, symbol: NumberSymbol): string;
+
+/** @experimental */
+export declare function getLocalePluralCase(locale: string): (value: number) => Plural;
+
+/** @experimental */
+export declare function getLocaleTimeFormat(locale: string, width: FormatWidth): string;
+
+/** @experimental */
+export declare function getLocaleWeekEndRange(locale: string): [WeekDay, WeekDay];
+
/** @stable */
export declare class HashLocationStrategy extends LocationStrategy {
constructor(_platformLocation: PlatformLocation, _baseHref?: string);
@@ -58,7 +162,7 @@ export declare class I18nPluralPipe implements PipeTransform {
constructor(_localization: NgLocalization);
transform(value: number, pluralMap: {
[count: string]: string;
- }): string;
+ }, locale?: string): string;
}
/** @experimental */
@@ -85,6 +189,11 @@ export declare class JsonPipe implements PipeTransform {
transform(value: any): string;
}
+/** @experimental */
+export declare const LOCALE_DATA: {
+ [localeId: string]: any;
+};
+
/** @stable */
export declare class Location {
constructor(platformStrategy: LocationStrategy);
@@ -197,12 +306,12 @@ export declare class NgIfContext {
export declare class NgLocaleLocalization extends NgLocalization {
protected locale: string;
constructor(locale: string);
- getPluralCategory(value: any): string;
+ getPluralCategory(value: any, locale?: string): string;
}
/** @experimental */
export declare abstract class NgLocalization {
- abstract getPluralCategory(value: any): string;
+ abstract getPluralCategory(value: any, locale?: string): string;
}
/** @experimental */
@@ -253,6 +362,31 @@ export declare class NgTemplateOutlet implements OnChanges {
ngOnChanges(changes: SimpleChanges): void;
}
+/** @experimental */
+export declare enum NumberFormatStyle {
+ Decimal = 0,
+ Percent = 1,
+ Currency = 2,
+ Scientific = 3,
+}
+
+/** @experimental */
+export declare enum NumberSymbol {
+ Decimal = 0,
+ Group = 1,
+ List = 2,
+ PercentSign = 3,
+ PlusSign = 4,
+ MinusSign = 5,
+ Exponential = 6,
+ SuperscriptingExponent = 7,
+ PerMille = 8,
+ Infinity = 9,
+ NaN = 10,
+ TimeSeparator = 11,
+ CurrencyGroup = 12,
+}
+
/** @stable */
export declare class PathLocationStrategy extends LocationStrategy {
constructor(_platformLocation: PlatformLocation, href?: string);
@@ -269,7 +403,7 @@ export declare class PathLocationStrategy extends LocationStrategy {
/** @stable */
export declare class PercentPipe implements PipeTransform {
constructor(_locale: string);
- transform(value: any, digits?: string): string | null;
+ transform(value: any, digits?: string, locale?: string): string | null;
}
/** @stable */
@@ -286,6 +420,16 @@ export declare abstract class PlatformLocation {
abstract replaceState(state: any, title: string, url: string): void;
}
+/** @experimental */
+export declare enum Plural {
+ Zero = 0,
+ One = 1,
+ Two = 2,
+ Few = 3,
+ Many = 4,
+ Other = 5,
+}
+
/** @experimental */
export interface PopStateEvent {
pop?: boolean;
@@ -293,16 +437,33 @@ export interface PopStateEvent {
url?: string;
}
+/** @experimental */
+export declare function registerLocaleData(data: any, extraData?: any): void;
+
/** @stable */
export declare class SlicePipe implements PipeTransform {
transform(value: any, start: number, end?: number): any;
}
+/** @experimental */
+export declare type Time = {
+ hours: number;
+ minutes: number;
+};
+
/** @stable */
export declare class TitleCasePipe implements PipeTransform {
transform(value: string): string;
}
+/** @experimental */
+export declare enum TranslationWidth {
+ Narrow = 0,
+ Abbreviated = 1,
+ Wide = 2,
+ Short = 3,
+}
+
/** @stable */
export declare class UpperCasePipe implements PipeTransform {
transform(value: string): string;
@@ -310,3 +471,14 @@ export declare class UpperCasePipe implements PipeTransform {
/** @stable */
export declare const VERSION: Version;
+
+/** @experimental */
+export declare enum WeekDay {
+ Sunday = 0,
+ Monday = 1,
+ Tuesday = 2,
+ Wednesday = 3,
+ Thursday = 4,
+ Friday = 5,
+ Saturday = 6,
+}
|