From ed3f21737413d5929feb6a8a6631177963aa28a1 Mon Sep 17 00:00:00 2001 From: Closure Team Date: Mon, 4 Oct 2021 15:24:51 -0700 Subject: [PATCH] Adds Intl native ECMAScript support for Closure DateTimeFormat. RELNOTES: Add Intl native support in DateTimeFormat. PiperOrigin-RevId: 400828431 Change-Id: I6e0679b14ff4d7496738b04915f7c10bf50b9367 --- closure/goog/i18n/BUILD | 11 + closure/goog/i18n/datetimeformat.js | 403 ++++- closure/goog/i18n/datetimeformat_test.js | 1948 +++++++++++++++------- closure/goog/i18n/localefeature.js | 9 + closure/goog/i18n/nativelocaledigits.js | 40 + closure/goog/i18n/numberformat.js | 47 +- 6 files changed, 1749 insertions(+), 709 deletions(-) create mode 100644 closure/goog/i18n/nativelocaledigits.js diff --git a/closure/goog/i18n/BUILD b/closure/goog/i18n/BUILD index 6af94dd0f6..93fee49d2b 100644 --- a/closure/goog/i18n/BUILD +++ b/closure/goog/i18n/BUILD @@ -102,10 +102,15 @@ closure_js_library( name = "datetimeformat", srcs = ["datetimeformat.js"], deps = [ + ":datetimepatterns", ":datetimesymbols", + ":localefeature", + ":nativelocaledigits", ":timezone", "//closure/goog/asserts", "//closure/goog/date", + "//closure/goog/date:utcdatetime", + "//closure/goog/labs/useragent:browser", "//closure/goog/string", ], ) @@ -187,6 +192,11 @@ closure_js_library( ], ) +closure_js_library( + name = "nativelocaledigits", + srcs = ["nativelocaledigits.js"], +) + closure_js_library( name = "numberformat", srcs = ["numberformat.js"], @@ -194,6 +204,7 @@ closure_js_library( ":compactnumberformatsymbols", ":currency", ":localefeature", + ":nativelocaledigits", ":numberformatsymbols", "//closure/goog/asserts", "//closure/goog/math", diff --git a/closure/goog/i18n/datetimeformat.js b/closure/goog/i18n/datetimeformat.js index c6035a4ef4..6de01061e0 100644 --- a/closure/goog/i18n/datetimeformat.js +++ b/closure/goog/i18n/datetimeformat.js @@ -14,18 +14,55 @@ */ goog.provide('goog.i18n.DateTimeFormat'); goog.provide('goog.i18n.DateTimeFormat.Format'); - goog.require('goog.asserts'); goog.require('goog.date'); +goog.require('goog.date.UtcDateTime'); + goog.require('goog.i18n.DateTimeSymbols'); + +goog.require('goog.i18n.LocaleFeature'); +goog.require('goog.i18n.NativeLocaleDigits'); goog.require('goog.i18n.TimeZone'); goog.require('goog.string'); goog.requireType('goog.i18n.DateTimeSymbolsType'); +goog.scope(function() { +// For referencing modules +const LocaleFeature = goog.module.get('goog.i18n.LocaleFeature'); +const NativeLocaleDigits = goog.module.get('goog.i18n.NativeLocaleDigits'); /** - * Datetime formatting functions following the pattern specification as defined + * IMPORTANT: Datetime formatting results different between JavaScript and + * native ECMAScript implementations. + * + * Native mode accepts a set of options for styles and also for specifying + * a small set of choices for each individual field of a formatted output. These + * effectively specify skeletons which direct the formatting according to + * formats built into the ECMAScript DateTime implementation of + * Intl.DateTimeFormat. + * + * The ECMAScript DateTimeFormat constructor and options are defined here: + * {@link + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat} + * + * Datetime formatting functions in JavaScript mode are provided with + * options to use standard styles, predefined patterns such as YEAR_FULL, + * and other values in goog.i18n.DateTimeFormat.Format. + * + * Native mode date/time formatting is supported only for these standard + * patterns because they can be directly mapped to native mode options. + * + * Native mode does not support custom patterns, which are discouraged. + * Using such custom pattern strings will call the JavaScript (polyfill) + * version of DateTimeFormat rather than native ECMAScript. + * + * Custom patterns can be used using the symbols below for date/time. + * Other text can be included. However, standard patterns are preferred + * because native EMCAScript code is more efficient in download size and time. + * + * The following symbols may be used in pattern specification, as defined * in JDK, ICU and CLDR, with minor modification for typical usage in JS. + * * Pattern specification: * {@link http://userguide.icu-project.org/formatparse/datetime} *
@@ -97,9 +134,9 @@ goog.requireType('goog.i18n.DateTimeSymbolsType');
  *     or the common patterns defined in goog.i18n.DateTimePatterns.
  *     Examples:
  *     
- *       var fmt = new goog.i18n.DateTimeFormat(
+ *       let fmt = new goog.i18n.DateTimeFormat(
  *           goog.i18n.DateTimeFormat.Format.FULL_DATE);
- *       var fmt = new goog.i18n.DateTimeFormat(
+ *       let fmt = new goog.i18n.DateTimeFormat(
  *           goog.i18n.DateTimePatterns.MONTH_DAY_YEAR_MEDIUM);
  *     
* @@ -115,19 +152,46 @@ goog.i18n.DateTimeFormat = function(pattern, opt_dateTimeSymbols) { goog.i18n.DateTimeSymbols !== undefined, 'goog.i18n.DateTimeSymbols or explicit symbols must be defined'); - this.patternParts_ = []; + /** + * Remember if the implementation is ECMAScript + * @type {?goog.global.Intl.DateTimeFormat} + * @private + */ + this.intlFormatter_ = null; /** - * Data structure that with all the locale info needed for date formatting. - * (day/month names, most common patterns, rules for week-end, etc.) - * @private {!goog.i18n.DateTimeSymbolsType} + * Remember the pattern applied for resetting Intl formatter. + * @type {number|string} + * @private @constant */ - this.dateTimeSymbols_ = /** @type {!goog.i18n.DateTimeSymbolsType} */ ( - opt_dateTimeSymbols || goog.i18n.DateTimeSymbols); - if (typeof pattern == 'number') { - this.applyStandardPattern_(pattern); + this.originalPattern_ = pattern; + + this.patternParts_ = []; + + // Try to look up pattern in the DateTimePattern data. + // If it is a standard value for the locale, then use the options + // with native formatter if possible + + if (LocaleFeature.USE_ECMASCRIPT_I18N_DATETIMEF && + ((typeof pattern == 'number'))) { + // Use Intl DateTimeFormat class with standard predefined- patterns + // Assumes no time zone settings + this.applyStandardEnumNative_(pattern, false, null); } else { - this.applyPattern_(pattern); + /** + * Use polyfill implementation with data defining locale-specific data such + * as (day/month names, most common patterns, rules for week-end, etc.) + * @private {!goog.i18n.DateTimeSymbolsType} + * @const + */ + this.dateTimeSymbols_ = /** @type {!goog.i18n.DateTimeSymbolsType} */ ( + opt_dateTimeSymbols || goog.i18n.DateTimeSymbols); + if (typeof pattern == 'number') { + this.applyStandardPattern_(pattern); + } else { + // Pattern is a string. This requires the polyfill implementation. + this.applyPattern_(pattern); + } } }; @@ -152,11 +216,10 @@ goog.i18n.DateTimeFormat.Format = { SHORT_DATETIME: 11 }; - /** * regular expression pattern for parsing pattern string - * @type {Array} - * @private + * @type {!Array} + * @private @const */ goog.i18n.DateTimeFormat.TOKENS_ = [ // quote string @@ -204,11 +267,11 @@ goog.i18n.DateTimeFormat.prototype.applyPattern_ = function(pattern) { } // lex the pattern, once for all uses while (pattern) { - var previousPattern = pattern; - for (var i = 0; i < goog.i18n.DateTimeFormat.TOKENS_.length; ++i) { - var m = pattern.match(goog.i18n.DateTimeFormat.TOKENS_[i]); + const previousPattern = pattern; + for (let i = 0; i < goog.i18n.DateTimeFormat.TOKENS_.length; ++i) { + const m = pattern.match(goog.i18n.DateTimeFormat.TOKENS_[i]); if (m) { - var part = m[0]; + let part = m[0]; pattern = pattern.substring(part.length); if (i == goog.i18n.DateTimeFormat.PartTypes_.QUOTED_STRING) { if (part == '\'\'') { @@ -234,8 +297,9 @@ goog.i18n.DateTimeFormat.prototype.applyPattern_ = function(pattern) { /** * Format the given date object according to preset pattern and current locale. - * @param {goog.date.DateLike} date The Date object that is being formatted. - * @param {goog.i18n.TimeZone=} opt_timeZone optional, if specified, time + * @param {?goog.date.DateLike|undefined} date The Date object that is being + * formatted. + * @param {?goog.i18n.TimeZone=} opt_timeZone optional, if specified, time * related fields will be formatted based on its setting. When this field * is not specified, "undefined" will be pass around and those function * that really need time zone service will create a default one. @@ -262,46 +326,205 @@ goog.i18n.DateTimeFormat.prototype.format = function(date, opt_timeZone) { // our own code, we uses 3 Date object instead, one for "Year, month, day", // one for time within that day, and one for timeZone object since it need // the real time to figure out actual time zone offset. - var diff = opt_timeZone ? - (date.getTimezoneOffset() - opt_timeZone.getOffset(date)) * 60000 : - 0; - var dateForDate = diff ? new Date(date.getTime() + diff) : date; - var dateForTime = dateForDate; - // When the time manipulation applied above spans the DST on/off hour, this - // could alter the time incorrectly by adding or subtracting an additional - // hour. - // We can mitigate this by: - // - Adding the difference in timezone offset to the date. This ensures that - // the dateForDate is still within the right day if the extra DST hour - // affected the date. - // - Move the time one day forward if we applied a timezone offset backwards, - // or vice versa. This trick ensures that the time is in the same offset - // as the original date, so we remove the additional hour added or - // subtracted by the DST switch. - if (opt_timeZone && - dateForDate.getTimezoneOffset() != date.getTimezoneOffset()) { - var dstDiff = - (dateForDate.getTimezoneOffset() - date.getTimezoneOffset()) * 60000; - dateForDate = new Date(dateForDate.getTime() + dstDiff); - - diff += diff > 0 ? -goog.date.MS_PER_DAY : goog.date.MS_PER_DAY; - dateForTime = new Date(date.getTime() + diff); - } - var out = []; - for (var i = 0; i < this.patternParts_.length; ++i) { - var text = this.patternParts_[i].text; - if (goog.i18n.DateTimeFormat.PartTypes_.FIELD == - this.patternParts_[i].type) { - out.push(this.formatField_( - text, date, dateForDate, dateForTime, opt_timeZone)); + if (this.intlFormatter_ && LocaleFeature.USE_ECMASCRIPT_I18N_DATETIMEF) { + // Use Native ECMASCript formatting + + // Compare the date for type UTC and formatter's timeZone setting. + let changedUtcSettings = false; + // Is the new date/time based on UTC or local time? + const isDateUtc = (date instanceof goog.date.UtcDateTime); + const options = this.intlFormatter_.resolvedOptions(); + if (isDateUtc) { + changedUtcSettings = (options.timeZone !== 'UTC'); } else { - out.push(text); + changedUtcSettings = (options.timeZone === 'UTC'); } + + if (goog.i18n.DateTimeFormat.resetEnforceAsciiDigits_ || + changedUtcSettings || opt_timeZone) { + // Create new Intl DateTimeFormat object with reset values. + this.applyStandardEnumNative_( + this.originalPattern_, isDateUtc, opt_timeZone); + goog.i18n.DateTimeFormat.resetEnforceAsciiDigits_ = false; + } + + /** + * @type {!Date|number|undefined} realdate type match for Closure + */ + const realdate = date ? new Date(date.valueOf()) : undefined; + + // To be consistent with polyfill results, use "UTC" instead of "GMT". + // return this.intlFormatter_.format(realdate).replace(/GMT-/, 'UTC-'); + let result = this.intlFormatter_.format(realdate).replace(/GMT-/, 'UTC-'); + return result; + } else { + // Format using polyfill. + let diff = opt_timeZone ? + (date.getTimezoneOffset() - opt_timeZone.getOffset(date)) * 60000 : + 0; + let dateForDate = diff ? new Date(date.getTime() + diff) : date; + let dateForTime = dateForDate; + // When the time manipulation applied above spans the DST on/off hour, this + // could alter the time incorrectly by adding or subtracting an additional + // hour. + // We can mitigate this by: + // - Adding the difference in timezone offset to the date. This ensures that + // the dateForDate is still within the right day if the extra DST hour + // affected the date. + // - Move the time one day forward if we applied a timezone offset + // backwards, + // or vice versa. This trick ensures that the time is in the same offset + // as the original date, so we remove the additional hour added or + // subtracted by the DST switch. + if (opt_timeZone && + dateForDate.getTimezoneOffset() != date.getTimezoneOffset()) { + const dstDiff = + (dateForDate.getTimezoneOffset() - date.getTimezoneOffset()) * 60000; + dateForDate = new Date(dateForDate.getTime() + dstDiff); + + diff += diff > 0 ? -goog.date.MS_PER_DAY : goog.date.MS_PER_DAY; + dateForTime = new Date(date.getTime() + diff); + } + + const out = []; + for (let i = 0; i < this.patternParts_.length; ++i) { + const text = this.patternParts_[i].text; + if (goog.i18n.DateTimeFormat.PartTypes_.FIELD == + this.patternParts_[i].type) { + out.push(this.formatField_( + text, date, dateForDate, dateForTime, opt_timeZone)); + } else { + out.push(text); + } + } + return out.join(''); } - return out.join(''); }; +/** + * Parameters to Intl.DateTimeFormat constructor + * @private @typedef {{ + * calendar: (string|undefined), + * dateStyle: (string|undefined), + * timeStyle: (string|undefined), + * era: (string|undefined), + * formatMatcher: (string|undefined), + * localeMatcher: (string|undefined), + * year: (string|undefined), + * month: (string|undefined), + * day: (string|undefined), + * weekday: (string|undefined), + * hour: (string|undefined), + * hour12: (boolean|undefined), + * minute: (string|undefined), + * second: (string|undefined), + * timeZone: (string|undefined), + * numberingSystem: (string|undefined), + * timeZoneName: (string|undefined), + * }} + */ +goog.i18n.DateTimeFormat.IntlOptions; + +/** + * Create an ECMAScript Intl.DateTimeFormat object based on + * a predefined skeleton of fields and settings. + * @param {number|string} formatType A number that identified the predefined + * pattern. + * @param {boolean} isUtc Should values be fixed in UTC? + * @param {?goog.i18n.TimeZone=} opt_timeZone explicit set time zone + * @private + */ +goog.i18n.DateTimeFormat.prototype.applyStandardEnumNative_ = function( + formatType, isUtc, opt_timeZone) { + /** @type {!goog.i18n.DateTimeFormat.IntlOptions} */ + const options = {calendar: 'gregory'}; // Only Gregorian calendar + + // When time zone is explicitly given + if (isUtc) { + options.timeZone = 'UTC'; + } else if (opt_timeZone) { + options.timeZone = opt_timeZone.getTimeZoneId(); + } + + switch (formatType) { + // DATEFORMATS + case goog.i18n.DateTimeFormat.Format.FULL_DATE: + options.dateStyle = 'full'; + break; + case goog.i18n.DateTimeFormat.Format.LONG_DATE: + options.dateStyle = 'long'; + break; + case goog.i18n.DateTimeFormat.Format.MEDIUM_DATE: + options.dateStyle = 'medium'; + break; + case goog.i18n.DateTimeFormat.Format.SHORT_DATE: + default: + options.dateStyle = 'short'; + break; + + // TIMEFORMATS + case goog.i18n.DateTimeFormat.Format.FULL_TIME: + options.timeStyle = 'full'; + break; + case goog.i18n.DateTimeFormat.Format.LONG_TIME: + options.timeStyle = 'long'; + break; + case goog.i18n.DateTimeFormat.Format.MEDIUM_TIME: + options.timeStyle = 'medium'; + break; + case goog.i18n.DateTimeFormat.Format.SHORT_TIME: + options.timeStyle = 'short'; + break; + + // DATETIMEFORMATS + case goog.i18n.DateTimeFormat.Format.FULL_DATETIME: + options.dateStyle = 'full'; + options.timeStyle = 'full'; + // Can we modify how timezone is presented? + // if (opt_timeZone) { + // options.timeZoneName = 'long'; + // } else { + // options.timeZoneName = 'short'; + // } + break; + case goog.i18n.DateTimeFormat.Format.LONG_DATETIME: + options.dateStyle = 'long'; + options.timeStyle = 'long'; + break; + case goog.i18n.DateTimeFormat.Format.MEDIUM_DATETIME: + options.dateStyle = 'medium'; + options.timeStyle = 'medium'; + break; + case goog.i18n.DateTimeFormat.Format.SHORT_DATETIME: + options.dateStyle = 'short'; + options.timeStyle = 'short'; + break; + } + + + // Intl requires '-' instead of '_'. + let fixedLocale = goog.LOCALE.replace(/_/g, '-'); + if (!goog.LOCALE) { + fixedLocale = 'en'; // The default + } + + if (goog.i18n.DateTimeFormat.enforceAsciiDigits_) { + options.numberingSystem = 'latn'; + } else { + if (fixedLocale in NativeLocaleDigits.FormatWithLocaleDigits) { + options.numberingSystem = + NativeLocaleDigits.FormatWithLocaleDigits[fixedLocale]; + } + } + + try { + this.intlFormatter_ = + new goog.global.Intl.DateTimeFormat(fixedLocale, options); + } catch (e) { + goog.asserts.assert(e != null); + } +}; /** * Apply a predefined pattern as identified by formatType, which is stored in @@ -312,7 +535,7 @@ goog.i18n.DateTimeFormat.prototype.format = function(date, opt_timeZone) { goog.i18n.DateTimeFormat.prototype.applyStandardPattern_ = function( formatType) { 'use strict'; - var pattern; + let pattern; if (formatType < 4) { pattern = this.dateTimeSymbols_.DATEFORMATS[formatType]; } else if (formatType < 8) { @@ -330,7 +553,6 @@ goog.i18n.DateTimeFormat.prototype.applyStandardPattern_ = function( this.applyPattern_(pattern); }; - /** * Localizes a string potentially containing numbers, replacing ASCII digits * with native digits if specified so by the locale. Leaves other characters. @@ -352,6 +574,13 @@ goog.i18n.DateTimeFormat.prototype.localizeNumbers_ = function(input) { goog.i18n.DateTimeFormat.enforceAsciiDigits_ = false; +/** + * Records if ASCII digits was set after formatter construction. + * @type {boolean} + * @private + */ +goog.i18n.DateTimeFormat.resetEnforceAsciiDigits_ = false; + /** * If RLM unicode characters should be removed from date/time patterns (useful * when enforcing ASCII digits for Arabic). See `#setEnforceAsciiDigits`. @@ -377,7 +606,11 @@ goog.i18n.DateTimeFormat.removeRlmInPatterns_ = false; */ goog.i18n.DateTimeFormat.setEnforceAsciiDigits = function(enforceAsciiDigits) { 'use strict'; - goog.i18n.DateTimeFormat.enforceAsciiDigits_ = enforceAsciiDigits; + if (goog.i18n.DateTimeFormat.enforceAsciiDigits_ !== enforceAsciiDigits) { + goog.i18n.DateTimeFormat.enforceAsciiDigits_ = enforceAsciiDigits; + // And remember for resetting native formatter. + goog.i18n.DateTimeFormat.resetEnforceAsciiDigits_ = true; + } // Also setting removal of RLM chracters when forcing ASCII digits since it's // the right thing to do for Arabic standard patterns. One could add an @@ -410,15 +643,15 @@ goog.i18n.DateTimeFormat.localizeNumbers = function( input, opt_dateTimeSymbols) { 'use strict'; input = String(input); - var dateTimeSymbols = opt_dateTimeSymbols || goog.i18n.DateTimeSymbols; + const dateTimeSymbols = opt_dateTimeSymbols || goog.i18n.DateTimeSymbols; if (dateTimeSymbols.ZERODIGIT === undefined || goog.i18n.DateTimeFormat.enforceAsciiDigits_) { return input; } - var parts = []; - for (var i = 0; i < input.length; i++) { - var c = input.charCodeAt(i); + const parts = []; + for (let i = 0; i < input.length; i++) { + const c = input.charCodeAt(i); parts.push( (0x30 <= c && c <= 0x39) ? // '0' <= c <= '9' String.fromCharCode(dateTimeSymbols.ZERODIGIT + c - 0x30) : @@ -439,7 +672,7 @@ goog.i18n.DateTimeFormat.localizeNumbers = function( */ goog.i18n.DateTimeFormat.prototype.formatEra_ = function(count, date) { 'use strict'; - var value = date.getFullYear() > 0 ? 1 : 0; + const value = date.getFullYear() > 0 ? 1 : 0; return count >= 4 ? this.dateTimeSymbols_.ERANAMES[value] : this.dateTimeSymbols_.ERAS[value]; }; @@ -460,7 +693,7 @@ goog.i18n.DateTimeFormat.prototype.formatEra_ = function(count, date) { */ goog.i18n.DateTimeFormat.prototype.formatYear_ = function(count, date) { 'use strict'; - var value = date.getFullYear(); + let value = date.getFullYear(); if (value < 0) { value = -value; } @@ -490,7 +723,7 @@ goog.i18n.DateTimeFormat.prototype.formatYear_ = function(count, date) { */ goog.i18n.DateTimeFormat.prototype.formatYearOfWeek_ = function(count, date) { 'use strict'; - var value = goog.date.getYearOfWeek( + let value = goog.date.getYearOfWeek( date.getFullYear(), date.getMonth(), date.getDate(), this.dateTimeSymbols_.FIRSTWEEKCUTOFFDAY, this.dateTimeSymbols_.FIRSTDAYOFWEEK); @@ -520,7 +753,7 @@ goog.i18n.DateTimeFormat.prototype.formatYearOfWeek_ = function(count, date) { */ goog.i18n.DateTimeFormat.prototype.formatMonth_ = function(count, date) { 'use strict'; - var value = date.getMonth(); + const value = date.getMonth(); switch (count) { case 5: return this.dateTimeSymbols_.NARROWMONTHS[value]; @@ -566,7 +799,7 @@ goog.i18n.DateTimeFormat.validateDateHasTime_ = function(date) { goog.i18n.DateTimeFormat.prototype.format24Hours_ = function(count, date) { 'use strict'; goog.i18n.DateTimeFormat.validateDateHasTime_(date); - var hours = goog.i18n.DateTimeFormat.getHours_(date) || 24; + const hours = goog.i18n.DateTimeFormat.getHours_(date) || 24; return this.localizeNumbers_(goog.string.padNumber(hours, count)); }; @@ -586,7 +819,7 @@ goog.i18n.DateTimeFormat.prototype.formatFractionalSeconds_ = function( count, date) { 'use strict'; // Fractional seconds left-justify, append 0 for precision beyond 3 - var value = date.getMilliseconds() / 1000; + const value = date.getMilliseconds() / 1000; return this.localizeNumbers_( value.toFixed(Math.min(3, count)).substr(2) + (count > 3 ? goog.string.padNumber(0, count - 3) : '')); @@ -604,7 +837,7 @@ goog.i18n.DateTimeFormat.prototype.formatFractionalSeconds_ = function( */ goog.i18n.DateTimeFormat.prototype.formatDayOfWeek_ = function(count, date) { 'use strict'; - var value = date.getDay(); + const value = date.getDay(); return count >= 4 ? this.dateTimeSymbols_.WEEKDAYS[value] : this.dateTimeSymbols_.SHORTWEEKDAYS[value]; }; @@ -622,7 +855,7 @@ goog.i18n.DateTimeFormat.prototype.formatDayOfWeek_ = function(count, date) { goog.i18n.DateTimeFormat.prototype.formatAmPm_ = function(count, date) { 'use strict'; goog.i18n.DateTimeFormat.validateDateHasTime_(date); - var hours = goog.i18n.DateTimeFormat.getHours_(date); + const hours = goog.i18n.DateTimeFormat.getHours_(date); return this.dateTimeSymbols_.AMPMS[hours >= 12 && hours < 24 ? 1 : 0]; }; @@ -639,7 +872,7 @@ goog.i18n.DateTimeFormat.prototype.formatAmPm_ = function(count, date) { goog.i18n.DateTimeFormat.prototype.format1To12Hours_ = function(count, date) { 'use strict'; goog.i18n.DateTimeFormat.validateDateHasTime_(date); - var hours = goog.i18n.DateTimeFormat.getHours_(date) % 12 || 12; + const hours = goog.i18n.DateTimeFormat.getHours_(date) % 12 || 12; return this.localizeNumbers_(goog.string.padNumber(hours, count)); }; @@ -656,7 +889,7 @@ goog.i18n.DateTimeFormat.prototype.format1To12Hours_ = function(count, date) { goog.i18n.DateTimeFormat.prototype.format0To11Hours_ = function(count, date) { 'use strict'; goog.i18n.DateTimeFormat.validateDateHasTime_(date); - var hours = goog.i18n.DateTimeFormat.getHours_(date) % 12; + const hours = goog.i18n.DateTimeFormat.getHours_(date) % 12; return this.localizeNumbers_(goog.string.padNumber(hours, count)); }; @@ -673,7 +906,7 @@ goog.i18n.DateTimeFormat.prototype.format0To11Hours_ = function(count, date) { goog.i18n.DateTimeFormat.prototype.format0To23Hours_ = function(count, date) { 'use strict'; goog.i18n.DateTimeFormat.validateDateHasTime_(date); - var hours = goog.i18n.DateTimeFormat.getHours_(date); + const hours = goog.i18n.DateTimeFormat.getHours_(date); return this.localizeNumbers_(goog.string.padNumber(hours, count)); }; @@ -690,7 +923,7 @@ goog.i18n.DateTimeFormat.prototype.format0To23Hours_ = function(count, date) { goog.i18n.DateTimeFormat.prototype.formatStandaloneDay_ = function( count, date) { 'use strict'; - var value = date.getDay(); + const value = date.getDay(); switch (count) { case 5: return this.dateTimeSymbols_.STANDALONENARROWWEEKDAYS[value]; @@ -716,7 +949,7 @@ goog.i18n.DateTimeFormat.prototype.formatStandaloneDay_ = function( goog.i18n.DateTimeFormat.prototype.formatStandaloneMonth_ = function( count, date) { 'use strict'; - var value = date.getMonth(); + const value = date.getMonth(); switch (count) { case 5: return this.dateTimeSymbols_.STANDALONENARROWMONTHS[value]; @@ -741,7 +974,7 @@ goog.i18n.DateTimeFormat.prototype.formatStandaloneMonth_ = function( */ goog.i18n.DateTimeFormat.prototype.formatQuarter_ = function(count, date) { 'use strict'; - var value = Math.floor(date.getMonth() / 3); + const value = Math.floor(date.getMonth() / 3); return count < 4 ? this.dateTimeSymbols_.SHORTQUARTERS[value] : this.dateTimeSymbols_.QUARTERS[value]; }; @@ -807,7 +1040,7 @@ goog.i18n.DateTimeFormat.prototype.formatSeconds_ = function(count, date) { */ goog.i18n.DateTimeFormat.prototype.formatWeekOfYear_ = function(count, date) { 'use strict'; - var weekNum = goog.date.getWeekNumber( + const weekNum = goog.date.getWeekNumber( date.getFullYear(), date.getMonth(), date.getDate(), this.dateTimeSymbols_.FIRSTWEEKCUTOFFDAY, this.dateTimeSymbols_.FIRSTDAYOFWEEK); @@ -822,7 +1055,7 @@ goog.i18n.DateTimeFormat.prototype.formatWeekOfYear_ = function(count, date) { * @param {number} count Number of time pattern char repeats, it controls * how a field should be formatted. * @param {!goog.date.DateLike} date It holds the date object to be formatted. - * @param {goog.i18n.TimeZone=} opt_timeZone This holds current time zone info. + * @param {?goog.i18n.TimeZone=} opt_timeZone This holds current time zone info. * @return {string} Formatted string that represent this field. * @private */ @@ -844,7 +1077,7 @@ goog.i18n.DateTimeFormat.prototype.formatTimeZoneRFC_ = function( * @param {number} count Number of time pattern char repeats, it controls * how a field should be formatted. * @param {!goog.date.DateLike} date Whose value being evaluated. - * @param {goog.i18n.TimeZone=} opt_timeZone This holds current time zone info. + * @param {?goog.i18n.TimeZone=} opt_timeZone This holds current time zone info. * @return {string} GMT timeZone string. * @private */ @@ -861,7 +1094,7 @@ goog.i18n.DateTimeFormat.prototype.formatTimeZone_ = function( /** * Generate GMT timeZone string for given date * @param {!goog.date.DateLike} date Whose value being evaluated. - * @param {goog.i18n.TimeZone=} opt_timeZone This holds current time zone info. + * @param {?goog.i18n.TimeZone=} opt_timeZone This holds current time zone info. * @return {string} GMT timeZone string. * @private */ @@ -879,7 +1112,7 @@ goog.i18n.DateTimeFormat.prototype.formatTimeZoneId_ = function( * @param {number} count Number of time pattern char repeats, it controls * how a field should be formatted. * @param {!goog.date.DateLike} date Whose value being evaluated. - * @param {goog.i18n.TimeZone=} opt_timeZone This holds current time zone info. + * @param {?goog.i18n.TimeZone=} opt_timeZone This holds current time zone info. * @return {string} GMT timeZone string. * @private */ @@ -901,14 +1134,14 @@ goog.i18n.DateTimeFormat.prototype.formatTimeZoneLocationId_ = function( * for formatting. * @param {!goog.date.DateLike} dateForTime used to resolve time fields * for formatting. - * @param {goog.i18n.TimeZone=} opt_timeZone This holds current time zone info. + * @param {?goog.i18n.TimeZone=} opt_timeZone This holds current time zone info. * @return {string} string representation for the given field. * @private */ goog.i18n.DateTimeFormat.prototype.formatField_ = function( patternStr, date, dateForDate, dateForTime, opt_timeZone) { 'use strict'; - var count = patternStr.length; + const count = patternStr.length; switch (patternStr.charAt(0)) { case 'G': return this.formatEra_(count, dateForDate); @@ -957,4 +1190,6 @@ goog.i18n.DateTimeFormat.prototype.formatField_ = function( default: return ''; } + }; +}); // End of scope for module data diff --git a/closure/goog/i18n/datetimeformat_test.js b/closure/goog/i18n/datetimeformat_test.js index 86e1b205c2..861134f62f 100644 --- a/closure/goog/i18n/datetimeformat_test.js +++ b/closure/goog/i18n/datetimeformat_test.js @@ -11,6 +11,14 @@ goog.module('goog.i18n.DateTimeFormatTest'); goog.setTestOnly(); + +const LocaleFeature = goog.require('goog.i18n.LocaleFeature'); +const PropertyReplacer = goog.require('goog.testing.PropertyReplacer'); + +// Note that exact formatted output equivalence between Closure and +// ECMAScript implementations is not required in all cases. +const replacer = new PropertyReplacer(); + const DateDate = goog.require('goog.date.Date'); const DateTime = goog.require('goog.date.DateTime'); const DateTimeFormat = goog.require('goog.i18n.DateTimeFormat'); @@ -29,9 +37,12 @@ const DateTimePatterns_zh_HK = goog.require('goog.i18n.DateTimePatterns_zh_HK'); const DateTimePatterns_zh_Hant_TW = goog.require('goog.i18n.DateTimePatterns_zh_Hant_TW'); /** @suppress {extraRequire} */ const DateTimeSymbols = goog.require('goog.i18n.DateTimeSymbols'); +const DateTimeSymbols_ar = goog.require('goog.i18n.DateTimeSymbols_ar'); const DateTimeSymbols_ar_AE = goog.require('goog.i18n.DateTimeSymbols_ar_AE'); const DateTimeSymbols_ar_EG = goog.require('goog.i18n.DateTimeSymbols_ar_EG'); const DateTimeSymbols_ar_SA = goog.require('goog.i18n.DateTimeSymbols_ar_SA'); +const DateTimeSymbols_bg = goog.require('goog.i18n.DateTimeSymbols_bg'); +const DateTimeSymbols_bn = goog.require('goog.i18n.DateTimeSymbols_bn'); const DateTimeSymbols_bn_BD = goog.require('goog.i18n.DateTimeSymbols_bn_BD'); const DateTimeSymbols_de = goog.require('goog.i18n.DateTimeSymbols_de'); const DateTimeSymbols_en = goog.require('goog.i18n.DateTimeSymbols_en'); @@ -39,19 +50,74 @@ const DateTimeSymbols_en_GB = goog.require('goog.i18n.DateTimeSymbols_en_GB'); const DateTimeSymbols_en_IE = goog.require('goog.i18n.DateTimeSymbols_en_IE'); const DateTimeSymbols_en_IN = goog.require('goog.i18n.DateTimeSymbols_en_IN'); const DateTimeSymbols_en_US = goog.require('goog.i18n.DateTimeSymbols_en_US'); +const DateTimeSymbols_en_XA = goog.require('goog.i18n.DateTimeSymbols_en_XA'); const DateTimeSymbols_fa = goog.require('goog.i18n.DateTimeSymbols_fa'); const DateTimeSymbols_fr = goog.require('goog.i18n.DateTimeSymbols_fr'); const DateTimeSymbols_fr_DJ = goog.require('goog.i18n.DateTimeSymbols_fr_DJ'); const DateTimeSymbols_he_IL = goog.require('goog.i18n.DateTimeSymbols_he_IL'); const DateTimeSymbols_ja = goog.require('goog.i18n.DateTimeSymbols_ja'); +const DateTimeSymbols_ml = goog.require('goog.i18n.DateTimeSymbols_ml'); +const DateTimeSymbols_mr = goog.require('goog.i18n.DateTimeSymbols_mr'); +const DateTimeSymbols_my = goog.require('goog.i18n.DateTimeSymbols_my'); +const DateTimeSymbols_ne = goog.require('goog.i18n.DateTimeSymbols_ne'); const DateTimeSymbols_ro_RO = goog.require('goog.i18n.DateTimeSymbols_ro_RO'); const DateTimeSymbols_sv = goog.require('goog.i18n.DateTimeSymbols_sv'); +const DateTimeSymbols_zh_HK = goog.require('goog.i18n.DateTimeSymbols_zh_HK'); +const DateTimeSymbols_zh_Hant_TW = goog.require('goog.i18n.DateTimeSymbols_zh_Hant_TW'); const TimeZone = goog.require('goog.i18n.TimeZone'); + +const UtcDateTime = goog.require('goog.date.UtcDateTime'); + const testSuite = goog.require('goog.testing.testSuite'); // Initial values -goog.i18n.DateTimePatterns = DateTimePatterns_en; -goog.i18n.DateTimeSymbols = DateTimeSymbols_en; +replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_en); +replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en); + +// Helpers for native mode +/** + * Changes status of ECMASCRIPT mode + * @param {boolean} new_setting + */ +function setNativeMode(new_setting) { + replacer.set(LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', new_setting); +} + +// Sets up goog.USE_ECMASCRIPT_I18N_DATETIMEF flag in each function. +let testECMAScriptOptions = [false]; +// Don't test native ECMASCript on IE11. +if (Intl.DateTimeFormat) { + // Add test if the browser environment supports ECMAScript implementation. + if (!goog.labs.userAgent.browser.isIE()) { + testECMAScriptOptions.push(true); + } +} + +/** + * Gets current setting for native ECMAScript mode + * @return {boolean} + */ +function getNativeMode() { + return LocaleFeature.USE_ECMASCRIPT_I18N_DATETIMEF; +} + +/** + * Returns string without RTL or LTR markers. + * @param {string} input + * @return {string} + */ +function removeDirectionMarkers(input) { + return input.replace(/[\u200e\u200f]/g, ''); +} + +/** + * Returns string without LTR markers. + * @param {string} input + * @return {string} + */ +function removeLtrMarkers(input) { + return input.replace(/[\u200e]/g, ''); +} /** * Helpers to make tests work regardless of the timeZone we're in. @@ -168,8 +234,6 @@ const europeBerlinData = { 'std_offset': 60, }; -let date; - /** * Creates a string by concatenating the week number for 7 successive days * @return {string} @@ -186,488 +250,719 @@ function weekInYearFor7Days() { } testSuite({ + getTestName: function() { + return 'DateTimeFormat Tests'; + }, + + setUpPage() {}, + + setUp() { + replacer.set(LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', false); + setNativeMode(false); + replacer.replace(goog, 'LOCALE', 'en'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_en); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en); + }, + tearDown() { // We always revert to a known state - goog.i18n.DateTimePatterns = DateTimePatterns_en; - goog.i18n.DateTimeSymbols = DateTimeSymbols_en; + replacer.replace(goog, 'LOCALE', 'en'); DateTimeFormat.setEnforceAsciiDigits(false); + + replacer.reset(); + + // Reset to non-native + setNativeMode(false); }, testHHmmss() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 6, 27, 13, 10, 10, 250); - const fmt = new DateTimeFormat('HH:mm:ss'); - assertEquals('13:10:10', fmt.format(date)); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + const date = new Date(2006, 6, 27, 13, 10, 10, 250); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + const fmt = new DateTimeFormat('HH:mm:ss'); + assertEquals('13:10:10', fmt.format(date)); + } }, testhhmmssa() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 6, 27, 13, 10, 10, 250); - const fmt = new DateTimeFormat('h:mm:ss a'); - assertEquals('1:10:10 PM', fmt.format(date)); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + const date = new Date(2006, 6, 27, 13, 10, 10, 250); + for (let nativeMode of testECMAScriptOptions) { + setNativeMode(nativeMode); + const fmt = new DateTimeFormat('h:mm:ss a'); + assertEquals('1:10:10 PM', fmt.format(date)); + } }, testEEEMMMddyy() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 6, 27, 13, 10, 10, 250); - const fmt = new DateTimeFormat('EEE, MMM d, yy'); - assertEquals('Do., Juli 27, 06', fmt.format(date)); + const date = new Date(2006, 6, 27, 13, 10, 10, 250); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + const fmt = new DateTimeFormat('EEE, MMM d, yy'); + assertEquals('Do., Juli 27, 06', fmt.format(date)); + } }, testEEEEMMMddyy() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 6, 27, 13, 10, 10, 250); - const fmt = new DateTimeFormat('EEEE,MMMM dd, yyyy'); - assertEquals('Donnerstag,Juli 27, 2006', fmt.format(date)); + const date = new Date(2006, 6, 27, 13, 10, 10, 250); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + const fmt = new DateTimeFormat('EEEE,MMMM dd, yyyy'); + assertEquals('Donnerstag,Juli 27, 2006', fmt.format(date)); + } }, testyyyyMMddG() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10, 250)); - /** @suppress {checkTypes} suppression added to enable type checking */ - const timeZone = TimeZone.createTimeZone(420, DateTimeSymbols_de); - const fmt = new DateTimeFormat('yyyy.MM.dd G \'at\' HH:mm:ss vvvv'); - assertEquals( - '2006.07.27 n. Chr. at 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); + const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10, 250)); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + /** @suppress {checkTypes} suppression added to enable type checking */ + const timeZone = TimeZone.createTimeZone(420, DateTimeSymbols_de); + const fmt = new DateTimeFormat('yyyy.MM.dd G \'at\' HH:mm:ss vvvv'); + assertEquals( + '2006.07.27 n. Chr. at 06:10:10 Etc/GMT+7', + fmt.format(date, timeZone)); + } }, testyyyyyMMMMM() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 6, 27, 13, 10, 10, 250); - let fmt = new DateTimeFormat('yyyyy.MMMMM.dd GGG hh:mm aaa'); - assertEquals('02006.J.27 n. Chr. 01:10 PM', fmt.format(date)); - - date = new Date(972, 11, 25, 13, 10, 10, 250); - fmt = new DateTimeFormat('yyyyy.MMMMM.dd'); - assertEquals('00972.D.25', fmt.format(date)); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + let date = new Date(2006, 6, 27, 13, 10, 10, 250); + let fmt = new DateTimeFormat('yyyyy.MMMMM.dd GGG hh:mm aaa'); + assertEquals('02006.J.27 n. Chr. 01:10 PM', fmt.format(date)); + + date = new Date(972, 11, 25, 13, 10, 10, 250); + fmt = new DateTimeFormat('yyyyy.MMMMM.dd'); + assertEquals('00972.D.25', fmt.format(date)); + } }, testQQQQyy() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 0, 27, 13, 10, 10, 250); - const fmt = new DateTimeFormat('QQQQ yy'); - assertEquals('1. Quartal 06', fmt.format(date)); - date = new Date(2006, 1, 27, 13, 10, 10, 250); - assertEquals('1. Quartal 06', fmt.format(date)); - date = new Date(2006, 2, 27, 13, 10, 10, 250); - assertEquals('1. Quartal 06', fmt.format(date)); - date = new Date(2006, 3, 27, 13, 10, 10, 250); - assertEquals('2. Quartal 06', fmt.format(date)); - date = new Date(2006, 4, 27, 13, 10, 10, 250); - assertEquals('2. Quartal 06', fmt.format(date)); - date = new Date(2006, 5, 27, 13, 10, 10, 250); - assertEquals('2. Quartal 06', fmt.format(date)); - date = new Date(2006, 6, 27, 13, 10, 10, 250); - assertEquals('3. Quartal 06', fmt.format(date)); - date = new Date(2006, 7, 27, 13, 10, 10, 250); - assertEquals('3. Quartal 06', fmt.format(date)); - date = new Date(2006, 8, 27, 13, 10, 10, 250); - assertEquals('3. Quartal 06', fmt.format(date)); - date = new Date(2006, 9, 27, 13, 10, 10, 250); - assertEquals('4. Quartal 06', fmt.format(date)); - date = new Date(2006, 10, 27, 13, 10, 10, 250); - assertEquals('4. Quartal 06', fmt.format(date)); - date = new Date(2006, 11, 27, 13, 10, 10, 250); - assertEquals('4. Quartal 06', fmt.format(date)); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + let date = new Date(2006, 0, 27, 13, 10, 10, 250); + const fmt = new DateTimeFormat('QQQQ yy'); + assertEquals('1. Quartal 06', fmt.format(date)); + date = new Date(2006, 1, 27, 13, 10, 10, 250); + assertEquals('1. Quartal 06', fmt.format(date)); + date = new Date(2006, 2, 27, 13, 10, 10, 250); + assertEquals('1. Quartal 06', fmt.format(date)); + date = new Date(2006, 3, 27, 13, 10, 10, 250); + assertEquals('2. Quartal 06', fmt.format(date)); + date = new Date(2006, 4, 27, 13, 10, 10, 250); + assertEquals('2. Quartal 06', fmt.format(date)); + date = new Date(2006, 5, 27, 13, 10, 10, 250); + assertEquals('2. Quartal 06', fmt.format(date)); + date = new Date(2006, 6, 27, 13, 10, 10, 250); + assertEquals('3. Quartal 06', fmt.format(date)); + date = new Date(2006, 7, 27, 13, 10, 10, 250); + assertEquals('3. Quartal 06', fmt.format(date)); + date = new Date(2006, 8, 27, 13, 10, 10, 250); + assertEquals('3. Quartal 06', fmt.format(date)); + date = new Date(2006, 9, 27, 13, 10, 10, 250); + assertEquals('4. Quartal 06', fmt.format(date)); + date = new Date(2006, 10, 27, 13, 10, 10, 250); + assertEquals('4. Quartal 06', fmt.format(date)); + date = new Date(2006, 11, 27, 13, 10, 10, 250); + assertEquals('4. Quartal 06', fmt.format(date)); + } }, testQQyyyy() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 0, 27, 13, 10, 10, 250); - const fmt = new DateTimeFormat('QQ yyyy'); - assertEquals('Q1 2006', fmt.format(date)); - date = new Date(2006, 1, 27, 13, 10, 10, 250); - assertEquals('Q1 2006', fmt.format(date)); - date = new Date(2006, 2, 27, 13, 10, 10, 250); - assertEquals('Q1 2006', fmt.format(date)); - date = new Date(2006, 3, 27, 13, 10, 10, 250); - assertEquals('Q2 2006', fmt.format(date)); - date = new Date(2006, 4, 27, 13, 10, 10, 250); - assertEquals('Q2 2006', fmt.format(date)); - date = new Date(2006, 5, 27, 13, 10, 10, 250); - assertEquals('Q2 2006', fmt.format(date)); - date = new Date(2006, 6, 27, 13, 10, 10, 250); - assertEquals('Q3 2006', fmt.format(date)); - date = new Date(2006, 7, 27, 13, 10, 10, 250); - assertEquals('Q3 2006', fmt.format(date)); - date = new Date(2006, 8, 27, 13, 10, 10, 250); - assertEquals('Q3 2006', fmt.format(date)); - date = new Date(2006, 9, 27, 13, 10, 10, 250); - assertEquals('Q4 2006', fmt.format(date)); - date = new Date(2006, 10, 27, 13, 10, 10, 250); - assertEquals('Q4 2006', fmt.format(date)); - date = new Date(2006, 11, 27, 13, 10, 10, 250); - assertEquals('Q4 2006', fmt.format(date)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + let date = new Date(2006, 0, 27, 13, 10, 10, 250); + const fmt = new DateTimeFormat('QQ yyyy'); + assertEquals('Q1 2006', fmt.format(date)); + date = new Date(2006, 1, 27, 13, 10, 10, 250); + assertEquals('Q1 2006', fmt.format(date)); + date = new Date(2006, 2, 27, 13, 10, 10, 250); + assertEquals('Q1 2006', fmt.format(date)); + date = new Date(2006, 3, 27, 13, 10, 10, 250); + assertEquals('Q2 2006', fmt.format(date)); + date = new Date(2006, 4, 27, 13, 10, 10, 250); + assertEquals('Q2 2006', fmt.format(date)); + date = new Date(2006, 5, 27, 13, 10, 10, 250); + assertEquals('Q2 2006', fmt.format(date)); + date = new Date(2006, 6, 27, 13, 10, 10, 250); + assertEquals('Q3 2006', fmt.format(date)); + date = new Date(2006, 7, 27, 13, 10, 10, 250); + assertEquals('Q3 2006', fmt.format(date)); + date = new Date(2006, 8, 27, 13, 10, 10, 250); + assertEquals('Q3 2006', fmt.format(date)); + date = new Date(2006, 9, 27, 13, 10, 10, 250); + assertEquals('Q4 2006', fmt.format(date)); + date = new Date(2006, 10, 27, 13, 10, 10, 250); + assertEquals('Q4 2006', fmt.format(date)); + date = new Date(2006, 11, 27, 13, 10, 10, 250); + assertEquals('Q4 2006', fmt.format(date)); + } }, /** @suppress {checkTypes} suppression added to enable type checking */ testMMddyyyyHHmmsszzz() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 6, 27, 13, 10, 10, 250); - const fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzz'); - assertEquals( - '07/27/2006 13:10:10 ' + timezoneString(date), fmt.format(date)); + const date = new Date(2006, 6, 27, 13, 10, 10, 250); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + const fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzz'); + assertEquals( + '07/27/2006 13:10:10 ' + timezoneString(date), fmt.format(date)); + } }, /** @suppress {checkTypes} suppression added to enable type checking */ testMMddyyyyHHmmssZ() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 6, 27, 13, 10, 10, 250); - const fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); - assertEquals( - '07/27/2006 13:10:10 ' + timezoneStringRFC(date), fmt.format(date)); + const date = new Date(2006, 6, 27, 13, 10, 10, 250); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + const fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); + + assertEquals( + '07/27/2006 13:10:10 ' + timezoneStringRFC(date), fmt.format(date)); + } }, testPatternMonthDayMedium() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 6, 27, 13, 10, 10, 250); - const fmt = new DateTimeFormat(goog.i18n.DateTimePatterns.MONTH_DAY_MEDIUM); - assertEquals('27. Juli', fmt.format(date)); + const date = new Date(2006, 6, 27, 13, 10, 10, 250); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + const fmt = new DateTimeFormat(DateTimePatterns_de.MONTH_DAY_MEDIUM); + assertEquals('27. Juli', fmt.format(date)); + } }, testPatternYearMonthNarrow() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 6, 27, 13, 10, 10, 250); - const fmt = new DateTimeFormat(goog.i18n.DateTimePatterns.YEAR_MONTH_SHORT); - assertEquals('07.2006', fmt.format(date)); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + const date = new Date(2006, 6, 27, 13, 10, 10, 250); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + let fmt = new DateTimeFormat(DateTimePatterns_de.YEAR_MONTH_SHORT); + assertEquals('07.2006', fmt.format(date)); + fmt = new DateTimeFormat(DateTimePatterns_de.YEAR_MONTH_ABBR); + assertEquals('Juli 2006', fmt.format(date)); + } }, testPatternDayOfWeekMonthDayMedium() { - date = new Date(2006, 6, 27, 13, 10, 10, 250); - - let fmt = - new DateTimeFormat(goog.i18n.DateTimePatterns.WEEKDAY_MONTH_DAY_MEDIUM); - assertEquals('Thu, Jul 27', fmt.format(date)); - - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - fmt = - new DateTimeFormat(goog.i18n.DateTimePatterns.WEEKDAY_MONTH_DAY_MEDIUM); - assertEquals('Do., 27. Juli', fmt.format(date)); + const date = new Date(2006, 6, 27, 13, 10, 10, 250); + + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + replacer.replace(goog, 'LOCALE', 'en'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_en); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en); + let fmt = new DateTimeFormat(DateTimePatterns.WEEKDAY_MONTH_DAY_MEDIUM); + assertEquals('Thu, Jul 27', fmt.format(date)); + + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + replacer.replace(goog, 'LOCALE', 'de'); + fmt = new DateTimeFormat(DateTimePatterns_de.WEEKDAY_MONTH_DAY_MEDIUM); + assertEquals('Do., 27. Juli', fmt.format(date)); + } }, testPatternDayOfWeekMonthDayYearMedium() { - date = new Date(2012, 5, 28, 13, 10, 10, 250); - - let fmt = new DateTimeFormat( - goog.i18n.DateTimePatterns.WEEKDAY_MONTH_DAY_YEAR_MEDIUM); - assertEquals('Thu, Jun 28, 2012', fmt.format(date)); - fmt = new DateTimeFormat(goog.i18n.DateTimePatterns.MONTH_DAY_YEAR_MEDIUM); - assertEquals('Jun 28, 2012', fmt.format(date)); + const date = new Date(2012, 5, 28, 13, 10, 10, 250); - goog.i18n.DateTimePatterns = DateTimePatterns_sv; - goog.i18n.DateTimeSymbols = DateTimeSymbols_sv; - fmt = new DateTimeFormat( - goog.i18n.DateTimePatterns.WEEKDAY_MONTH_DAY_YEAR_MEDIUM); - assertEquals('tors 28 juni 2012', fmt.format(date)); - fmt = new DateTimeFormat(goog.i18n.DateTimePatterns.MONTH_DAY_YEAR_MEDIUM); - assertEquals('28 juni 2012', fmt.format(date)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + replacer.replace(goog, 'LOCALE', 'en'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_en); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en); + let fmt = + new DateTimeFormat(DateTimePatterns_en.WEEKDAY_MONTH_DAY_YEAR_MEDIUM); + assertEquals('Thu, Jun 28, 2012', fmt.format(date)); + fmt = new DateTimeFormat(DateTimePatterns_en.MONTH_DAY_YEAR_MEDIUM); + assertEquals('Jun 28, 2012', fmt.format(date)); + + replacer.replace(goog, 'LOCALE', 'sv'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_sv); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_sv); + fmt = new DateTimeFormat(DateTimePatterns.WEEKDAY_MONTH_DAY_YEAR_MEDIUM); + assertEquals('tors, juni 28, 2012', fmt.format(date)); + fmt = new DateTimeFormat(DateTimePatterns.MONTH_DAY_YEAR_MEDIUM); + assertEquals('juni 28, 2012', fmt.format(date)); + } }, testMonthDayHourMinuteTimezone() { - // Include various locales. - const date = new Date(2012, 5, 28, 13, 10, 10, 250); - const fmt = new DateTimeFormat( - goog.i18n.DateTimePatterns.MONTH_DAY_TIME_ZONE_SHORT); - assertEquals('Jun 28, 1:10 PM UTC-7', fmt.format(date)); - - goog.i18n.DateTimePatterns = DateTimePatterns_sv; - const fmtSv = new DateTimeFormat( - goog.i18n.DateTimePatterns.MONTH_DAY_TIME_ZONE_SHORT); - assertEquals('28 Jun 13:10 UTC-7', fmtSv.format(date)); - - goog.i18n.DateTimePatterns = DateTimePatterns_bg; - const fmtBg = new DateTimeFormat( - goog.i18n.DateTimePatterns.MONTH_DAY_TIME_ZONE_SHORT); - assertEquals('28.06, 13:10 ч. UTC-7', fmtBg.format(date)); - - goog.i18n.DateTimePatterns = DateTimePatterns_zh_HK; - const fmtZhHk = new DateTimeFormat( - goog.i18n.DateTimePatterns.MONTH_DAY_TIME_ZONE_SHORT); - assertEquals('6月28日 PM1:10 [UTC-7]', fmtZhHk.format(date)); - - // And with explicit timezone. - const timeZone = TimeZone.createTimeZone(-600); - assertEquals('6月29日 AM6:10 [UTC+10]', fmtZhHk.format(date, timeZone)); - - // And some from the extended patterns. - /** @suppress {checkTypes} suppression added to enable type checking */ - goog.i18n.DateTimePatterns = DateTimePatterns_en_XA; - const fmtEnXa = new DateTimeFormat( - goog.i18n.DateTimePatterns.MONTH_DAY_TIME_ZONE_SHORT); - assertEquals( - '[[Jun 28 one], [13:10 UTC-7 one two] one two]', fmtEnXa.format(date)); - - /** @suppress {checkTypes} suppression added to enable type checking */ - goog.i18n.DateTimePatterns = DateTimePatterns_zh_Hant_TW; - const fmtZhHantTw = new DateTimeFormat( - goog.i18n.DateTimePatterns.MONTH_DAY_TIME_ZONE_SHORT); - assertEquals('6月28日 PM1:10 [UTC-7]', fmtZhHantTw.format(date)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + // Include various locales. + replacer.replace(goog, 'LOCALE', 'en-US'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_en); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en); + const date = new Date(2012, 5, 28, 13, 10, 10, 250); + const fmt = + new DateTimeFormat(DateTimePatterns.MONTH_DAY_TIME_ZONE_SHORT); + assertEquals('Jun 28, 1:10 PM UTC-7', fmt.format(date)); + + replacer.replace(goog, 'LOCALE', 'sv'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_sv); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_sv); + const fmtSv = + new DateTimeFormat(DateTimePatterns_sv.MONTH_DAY_TIME_ZONE_SHORT); + assertEquals('28 juni 13:10 UTC-7', fmtSv.format(date)); + + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_bg); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_bg); + replacer.replace(goog, 'LOCALE', 'bg'); + const fmtBg = + new DateTimeFormat(DateTimePatterns_bg.MONTH_DAY_TIME_ZONE_SHORT); + assertEquals('28.06, 13:10 ч. UTC-7', fmtBg.format(date)); + + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_zh_HK); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_zh_HK); + replacer.replace(goog, 'LOCALE', 'zh_HK'); + const fmtZhHk = + new DateTimeFormat(DateTimePatterns_zh_HK.MONTH_DAY_TIME_ZONE_SHORT); + assertEquals('6月28日 下午1:10 [UTC-7]', fmtZhHk.format(date)); + + // And with explicit timezone. + const timeZone = TimeZone.createTimeZone(-600); + assertEquals('6月29日 上午6:10 [UTC+10]', fmtZhHk.format(date, timeZone)); + + // And some from the extended patterns. + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_en_XA); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en_XA); + replacer.replace(goog, 'LOCALE', 'en_XA'); + const fmtEnXa = new DateTimeFormat( + goog.i18n.DateTimePatterns.MONTH_DAY_TIME_ZONE_SHORT); + assertEquals( + '[[[Ĵûñ one] 28 one], [13:10 UTC-7 one two] one two]', + fmtEnXa.format(date)); + + replacer.replace( + goog.i18n, 'DateTimePatterns', DateTimePatterns_zh_Hant_TW); + replacer.replace( + goog.i18n, 'DateTimeSymbols', DateTimeSymbols_zh_Hant_TW); + replacer.replace(goog, 'LOCALE', 'zh_Hant_TW'); + const fmtZhHantTw = new DateTimeFormat( + goog.i18n.DateTimePatterns.MONTH_DAY_TIME_ZONE_SHORT); + assertEquals('6月28日 下午1:10 [UTC-7]', fmtZhHantTw.format(date)); + } }, testQuote() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 6, 27, 13, 10, 10, 250); - let fmt = new DateTimeFormat('HH \'o\'\'clock\''); - assertEquals('13 o\'clock', fmt.format(date)); - fmt = new DateTimeFormat('HH \'oclock\''); - assertEquals('13 oclock', fmt.format(date)); - fmt = new DateTimeFormat('HH \'\''); - assertEquals('13 \'', fmt.format(date)); + const date = new Date(2006, 6, 27, 13, 10, 10, 250); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + let fmt = new DateTimeFormat('HH \'o\'\'clock\''); + assertEquals('13 o\'clock', fmt.format(date)); + fmt = new DateTimeFormat('HH \'oclock\''); + assertEquals('13 oclock', fmt.format(date)); + fmt = new DateTimeFormat('HH \'\''); + assertEquals('13 \'', fmt.format(date)); + } }, testFractionalSeconds() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 6, 27, 13, 10, 10, 256); - let fmt = new DateTimeFormat('s:S'); - assertEquals('10:3', fmt.format(date)); - fmt = new DateTimeFormat('s:SS'); - assertEquals('10:26', fmt.format(date)); - fmt = new DateTimeFormat('s:SSS'); - assertEquals('10:256', fmt.format(date)); - fmt = new DateTimeFormat('s:SSSS'); - assertEquals('10:2560', fmt.format(date)); - fmt = new DateTimeFormat('s:SSSSS'); - assertEquals('10:25600', fmt.format(date)); - - date = new Date(1960, 6, 27, 13, 10, 10, 256); - fmt = new DateTimeFormat('s:S'); - assertEquals('10:3', fmt.format(date)); - fmt = new DateTimeFormat('s:SS'); - assertEquals('10:26', fmt.format(date)); - fmt = new DateTimeFormat('s:SSS'); - assertEquals('10:256', fmt.format(date)); - fmt = new DateTimeFormat('s:SSSS'); - assertEquals('10:2560', fmt.format(date)); - fmt = new DateTimeFormat('s:SSSSS'); - assertEquals('10:25600', fmt.format(date)); + for (let nativeMode of testECMAScriptOptions) { + let date = new Date(2006, 6, 27, 13, 10, 10, 256); + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + let fmt = new DateTimeFormat('s:S'); + assertEquals('10:3', fmt.format(date)); + fmt = new DateTimeFormat('s:SS'); + assertEquals('10:26', fmt.format(date)); + fmt = new DateTimeFormat('s:SSS'); + assertEquals('10:256', fmt.format(date)); + fmt = new DateTimeFormat('s:SSSS'); + assertEquals('10:2560', fmt.format(date)); + fmt = new DateTimeFormat('s:SSSSS'); + assertEquals('10:25600', fmt.format(date)); + + date = new Date(1960, 6, 27, 13, 10, 10, 256); + fmt = new DateTimeFormat('s:S'); + assertEquals('10:3', fmt.format(date)); + fmt = new DateTimeFormat('s:SS'); + assertEquals('10:26', fmt.format(date)); + fmt = new DateTimeFormat('s:SSS'); + assertEquals('10:256', fmt.format(date)); + fmt = new DateTimeFormat('s:SSSS'); + assertEquals('10:2560', fmt.format(date)); + fmt = new DateTimeFormat('s:SSSSS'); + assertEquals('10:25600', fmt.format(date)); + } }, /** @suppress {checkTypes} suppression added to enable type checking */ testPredefinedFormatter() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - date = new Date(2006, 7, 4, 13, 49, 24, 0); - let fmt = new DateTimeFormat(DateTimeFormat.Format.FULL_DATE); - assertEquals('Freitag, 4. August 2006', fmt.format(date)); - fmt = new DateTimeFormat(DateTimeFormat.Format.LONG_DATE); - assertEquals('4. August 2006', fmt.format(date)); - fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATE); - assertEquals('04.08.2006', fmt.format(date)); - fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); - assertEquals('04.08.06', fmt.format(date)); - fmt = new DateTimeFormat(DateTimeFormat.Format.FULL_TIME); - assertEquals('13:49:24 ' + timezoneString(date), fmt.format(date)); - fmt = new DateTimeFormat(DateTimeFormat.Format.LONG_TIME); - assertEquals('13:49:24 ' + timezoneString(date), fmt.format(date)); - fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_TIME); - assertEquals('13:49:24', fmt.format(date)); - fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_TIME); - assertEquals('13:49', fmt.format(date)); - fmt = new DateTimeFormat(DateTimeFormat.Format.FULL_DATETIME); - assertEquals( - 'Freitag, 4. August 2006 um 13:49:24 ' + timezoneString(date), - fmt.format(date)); - fmt = new DateTimeFormat(DateTimeFormat.Format.LONG_DATETIME); - assertEquals( - '4. August 2006 um 13:49:24 ' + timezoneString(date), fmt.format(date)); - fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATETIME); - assertEquals('04.08.2006, 13:49:24', fmt.format(date)); - fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATETIME); - assertEquals('04.08.06, 13:49', fmt.format(date)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + const date = new Date(2006, 7, 4, 13, 49, 24, 0); + + let fmt = new DateTimeFormat(DateTimeFormat.Format.FULL_DATE); + let result = removeLtrMarkers(fmt.format(date)); + assertEquals('Freitag, 4. August 2006', result); + + fmt = new DateTimeFormat(DateTimeFormat.Format.LONG_DATE); + result = removeLtrMarkers(fmt.format(date)); + assertEquals('4. August 2006', result); + + fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATE); + result = removeLtrMarkers(fmt.format(date)); + assertEquals('nativeMode = ' + nativeMode, '04.08.2006', result); + + fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); + result = removeLtrMarkers(fmt.format(date)); + let expected = '04.08.06'; + assertEquals(expected, result); + + fmt = new DateTimeFormat(DateTimeFormat.Format.FULL_TIME); + result = fmt.format(date); + + // This timezone result does not change with the type of time (FULL..SHORT). + let timezoneResult = timezoneString(date); + const theTime = '13:49:24'; + expected = theTime + ' ' + timezoneResult; + + if (nativeMode) { + const expected2 = theTime + ' Nordamerikanische Westküsten-Sommerzeit'; + assertEquals('nativeMode = ' + nativeMode, + expected2, result); + } else { + assertEquals(expected, result); + } + + fmt = new DateTimeFormat(DateTimeFormat.Format.LONG_TIME); + result = fmt.format(date); + assertEquals('nativeMode = ' + nativeMode, expected, result); + + fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_TIME); + result = removeLtrMarkers(fmt.format(date)); + assertEquals('nativeMode = ' + nativeMode, theTime, result); + + fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_TIME); + result = removeLtrMarkers(fmt.format(date)); + assertEquals('13:49', result); + + fmt = new DateTimeFormat(DateTimeFormat.Format.FULL_DATETIME); + result = removeDirectionMarkers(fmt.format(date)); + expected = nativeMode ? + 'Freitag, 4. August 2006 um 13:49:24 Nordamerikanische Westküsten-Sommerzeit' : + 'Freitag, 4. August 2006 um 13:49:24 UTC-7'; + // TODO(user): Resolve timezone full name + assertEquals('nativeMode=' + nativeMode, expected, result); + + fmt = new DateTimeFormat(DateTimeFormat.Format.LONG_DATETIME); + expected = '4. August 2006 um 13:49:24 ' + timezoneResult; + result = removeDirectionMarkers(fmt.format(date)); + assertEquals('nativeMode=' + nativeMode, expected, result); + + fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATETIME); + // TODO(user): Why this difference? + expected = '04.08.2006, 13:49:24'; + assertEquals('nativeMode=' + nativeMode, expected, fmt.format(date)); + + fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATETIME); + expected = '04.08.06, 13:49'; + assertEquals('nativeMode=' + nativeMode, expected, fmt.format(date)); + } }, testMMddyyyyHHmmssZSimpleTimeZone() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); - const timeZone = TimeZone.createTimeZone(480); - let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); - assertEquals('07/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZ'); - assertEquals('07/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZ'); - assertEquals('07/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZZ'); - assertEquals('07/27/2006 05:10:10 GMT-08:00', fmt.format(date, timeZone)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); + const timeZone = TimeZone.createTimeZone(480); + let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); + assertEquals('07/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZ'); + assertEquals('07/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZ'); + assertEquals('07/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZZ'); + assertEquals('07/27/2006 05:10:10 GMT-08:00', fmt.format(date, timeZone)); + } }, testMMddyyyyHHmmssZCommonTimeZone() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - let date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); - const timeZone = TimeZone.createTimeZone(americaLosAngelesData); - let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); - assertEquals('07/27/2006 06:10:10 -0700', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZ'); - assertEquals('07/27/2006 06:10:10 -0700', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZ'); - assertEquals('07/27/2006 06:10:10 -0700', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZZ'); - assertEquals('07/27/2006 06:10:10 GMT-07:00', fmt.format(date, timeZone)); - date = new Date(Date.UTC(2006, 1, 27, 13, 10, 10)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); - assertEquals('02/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZ'); - assertEquals('02/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZ'); - assertEquals('02/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZZ'); - assertEquals('02/27/2006 05:10:10 GMT-08:00', fmt.format(date, timeZone)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + let date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); + const timeZone = TimeZone.createTimeZone(americaLosAngelesData); + let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); + assertEquals('07/27/2006 06:10:10 -0700', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZ'); + assertEquals('07/27/2006 06:10:10 -0700', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZ'); + assertEquals('07/27/2006 06:10:10 -0700', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZZ'); + assertEquals('07/27/2006 06:10:10 GMT-07:00', fmt.format(date, timeZone)); + + date = new Date(Date.UTC(2006, 1, 27, 13, 10, 10)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); + assertEquals('02/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZ'); + assertEquals('02/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZ'); + assertEquals('02/27/2006 05:10:10 -0800', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss ZZZZ'); + assertEquals('02/27/2006 05:10:10 GMT-08:00', fmt.format(date, timeZone)); + } }, testMMddyyyyHHmmsszSimpleTimeZone() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); - const timeZone = TimeZone.createTimeZone(420); - let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss z'); - assertEquals('07/27/2006 06:10:10 UTC-7', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zz'); - assertEquals('07/27/2006 06:10:10 UTC-7', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzz'); - assertEquals('07/27/2006 06:10:10 UTC-7', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzzz'); - assertEquals('07/27/2006 06:10:10 UTC-7', fmt.format(date, timeZone)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); + const timeZone = TimeZone.createTimeZone(420); + let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss z'); + assertEquals('07/27/2006 06:10:10 UTC-7', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zz'); + assertEquals('07/27/2006 06:10:10 UTC-7', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzz'); + assertEquals('07/27/2006 06:10:10 UTC-7', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzzz'); + assertEquals('07/27/2006 06:10:10 UTC-7', fmt.format(date, timeZone)); + } }, testMMddyyyyHHmmsszCommonTimeZone() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - let date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); - let timeZone = TimeZone.createTimeZone(americaLosAngelesData); - let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss z'); - assertEquals('07/27/2006 06:10:10 PDT', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zz'); - assertEquals('07/27/2006 06:10:10 PDT', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzz'); - assertEquals('07/27/2006 06:10:10 PDT', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzzz'); - assertEquals( - '07/27/2006 06:10:10 Pacific Daylight Time', - fmt.format(date, timeZone)); - date = new Date(Date.UTC(2006, 1, 27, 13, 10, 10)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss z'); - assertEquals('02/27/2006 05:10:10 PST', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zz'); - assertEquals('02/27/2006 05:10:10 PST', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzz'); - assertEquals('02/27/2006 05:10:10 PST', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzzz'); - assertEquals( - '02/27/2006 05:10:10 Pacific Standard Time', - fmt.format(date, timeZone)); - - timeZone = TimeZone.createTimeZone(europeBerlinData); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss z'); - assertEquals('02/27/2006 14:10:10 MEZ', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzzz'); - assertEquals( - '02/27/2006 14:10:10 Mitteleurop\u00e4ische Zeit', - fmt.format(date, timeZone)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + let date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); + let timeZone = TimeZone.createTimeZone(americaLosAngelesData); + let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss z'); + assertEquals('07/27/2006 06:10:10 PDT', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zz'); + assertEquals('07/27/2006 06:10:10 PDT', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzz'); + assertEquals('07/27/2006 06:10:10 PDT', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzzz'); + assertEquals( + '07/27/2006 06:10:10 Pacific Daylight Time', + fmt.format(date, timeZone)); + date = new Date(Date.UTC(2006, 1, 27, 13, 10, 10)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss z'); + assertEquals('02/27/2006 05:10:10 PST', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zz'); + assertEquals('02/27/2006 05:10:10 PST', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzz'); + assertEquals('02/27/2006 05:10:10 PST', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzzz'); + assertEquals( + '02/27/2006 05:10:10 Pacific Standard Time', + fmt.format(date, timeZone)); + + timeZone = TimeZone.createTimeZone(europeBerlinData); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss z'); + assertEquals('02/27/2006 14:10:10 MEZ', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss zzzz'); + assertEquals( + '02/27/2006 14:10:10 Mitteleurop\u00e4ische Zeit', + fmt.format(date, timeZone)); + } }, testMMddyyyyHHmmssvCommonTimeZone() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); - const timeZone = TimeZone.createTimeZone(americaLosAngelesData); - let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss v'); - assertEquals( - '07/27/2006 06:10:10 America/Los_Angeles', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vv'); - assertEquals( - '07/27/2006 06:10:10 America/Los_Angeles', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vvv'); - assertEquals( - '07/27/2006 06:10:10 America/Los_Angeles', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vvvv'); - assertEquals( - '07/27/2006 06:10:10 America/Los_Angeles', fmt.format(date, timeZone)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); + const timeZone = TimeZone.createTimeZone(americaLosAngelesData); + let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss v'); + assertEquals( + '07/27/2006 06:10:10 America/Los_Angeles', + fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vv'); + assertEquals( + '07/27/2006 06:10:10 America/Los_Angeles', + fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vvv'); + assertEquals( + '07/27/2006 06:10:10 America/Los_Angeles', + fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vvvv'); + assertEquals( + '07/27/2006 06:10:10 America/Los_Angeles', + fmt.format(date, timeZone)); + } }, testMMddyyyyHHmmssvSimpleTimeZone() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); - const timeZone = TimeZone.createTimeZone(420); - let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss v'); - assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vv'); - assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vvv'); - assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vvvv'); - assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); + const timeZone = TimeZone.createTimeZone(420); + let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss v'); + assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vv'); + assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vvv'); + assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss vvvv'); + assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); + } }, testMMddyyyyHHmmssVCommonTimeZone() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); - const timeZone = TimeZone.createTimeZone(americaLosAngelesData); - let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss V'); - assertEquals( - '07/27/2006 06:10:10 America/Los_Angeles', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VV'); - assertEquals( - '07/27/2006 06:10:10 America/Los_Angeles', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VVV'); - assertEquals( - '07/27/2006 06:10:10 Los Angeles Time', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VVVV'); - assertEquals( - '07/27/2006 06:10:10 Los Angeles Time', fmt.format(date, timeZone)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); + const timeZone = TimeZone.createTimeZone(americaLosAngelesData); + let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss V'); + assertEquals( + '07/27/2006 06:10:10 America/Los_Angeles', + fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VV'); + assertEquals( + '07/27/2006 06:10:10 America/Los_Angeles', + fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VVV'); + assertEquals( + '07/27/2006 06:10:10 Los Angeles Time', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VVVV'); + assertEquals( + '07/27/2006 06:10:10 Los Angeles Time', fmt.format(date, timeZone)); + } }, testMMddyyyyHHmmssVSimpleTimeZone() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); - const timeZone = TimeZone.createTimeZone(420); - let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss V'); - assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VV'); - assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VVV'); - assertEquals('07/27/2006 06:10:10 GMT-07:00', fmt.format(date, timeZone)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VVVV'); - assertEquals('07/27/2006 06:10:10 GMT-07:00', fmt.format(date, timeZone)); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + const date = new Date(Date.UTC(2006, 6, 27, 13, 10, 10)); + const timeZone = TimeZone.createTimeZone(420); + let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss V'); + assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VV'); + assertEquals('07/27/2006 06:10:10 Etc/GMT+7', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VVV'); + assertEquals('07/27/2006 06:10:10 GMT-07:00', fmt.format(date, timeZone)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss VVVV'); + assertEquals('07/27/2006 06:10:10 GMT-07:00', fmt.format(date, timeZone)); + } }, test_yyyyMMddG() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + const date = new Date(Date.UTC(2006, 6, 27, 20, 10, 10)); - let timeZone = TimeZone.createTimeZone(420); - let fmt = new DateTimeFormat('yyyy.MM.dd G \'at\' HH:mm:ss vvvv'); - assertEquals( - '2006.07.27 n. Chr. at 13:10:10 Etc/GMT+7', fmt.format(date, timeZone)); - timeZone = TimeZone.createTimeZone(americaLosAngelesData); - fmt = new DateTimeFormat('yyyy.MM.dd G \'at\' HH:mm:ss vvvv'); - assertEquals( - '2006.07.27 n. Chr. at 13:10:10 America/Los_Angeles', - fmt.format(date, timeZone)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + let timeZone = TimeZone.createTimeZone(420); + let fmt = new DateTimeFormat('yyyy.MM.dd G \'at\' HH:mm:ss vvvv'); + assertEquals( + '2006.07.27 n. Chr. at 13:10:10 Etc/GMT+7', + fmt.format(date, timeZone)); + + timeZone = TimeZone.createTimeZone(americaLosAngelesData); + fmt = new DateTimeFormat('yyyy.MM.dd G \'at\' HH:mm:ss vvvv'); + assertEquals( + '2006.07.27 n. Chr. at 13:10:10 America/Los_Angeles', + fmt.format(date, timeZone)); + } }, test_daylightTimeTransition() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); // US PST transition to PDT on 2006/4/2/ 2:00am, jump to 2006/4/2 3:00am, // That's UTC time 2006/4/2 10:00am @@ -684,283 +979,455 @@ testSuite({ }, test_timeDisplayOnDaylighTimeTransition() { - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - - // US PST transition to PDT on 2006/4/2/ 2:00am, jump to 2006/4/2 3:00am, - let date = new Date(Date.UTC(2006, 4 - 1, 2, 2, 30, 0)); - const timeZone = TimeZone.createTimeZone(0); - let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); - assertEquals('04/02/2006 02:30:00 +0000', fmt.format(date, timeZone)); - - // US PDT transition to PST on 2006/10/29/ 2:00am, jump back to PDT - // 2006/4/2 1:00am, - date = new Date(Date.UTC(2006, 10 - 1, 29, 1, 30, 0)); - fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); - assertEquals('10/29/2006 01:30:00 +0000', fmt.format(date, timeZone)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + + // US PST transition to PDT on 2006/4/2/ 2:00am, jump to 2006/4/2 3:00am, + let date = new Date(Date.UTC(2006, 4 - 1, 2, 2, 30, 0)); + const timeZone = TimeZone.createTimeZone(0); + let fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); + assertEquals('04/02/2006 02:30:00 +0000', fmt.format(date, timeZone)); + + // US PDT transition to PST on 2006/10/29/ 2:00am, jump back to PDT + // 2006/4/2 1:00am, + date = new Date(Date.UTC(2006, 10 - 1, 29, 1, 30, 0)); + fmt = new DateTimeFormat('MM/dd/yyyy HH:mm:ss Z'); + assertEquals('10/29/2006 01:30:00 +0000', fmt.format(date, timeZone)); + } }, testTimeDisplayOnDaylightTimeTransitionDayChange() { // NOTE: this test is a regression test only if the test browser has an OS // timezone of PST. While the test should still work in other timezones, it // does not serve as a regression test in them. - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - - // Time is 2015/11/01 3:00:01am after US PDT -> PST. 11:00:01am UTC. - const date = new Date(Date.UTC(2015, 11 - 1, 1, 11, 0, 1)); - // Convert to GMT-12, across DST transition. - // The date should also change, but does not change when subtracting 4 hours - // from PST/PDT due to the extra hour from switching DST. - const timeZone = TimeZone.createTimeZone(12 * 60); - const fmt = new DateTimeFormat('yyyy/MM/dd HH:mm:ss Z'); - // Regression test: this once returned 2015/11/01 instead. - assertEquals('2015/10/31 23:00:01 -1200', fmt.format(date, timeZone)); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + replacer.replace(goog, 'LOCALE', 'de'); + + // Time is 2015/11/01 3:00:01am after US PDT -> PST. 11:00:01am UTC. + const date = new Date(Date.UTC(2015, 11 - 1, 1, 11, 0, 1)); + // Convert to GMT-12, across DST transition. + // The date should also change, but does not change when subtracting 4 + // hours from PST/PDT due to the extra hour from switching DST. + const timeZone = TimeZone.createTimeZone(12 * 60); + const fmt = new DateTimeFormat('yyyy/MM/dd HH:mm:ss Z'); + // Regression test: this once returned 2015/11/01 instead. + assertEquals('2015/10/31 23:00:01 -1200', fmt.format(date, timeZone)); + } }, test_nativeDigits_fa() { - goog.i18n.DateTimePatterns = DateTimePatterns_fa; - goog.i18n.DateTimeSymbols = DateTimeSymbols_fa; - - date = new Date(2006, 7 - 1, 27, 13, 10, 10, 250); - const timeZone = TimeZone.createTimeZone(420); - let fmt = new DateTimeFormat('y/MM/dd H:mm:ss٫SS'); - assertEquals('۲۰۰۶/۰۷/۲۷ ۱۳:۱۰:۱۰٫۲۵', fmt.format(date)); - - // Make sure standardized timezone formats don't use native digits - fmt = new DateTimeFormat('Z'); - assertEquals('-0700', fmt.format(date, timeZone)); + const date = new Date(2006, 7 - 1, 27, 13, 10, 10, 250); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'fa'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_fa); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_fa); + + const timeZone = TimeZone.createTimeZone(420); + let fmt = new DateTimeFormat('y/MM/dd H:mm:ss٫SS'); + assertEquals('۲۰۰۶/۰۷/۲۷ ۱۳:۱۰:۱۰٫۲۵', fmt.format(date)); + + // Make sure standardized timezone formats don't use native digits + fmt = new DateTimeFormat('Z'); + assertEquals('-0700', fmt.format(date, timeZone)); + } }, test_nativeDigits_ar() { - goog.i18n.DateTimePatterns = DateTimePatterns_ar_EG; - goog.i18n.DateTimeSymbols = DateTimeSymbols_ar_EG; - - date = new Date(2006, 7 - 1, 27, 13, 10, 10, 250); - const timeZone = TimeZone.createTimeZone(420); - let fmt = new DateTimeFormat('y/MM/dd H:mm:ss٫SS'); - assertEquals('٢٠٠٦/٠٧/٢٧ ١٣:١٠:١٠٫٢٥', fmt.format(date)); - - fmt = new DateTimeFormat(11); - assertEquals('٢٧\u200f/٧\u200f/٢٠٠٦, ١:١٠ م', fmt.format(date)); - - // Make sure standardized timezone formats don't use native digits - fmt = new DateTimeFormat('Z'); - assertEquals('-0700', fmt.format(date, timeZone)); + const date = new Date(2006, 7 - 1, 27, 13, 10, 10, 250); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'ar_EG'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_ar_EG); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_ar_EG); + + const timeZone = TimeZone.createTimeZone(420); + let fmt = new DateTimeFormat('y/MM/dd H:mm:ss٫SS'); + assertEquals('٢٠٠٦/٠٧/٢٧ ١٣:١٠:١٠٫٢٥', fmt.format(date)); + + fmt = new DateTimeFormat(11); + let result = fmt.format(date); + const hasAsciiDigit = /[0-9]/.test(result); + assertFalse('No ASCII digits = ' + result, hasAsciiDigit); + + // Make sure standardized timezone formats don't use native digits + fmt = new DateTimeFormat('Z'); + assertEquals('-0700', fmt.format(date, timeZone)); + } }, test_enforceAsciiDigits_ar() { - goog.i18n.DateTimePatterns = DateTimePatterns_ar_EG; - goog.i18n.DateTimeSymbols = DateTimeSymbols_ar_EG; - - DateTimeFormat.setEnforceAsciiDigits(true); - date = new Date(2006, 7 - 1, 27, 13, 10, 10, 250); - const timeZone = TimeZone.createTimeZone(420); - let fmt = new DateTimeFormat('y/MM/dd H:mm:ss٫SS'); - assertEquals('2006/07/27 13:10:10٫25', fmt.format(date)); - - fmt = new DateTimeFormat(11); - assertEquals('27/7/2006, 1:10 م', fmt.format(date)); - - // Make sure standardized timezone formats don't use native digits - fmt = new DateTimeFormat('Z'); - assertEquals('-0700', fmt.format(date, timeZone)); + const date = new Date(2006, 7 - 1, 27, 13, 10, 10, 250); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + replacer.replace(goog, 'LOCALE', 'ar_EG'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_ar_EG); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_ar_EG); + + DateTimeFormat.setEnforceAsciiDigits(true); + const timeZone = TimeZone.createTimeZone(420); + let fmt = new DateTimeFormat('y/MM/dd H:mm:ss٫SS'); + assertEquals('2006/07/27 13:10:10٫25', fmt.format(date)); + + fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATETIME); + let result = fmt.format(date); + const hasArabicDigit = /[\u0660-\u0669]/.test(result); + assertFalse('Expect only ASCII = ' + result, hasArabicDigit); + + // Remove LTR, RTL markers + result = result.replace(/\u200e/g, '').replace(/\u200f/g, ''); + + // Make sure standardized timezone formats don't use native digits + fmt = new DateTimeFormat('Z'); + assertEquals('-0700', fmt.format(date, timeZone)); + + // Check with another locale. + replacer.replace(goog, 'LOCALE', 'my'); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_my); + fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATETIME); + result = fmt.format(date); + const hasMyanmarDigit = /[\u1040-\u1049]/.test(result); + assertFalse('No Myamnar digit = ' + result, hasMyanmarDigit); + } }, // Making sure that the date-time combination is not a simple concatenation test_dateTimeConcatenation() { - const date = new Date(Date.UTC(2006, 4 - 1, 2, 2, 30, 0)); - const timeZone = TimeZone.createTimeZone(americaLosAngelesData); - let fmt = new DateTimeFormat(DateTimeFormat.Format.FULL_DATETIME); - // {1} 'at' {0} - assertEquals( - 'Saturday, April 1, 2006 at 6:30:00 PM Pacific Standard Time', - fmt.format(date, timeZone)); - fmt = new DateTimeFormat(DateTimeFormat.Format.LONG_DATETIME); - assertEquals('April 1, 2006 at 6:30:00 PM PST', fmt.format(date, timeZone)); - // {1}, {0} - fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATETIME); - assertEquals('Apr 1, 2006, 6:30:00 PM', fmt.format(date, timeZone)); - fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATETIME); - assertEquals('4/1/06, 6:30 PM', fmt.format(date, timeZone)); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_en); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en); + replacer.replace(goog, 'LOCALE', 'en'); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + const date = new Date(Date.UTC(2006, 4 - 1, 2, 2, 30, 0)); + const timeZone = TimeZone.createTimeZone(americaLosAngelesData); + + let fmt = new DateTimeFormat(DateTimeFormat.Format.FULL_DATETIME); + + let result = fmt.format(date, timeZone); + let expect = + 'Saturday, April 1, 2006 at 6:30:00 PM Pacific Standard Time'; + + assertEquals('nativeMode=' + nativeMode, expect, result); + + fmt = new DateTimeFormat(DateTimeFormat.Format.LONG_DATETIME); + result = fmt.format(date, timeZone); + expect = 'April 1, 2006 at 6:30:00 PM PST'; + assertEquals(expect, result); + + fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATETIME); + expect = 'Apr 1, 2006, 6:30:00 PM'; + result = removeLtrMarkers(fmt.format(date, timeZone)); + let matched = + /Apr 1, 2006(,| at)? 6:30:00 PM/.test(result); // Optional comma + assertTrue('MEDIUM_DATETIME result = ' + result, + matched); + + fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATETIME); + expect = '4/1/06, 6:30 PM'; + assertEquals( + 'SHORT_DATETIME = ' + result, expect, fmt.format(date, timeZone)); + } }, testNotUsingGlobalSymbols() { - date = new Date(2013, 10, 15); + const date = new Date(2013, 10, 15); - goog.i18n.DateTimePatterns = DateTimePatterns_fr; - goog.i18n.DateTimeSymbols = DateTimeSymbols_fr; - const fmtFr = new DateTimeFormat(DateTimeFormat.Format.FULL_DATE); - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - const fmtDe = new DateTimeFormat(DateTimeFormat.Format.FULL_DATE); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); - // The two formatters should return different results (French & German) - assertEquals('vendredi 15 novembre 2013', fmtFr.format(date)); - assertEquals('Freitag, 15. November 2013', fmtDe.format(date)); - }, + replacer.replace(goog, 'LOCALE', 'fr'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_fr); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_fr); + const fmtFr = new DateTimeFormat(DateTimeFormat.Format.FULL_DATE); - testConstructorSymbols() { - date = new Date(2013, 10, 15); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + const fmtDe = new DateTimeFormat(DateTimeFormat.Format.FULL_DATE); - const fmtFr = - new DateTimeFormat(DateTimeFormat.Format.FULL_DATE, DateTimeSymbols_fr); - - const fmtDe = - new DateTimeFormat(DateTimeFormat.Format.FULL_DATE, DateTimeSymbols_de); + // The two formatters should return different results (French & German) + // Remove LTR marks if present. + const frResult = fmtFr.format(date).replace(/\u200c/g, ''); + assertEquals('vendredi 15 novembre 2013', frResult); + const deResult = fmtDe.format(date).replace(/\u200c/g, ''); + assertEquals('Freitag, 15. November 2013', deResult); + } + }, - // The two formatters should return different results (French & German) - assertEquals('vendredi 15 novembre 2013', fmtFr.format(date)); - assertEquals('Freitag, 15. November 2013', fmtDe.format(date)); + testConstructorSymbols() { + const date = new Date(2013, 10, 15); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + replacer.replace(goog, 'LOCALE', 'fr'); + const fmtFr = new DateTimeFormat( + DateTimeFormat.Format.FULL_DATE, DateTimeSymbols_fr); + + replacer.replace(goog, 'LOCALE', 'de'); + const fmtDe = new DateTimeFormat( + DateTimeFormat.Format.FULL_DATE, DateTimeSymbols_de); + + // The two formatters should return different results (French & German) + // Remove LTR marks if present. + let expected = '‎vendredi 15 novembre 2013'.replace(/\u200e/g, ''); + const frResult = + fmtFr.format(date).replace(/\u200c/g, '').replace(/\u200e/g, ''); + assertEquals(expected, frResult); + expected = 'Freitag, 15. November 2013'.replace(/\u200e/g, ''); + const deResult = + fmtDe.format(date).replace(/\u200c/g, '').replace(/\u200e/g, ''); + assertEquals(expected, deResult); + } }, testQuotedPattern() { // Regression test for b/29990921. - goog.i18n.DateTimeSymbols = DateTimeSymbols_en; - date = new Date(2013, 10, 15); - - // Literal apostrophe - let fmt = new DateTimeFormat('MMM \'\'yy'); - assertEquals('Nov \'13', fmt.format(date)); - // Quoted text - fmt = new DateTimeFormat('MMM dd\'th\' yyyy'); - assertEquals('Nov 15th 2013', fmt.format(date)); - // Quoted text (only opening apostrophe) - fmt = new DateTimeFormat('MMM dd\'th yyyy'); - assertEquals('Nov 15th yyyy', fmt.format(date)); - // Quoted text with literal apostrophe - fmt = new DateTimeFormat('MMM dd\'th\'\'\''); - assertEquals('Nov 15th\'', fmt.format(date)); - // Quoted text with literal apostrophe (only opening apostrophe) - fmt = new DateTimeFormat('MMM dd\'th\'\''); - assertEquals('Nov 15th\'', fmt.format(date)); + const date = new Date(2013, 10, 15); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en); + + // Literal apostrophe + let fmt = new DateTimeFormat('MMM \'\'yy'); + assertEquals('Nov \'13', fmt.format(date)); + // Quoted text + fmt = new DateTimeFormat('MMM dd\'th\' yyyy'); + assertEquals('Nov 15th 2013', fmt.format(date)); + // Quoted text (only opening apostrophe) + fmt = new DateTimeFormat('MMM dd\'th yyyy'); + assertEquals('Nov 15th yyyy', fmt.format(date)); + // Quoted text with literal apostrophe + fmt = new DateTimeFormat('MMM dd\'th\'\'\''); + assertEquals('Nov 15th\'', fmt.format(date)); + // Quoted text with literal apostrophe (only opening apostrophe) + fmt = new DateTimeFormat('MMM dd\'th\'\''); + assertEquals('Nov 15th\'', fmt.format(date)); + } }, testSupportForWeekInYear() { - const date = new Date(2013, 1, 25); - - goog.i18n.DateTimePatterns = DateTimePatterns_fr; - goog.i18n.DateTimeSymbols = DateTimeSymbols_fr; - let fmt = new DateTimeFormat('\'week\' w'); - assertEquals('week 9', fmt.format(date)); - fmt = new DateTimeFormat('\'week\' ww'); - assertEquals('week 09', fmt.format(date)); - - // Make sure it uses native digits when needed - goog.i18n.DateTimePatterns = DateTimePatterns_fa; - goog.i18n.DateTimeSymbols = DateTimeSymbols_fa; - fmt = new DateTimeFormat('\'week\' w'); - assertEquals('week ۹', fmt.format(date)); - fmt = new DateTimeFormat('\'week\' ww'); - assertEquals('week ۰۹', fmt.format(date)); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + const date = new Date(2013, 1, 25); + + replacer.replace(goog, 'LOCALE', 'fr'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_fr); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_fr); + let fmt = new DateTimeFormat('\'week\' w'); + assertEquals('week 9', fmt.format(date)); + fmt = new DateTimeFormat('\'week\' ww'); + assertEquals('week 09', fmt.format(date)); + + // Make sure it uses native digits when needed + replacer.replace(goog, 'LOCALE', 'fa'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_fa); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_fa); + fmt = new DateTimeFormat('\'week\' w'); + assertEquals('week ۹', fmt.format(date)); + fmt = new DateTimeFormat('\'week\' ww'); + assertEquals('week ۰۹', fmt.format(date)); + } }, testSupportYearOfWeek() { - const date = new Date(2005, 0, 2); - - goog.i18n.DateTimePatterns = DateTimePatterns_fr; - goog.i18n.DateTimeSymbols = DateTimeSymbols_fr; - let fmt = new DateTimeFormat('YYYY'); - assertEquals('2004', fmt.format(date)); - fmt = new DateTimeFormat('YY'); - assertEquals('04', fmt.format(date)); + replacer.replace(goog, 'LOCALE', 'fr'); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + const date = new Date(2005, 0, 2); + + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_fr); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_fr); + let fmt = new DateTimeFormat('YYYY'); + assertEquals('2004', fmt.format(date)); + fmt = new DateTimeFormat('YY'); + assertEquals('04', fmt.format(date)); + } }, testSupportForYearAndEra() { - const date = new Date(2013, 1, 25); - let fmt = new DateTimeFormat(goog.i18n.DateTimePatterns.YEAR_FULL_WITH_ERA); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + replacer.replace(goog, 'LOCALE', 'en'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_en); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en); + const date = new Date(2013, 1, 25); + let fmt = new DateTimeFormat(DateTimePatterns.YEAR_FULL_WITH_ERA); - assertEquals('2013 AD', fmt.format(date)); + assertEquals('2013 AD', fmt.format(date)); - date.setFullYear(213); - assertEquals('213 AD', fmt.format(date)); + date.setFullYear(213); + assertEquals('213 AD', fmt.format(date)); - date.setFullYear(11); - assertEquals('11 AD', fmt.format(date)); + date.setFullYear(11); + assertEquals('11 AD', fmt.format(date)); - date.setFullYear(-213); - assertEquals('213 BC', fmt.format(date)); + date.setFullYear(-213); + assertEquals('213 BC', fmt.format(date)); - goog.i18n.DateTimePatterns = DateTimePatterns_de; - goog.i18n.DateTimeSymbols = DateTimeSymbols_de; - fmt = new DateTimeFormat(goog.i18n.DateTimePatterns.YEAR_FULL_WITH_ERA); + replacer.replace(goog, 'LOCALE', 'de'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_de); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_de); + fmt = new DateTimeFormat(DateTimePatterns_de.YEAR_FULL_WITH_ERA); - date.setFullYear(2013); - assertEquals('2013 n. Chr.', fmt.format(date)); + date.setFullYear(2013); + assertEquals('2013 n. Chr.', fmt.format(date)); - date.setFullYear(213); - assertEquals('213 n. Chr.', fmt.format(date)); + date.setFullYear(213); + assertEquals('213 n. Chr.', fmt.format(date)); - date.setFullYear(11); - assertEquals('11 n. Chr.', fmt.format(date)); + date.setFullYear(11); + assertEquals('11 n. Chr.', fmt.format(date)); - date.setFullYear(-213); - assertEquals('213 v. Chr.', fmt.format(date)); + date.setFullYear(-213); + assertEquals('213 v. Chr.', fmt.format(date)); - goog.i18n.DateTimePatterns = DateTimePatterns_ja; - goog.i18n.DateTimeSymbols = DateTimeSymbols_ja; - fmt = new DateTimeFormat(goog.i18n.DateTimePatterns.YEAR_FULL_WITH_ERA); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_ja); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_ja); + fmt = new DateTimeFormat(DateTimePatterns_ja.YEAR_FULL_WITH_ERA); - date.setFullYear(2013); - assertEquals('西暦2013年', fmt.format(date)); + date.setFullYear(2013); + assertEquals('西暦2013年', fmt.format(date)); - date.setFullYear(213); - assertEquals('西暦213年', fmt.format(date)); + date.setFullYear(213); + assertEquals('西暦213年', fmt.format(date)); - date.setFullYear(11); - assertEquals('西暦11年', fmt.format(date)); + date.setFullYear(11); + assertEquals('西暦11年', fmt.format(date)); - date.setFullYear(-213); - assertEquals('紀元前213年', fmt.format(date)); + date.setFullYear(-213); + assertEquals('紀元前213年', fmt.format(date)); + } }, // Expected results from ICU4J v51. One entry will change in v52. // These cover all combinations of FIRSTDAYOFWEEK / FIRSTWEEKCUTOFFDAY in use. testWeekInYearI18n() { - goog.i18n.DateTimeSymbols = DateTimeSymbols_bn_BD; - assertEquals('bn_BD', '১১১১১২২', weekInYearFor7Days()); - goog.i18n.DateTimeSymbols = DateTimeSymbols_en_IE; - assertEquals('en_IE', '1111112', weekInYearFor7Days()); - goog.i18n.DateTimeSymbols = DateTimeSymbols_fr_DJ; - assertEquals('fr_DJ', '1111222', weekInYearFor7Days()); - goog.i18n.DateTimeSymbols = DateTimeSymbols_he_IL; - assertEquals('he_IL', '1111122', weekInYearFor7Days()); - goog.i18n.DateTimeSymbols = DateTimeSymbols_ar_SA; - assertEquals('ar_SA', '١١١١١٢٢', weekInYearFor7Days()); - goog.i18n.DateTimeSymbols = DateTimeSymbols_ar_AE; - assertEquals('ar_AE', '١١١١٢٢٢', weekInYearFor7Days()); - goog.i18n.DateTimeSymbols = DateTimeSymbols_en_IN; - assertEquals('en_IN', '1111122', weekInYearFor7Days()); - goog.i18n.DateTimeSymbols = DateTimeSymbols_en_GB; - assertEquals('en_GB', '1111112', weekInYearFor7Days()); - goog.i18n.DateTimeSymbols = DateTimeSymbols_en_US; - assertEquals('en_US', '1111122', weekInYearFor7Days()); - goog.i18n.DateTimeSymbols = DateTimeSymbols_ro_RO; - assertEquals('ro_RO', '1111112', weekInYearFor7Days()); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_bn_BD); + assertEquals('bn_BD', '১১১১১২২', weekInYearFor7Days()); + + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en_IE); + assertEquals('en_IE', '1111112', weekInYearFor7Days()); + + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_fr_DJ); + assertEquals('fr_DJ', '1111222', weekInYearFor7Days()); + + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_he_IL); + assertEquals('he_IL', '1111122', weekInYearFor7Days()); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_ar_SA); + assertEquals('ar_SA', '١١١١١٢٢', weekInYearFor7Days()); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_ar_AE); + assertEquals('ar_AE', '١١١١٢٢٢', weekInYearFor7Days()); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en_IN); + assertEquals('en_IN', '1111122', weekInYearFor7Days()); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en_GB); + assertEquals('en_GB', '1111112', weekInYearFor7Days()); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en_US); + assertEquals('en_US', '1111122', weekInYearFor7Days()); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_ro_RO); + assertEquals('ro_RO', '1111112', weekInYearFor7Days()); + } }, // Regression for b/11567443 (no method 'getHours' when formatting a // goog.date.Date) test_variousDateTypes() { - goog.i18n.DateTimePatterns = DateTimePatterns_fr; - goog.i18n.DateTimeSymbols = DateTimeSymbols_fr; - - const fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATETIME); - - const date = new Date(2006, 6, 27, 13, 10, 42, 250); - assertEquals('27 juil. 2006, 13:10:42', fmt.format(date)); - - const gdatetime = new DateTime(2006, 6, 27, 13, 10, 42, 250); - assertEquals('27 juil. 2006, 13:10:42', fmt.format(gdatetime)); - - const gdate = new DateDate(2006, 6, 27); - const fmtDate = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATE); - assertEquals('27 juil. 2006', fmtDate.format(gdatetime)); - try { - fmt.format(gdate); - fail('Should have thrown exception.'); - } catch (e) { + replacer.replace(goog, 'LOCALE', 'fr'); + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_fr); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_fr); + + + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + const fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATETIME); + + const date = new Date(2006, 6, 27, 13, 10, 42, 250); + let expected = '27 juil. 2006, 13:10:42'; + let alternate = '27 juil. 2006 à 13:10:42'; + let result = removeLtrMarkers(fmt.format(date)); + + // Some browsers use the alternate form. + if (nativeMode) { + assertTrue( + 'Native 27 juil = ' + result, + expected === result || alternate === result); + } else { + // Polyfill mode + assertEquals(expected, result); + } + + const gdatetime = new DateTime(2006, 6, 27, 13, 10, 42, 250); + expected = '27 juil. 2006, 13:10:42'; + alternate = '27 juil. 2006 à 13:10:42'; + result = removeLtrMarkers(fmt.format(gdatetime)); + + if (nativeMode) { + assertTrue( + 'gdatetime MEDIUM_DATETIME juil. result = ' + result, + expected === result || alternate === result); + } else { + assertEquals(expected, result); + } + + const gdate = new DateDate(2006, 6, 27); + const fmtDate = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATE); + + expected = '27 juil. 2006'; + alternate = removeDirectionMarkers('27/07/2006'); + result = removeDirectionMarkers(fmtDate.format(gdatetime)); + + assertTrue( + 'Native mode = ' + nativeMode + ' gdate MEDIUM_DATE = ' + result, + result === expected || result === alternate); + + if (!nativeMode) { + try { + fmt.format(gdate); + fail('Should have thrown exception.'); + } catch (e) { + } + } else { + // Native mode returns midnight without explicit time set. + // Make better match to remove commas. + const result = removeDirectionMarkers(fmt.format(gdate)) + .replace(/[\u002c\u060c]/g, ''); + expected = '27 juil. 2006 0:00:00'; + const alternate = '27 juil. 2006 à 00:00:00'; + const alternate2 = '27 juil. 2006 à 0:00:00'; + const alternate3 = '27 juil. 2006 00:00:00'; + assertTrue( + 'Midnight result = ' + result, + result === expected || result === alternate || + result === alternate2 || result === alternate3); + } } }, @@ -972,4 +1439,275 @@ testSuite({ } catch (e) { } }, + + testNativeModeEnglishStdPatterns() { + for (let nativeMode of testECMAScriptOptions) { + replacer.replace(goog, 'LOCALE', 'en-US'); + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + const fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATETIME); + + assertEquals(10, DateTimeFormat.Format.MEDIUM_DATETIME); + + const date = new Date(2006, 6, 27, 13, 10, 42, 250); + let result = fmt.format(date); + let expected = 'Jul 27, 2006, 1:10:42 PM'; + result = removeLtrMarkers(result); + let matched = + /Jul 27, 2006(,| at)? 1:10:42 PM/.test(result); // Optional comma + assertTrue('Expected match = ' + result, matched); + + const gdatetime = new DateTime(2006, 6, 27, 13, 10, 42, 250); + + result = removeLtrMarkers(fmt.format(gdatetime)); + matched = /Jul 27, 2006(,| at)? 1:10:42 PM/.test(result); // Optional comma + if (matched) { + assertTrue(matched); + } else { + assertEquals('Jul 27, 2006, 1:10:42 PM', result); + } + + const fmtDate = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATE); + result = removeDirectionMarkers(fmtDate.format(gdatetime)); + expected = 'Jul 27, 2006'; + assertEquals(expected, result); + + const fmt0 = new DateTimeFormat(DateTimeFormat.Format.FULL_DATE); + result = removeDirectionMarkers(fmt0.format(gdatetime)); + assertEquals('Thursday, July 27, 2006', result); + + const fmt1 = new DateTimeFormat(DateTimeFormat.Format.SHORT_TIME); + result = removeDirectionMarkers(fmt1.format(gdatetime)); + assertEquals('1:10 PM', result); + + const fmt2 = new DateTimeFormat(DateTimeFormat.Format.FULL_DATETIME); + result = removeDirectionMarkers(fmt2.format(gdatetime)); + + if (nativeMode) { + expected = 'Thursday, July 27, 2006 at 1:10:42 PM Pacific Daylight Time'; + } else { + expected = 'Thursday, July 27, 2006 at 1:10:42 PM UTC-7'; + } + assertEquals('nativeMode=' + nativeMode, expected, result); + + const fmt3 = new DateTimeFormat(DateTimeFormat.Format.LONG_DATE); + result = removeDirectionMarkers(fmt3.format(gdatetime)); + assertEquals('July 27, 2006', result); + } + }, + + test_FrenchPatternStrings() { + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + replacer.replace(goog.i18n, 'DateTimePatterns', DateTimePatterns_fr); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_fr); + replacer.replace(goog, 'LOCALE', 'fr'); + + const fmt = new DateTimeFormat(DateTimePatterns_fr.YEAR_FULL); + + const date = new Date(2006, 6, 27, 13, 10, 42, 250); + let result = fmt.format(date); + assertEquals('2006', result); + + const fmt1 = new DateTimeFormat(DateTimePatterns_fr.YEAR_FULL_WITH_ERA); + result = fmt1.format(date); + assertEquals('2006 ap. J.-C.', result); + + const fmt2 = new DateTimeFormat(DateTimePatterns_fr.MONTH_DAY_SHORT); + result = fmt2.format(date); + assertEquals('27/07', result); + + const fmt3 = + new DateTimeFormat(DateTimePatterns_fr.MONTH_DAY_TIME_ZONE_SHORT); + result = fmt3.format(date); + assertEquals('27 juil., 13:10 UTC-7', result); + } + }, + + test_NativeModeWithUnsupportedLocale() { + setNativeMode(true); + if (goog.labs.userAgent.browser.isIE() || + goog.global.Intl == undefined || + goog.global.Intl.DateTimeFormat == undefined) { + return; + } + + // Sumerian will default to English. + replacer.replace(goog, 'LOCALE', 'sux'); + + const nativeSetting = getNativeMode(); + assert(nativeSetting); + + const fmt = new DateTimeFormat(DateTimeFormat.Format.MEDIUM_DATETIME); + + const date = new Date(2006, 6, 27, 13, 10, 42, 250); + let result = removeLtrMarkers(fmt.format(date)).replace(/2006,/g, '2006'); + let matched = /Jul 27, 2006 (at )?1:10:42 PM/.test(result); + assertTrue('MEDIUM_DATETIME result = ' + result, + matched); + + const fmt1 = new DateTimeFormat(DateTimePatterns.YEAR_FULL); + + result = fmt1.format(date); + assertEquals('2006', result); + }, + + testNonAsciiDigitsNative() { + const date = new Date(2006, 6, 27, 13, 10, 42, 250); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + // Arabic with ASCII + replacer.replace(goog, 'LOCALE', 'ar'); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_ar); + const ar = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); + let expected = removeDirectionMarkers('27‏/7‏/2006'); + let result = removeDirectionMarkers(ar.format(date)); + assertEquals('ar', expected, result); + + // Egyptian Arabic + replacer.replace(goog, 'LOCALE', 'ar-EG'); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_ar_EG); + const ar_EG = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); + expected = '٢٧‏/٧‏/٢٠٠٦'; + let expected2 = '٢٧‏/٠٧‏/٢٠٠٦'; // Zero digit in month + result = ar_EG.format(date); + result = removeDirectionMarkers(result); + assertTrue( + 'ar-EG', + removeDirectionMarkers(expected) == removeDirectionMarkers(result) || + removeDirectionMarkers(expected2) == + removeDirectionMarkers(result)); + + // Bengali + replacer.replace(goog, 'LOCALE', 'bn'); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_bn); + const bn = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); + expected = '২৭/৭/০৬'; + result = bn.format(date); + assertEquals('bn', expected, result); + + replacer.replace(goog, 'LOCALE', 'bn_BD'); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_bn); + const bn_BD = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); + result = bn_BD.format(date); + assertEquals('bn_BD', expected, result); + + // Persian / Farsi + replacer.replace(goog, 'LOCALE', 'fa'); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_fa); + const fa = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); + expected = + removeDirectionMarkers('۲۰۰۶/۷/۲۷'); // Different from Arabic digits + result = removeDirectionMarkers(fa.format(date)); + assertEquals('Farsi digits result = ' + result, expected, result); + + // Malayalam + replacer.replace(goog, 'LOCALE', 'ml'); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_ml); + const ml = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); + expected = '27/7/06'; + result = ml.format(date); + assertEquals('Malayalam digits result = ' + result, expected, result); + + // Marathi + replacer.replace(goog, 'LOCALE', 'mr'); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_mr); + const mr = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); + expected = '२७/७/०६'; + result = mr.format(date); + assertEquals('Marathi digits result = ' + result, expected, result); + + // Myanmar + replacer.replace(goog, 'LOCALE', 'my'); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_my); + const my = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); + result = my.format(date); + expected = '၂၇-၀၇-၀၆'; + let alternate = '၇/၂၇/၀၆'; // Chrome native gregorian + assertTrue( + 'Myanmar digits = ' + result, + expected === result || alternate === result); + + // Nepali + replacer.replace(goog, 'LOCALE', 'ne'); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_ne); + const ne = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); + result = ne.format(date); + expected = '०६/७/२७'; + alternate = '७/२७/०६'; // Chrome native gregorian + assertTrue( + 'Nepali digits = ' + result, + expected === result || alternate == result); + } + }, + + /** @suppress {checkTypes} suppression added to enable type checking */ + testWebMapsDirectionsCase() { + // Verify that UTC and locale times are formatted for the correct timezone. + + replacer.replace(goog, 'LOCALE', 'en'); + + const utcTime = UtcDateTime.fromIsoString('2010-01-01'); + const localeTime = new Date('2010-01-01'); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + let fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_TIME); + let result = fmt.format(utcTime); + assertEquals('12:00 AM', result); + + // Formatter needs to have its timzone changed. + // Change to non-UTC time + result = fmt.format(localeTime); + assertEquals('4:00 PM', result); + + // Change back to UTC time + result = fmt.format(utcTime); + assertEquals('12:00 AM', result); + + // Another change to non-UTC time + result = fmt.format(localeTime); + assertEquals('4:00 PM', result); + } + }, + + /** @suppress {checkTypes} suppression added to enable type checking */ + testShortDate() { + const date = new Date(2012, 4, 8); + replacer.replace(goog.i18n, 'DateTimeSymbols', DateTimeSymbols_en); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + + const fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE, + DateTimeSymbols); + let result = fmt.format(date); + assertEquals('5/8/12', result); + + const fmt2 = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATETIME, + DateTimeSymbols); + result = fmt2.format(date); + assertEquals('5/8/12, 12:00 AM', result); + } + }, + + testNoLocaleSet() { + const date = new Date(2012, 4, 8); + replacer.replace(goog, 'LOCALE', ''); + for (let nativeMode of testECMAScriptOptions) { + replacer.replace( + LocaleFeature, 'USE_ECMASCRIPT_I18N_DATETIMEF', nativeMode); + const fmt = new DateTimeFormat(DateTimeFormat.Format.SHORT_DATE); + assertTrue(fmt !== null); + const result = fmt.format(date); + assertEquals('5/8/12', result); + } + }, + }); diff --git a/closure/goog/i18n/localefeature.js b/closure/goog/i18n/localefeature.js index 6ed09ed740..ea0ddd432c 100644 --- a/closure/goog/i18n/localefeature.js +++ b/closure/goog/i18n/localefeature.js @@ -101,3 +101,12 @@ exports.USE_ECMASCRIPT_I18N_NUMFORMAT = exports.USE_ECMASCRIPT_I18N_PLURALRULES = (!exports.ECMASCRIPT_INTL_OPT_OUT && goog.FEATURESET_YEAR >= 2021 && exports.ECMASCRIPT_COMMON_LOCALES_2019); + +/** + * @define {boolean} USE_ECMASCRIPT_I18N_DATETIMEF is evaluted to enable + * ECMAScript support for Intl.DateTimeFormat support in + * browsers based on the locale. Browsers that are considered include: + * Chrome, Firefox, Edge, and Safari. + */ +exports.USE_ECMASCRIPT_I18N_DATETIMEF = + (exports.USE_ECMASCRIPT_I18N && !exports.ECMASCRIPT_INTL_OPT_OUT); diff --git a/closure/goog/i18n/nativelocaledigits.js b/closure/goog/i18n/nativelocaledigits.js new file mode 100644 index 0000000000..6364dabbc4 --- /dev/null +++ b/closure/goog/i18n/nativelocaledigits.js @@ -0,0 +1,40 @@ +/** + * @license + * Copyright The Closure Library Authors. + * SPDX-License-Identifier: Apache-2.0 + */ + +// clang-format off + +goog.module('goog.i18n.NativeLocaleDigits'); + +/** + * @fileoverview Provides map of locales to script identifiers + * where locales require specific digits other than ASCII. + */ + +/** + * Type of map from locale string to script codes + * @typedef {!Object} + */ +let LocaleScriptMap; + +/** @typedef {{LocaleScriptMap}} */ +exports.LocaleScriptMap; + +/** + * Native digit codes in ECMAScript Intl objects for locales + * where native digits are prescribed and Intl data is generally available. + * This is designed for classes that create locale-specific + * numbers. Examples include number and date/time formatting. + * @const {!LocaleScriptMap} + */ +exports.FormatWithLocaleDigits = { + 'ar': 'latn', + 'ar-EG': 'arab', + 'bn': 'beng', + 'fa': 'arabext', + 'mr': 'deva', + 'my': 'mymr', + 'ne': 'deva' +}; diff --git a/closure/goog/i18n/numberformat.js b/closure/goog/i18n/numberformat.js index f1ba73f133..6746ae4ad3 100644 --- a/closure/goog/i18n/numberformat.js +++ b/closure/goog/i18n/numberformat.js @@ -18,6 +18,9 @@ goog.provide('goog.i18n.NumberFormat.Format'); goog.require('goog.asserts'); goog.require('goog.i18n.CompactNumberFormatSymbols'); +goog.require('goog.i18n.LocaleFeature'); +goog.require('goog.i18n.NativeLocaleDigits'); + goog.require('goog.i18n.NumberFormatSymbols'); goog.require('goog.i18n.NumberFormatSymbolsType'); goog.require('goog.i18n.NumberFormatSymbols_u_nu_latn'); @@ -26,6 +29,12 @@ goog.require('goog.i18n.currency.CurrencyInfo'); goog.require('goog.math'); goog.require('goog.string'); +goog.scope(function() { + +// For referencing modules +const LocaleFeature = goog.module.get('goog.i18n.LocaleFeature'); +const NativeLocaleDigits = goog.module.get('goog.i18n.NativeLocaleDigits'); + /** * Constructor of NumberFormat. * @param {number|string} pattern The number that indicates a predefined @@ -116,6 +125,7 @@ goog.i18n.NumberFormat = function( * goog.i18n.NumberFormat('#,##,###') should have [3,2] where 2 is the * repeated number group following a fixed number grouping of size 3. * @private {!Array} + * @const */ this.groupingArray_ = []; @@ -135,7 +145,7 @@ goog.i18n.NumberFormat = function( this.baseFormattingNumber_ = null; // Original numeric pattern. Needed because JS modifies this.pattern_*/ - /** @private {number} */ + /** @const @private {number} */ this.inputPattern_ = (typeof pattern === 'number') ? pattern : -1; /** @private {string} */ @@ -202,22 +212,6 @@ goog.i18n.NumberFormat.CompactStyle = { */ goog.i18n.NumberFormat.enforceAsciiDigits_ = false; - -/** - * Native digit codes in EMACScript Intl objects for locales - * where native digits are prescribed and Intl data is - * generally available. - * @private - */ -goog.i18n.NumberFormat.NativeLocaleDigits_ = { - 'ar': 'latn', - 'ar-EG': 'arab', - 'bn': 'beng', - 'fa': 'arabext', - 'mr': 'deva', - 'my': 'mymr', - 'ne': 'deva' -}; /** * Set if the usage of Ascii digits in formatting should be enforced. * NOTE: This function must be called before constructing NumberFormat. @@ -790,11 +784,11 @@ goog.i18n.NumberFormat.prototype.SetUpIntlFormatter_ = function(inputPattern) { if (goog.LOCALE) { locale = goog.LOCALE.replace('_', '-'); } - if (!goog.i18n.NumberFormat.enforceAsciiDigits_ && - locale in goog.i18n.NumberFormat.NativeLocaleDigits_) { + if (locale && !goog.i18n.NumberFormat.enforceAsciiDigits_ && + (locale in NativeLocaleDigits.FormatWithLocaleDigits)) { // Sets native digits for same locales as polyfill. options.numberingSystem = - goog.i18n.NumberFormat.NativeLocaleDigits_[locale]; + NativeLocaleDigits.FormatWithLocaleDigits[locale]; } // This works with undefined locale or empty string. this.intlFormatter_ = new Intl.NumberFormat(locale, options); @@ -1467,6 +1461,7 @@ goog.i18n.NumberFormat.prototype.getDigit_ = function(ch) { * A zero digit character. * @type {string} * @private + * @const */ goog.i18n.NumberFormat.PATTERN_ZERO_DIGIT_ = '0'; @@ -1475,6 +1470,7 @@ goog.i18n.NumberFormat.PATTERN_ZERO_DIGIT_ = '0'; * A grouping separator character. * @type {string} * @private + * @const */ goog.i18n.NumberFormat.PATTERN_GROUPING_SEPARATOR_ = ','; @@ -1483,6 +1479,7 @@ goog.i18n.NumberFormat.PATTERN_GROUPING_SEPARATOR_ = ','; * A decimal separator character. * @type {string} * @private + * @const */ goog.i18n.NumberFormat.PATTERN_DECIMAL_SEPARATOR_ = '.'; @@ -1491,6 +1488,7 @@ goog.i18n.NumberFormat.PATTERN_DECIMAL_SEPARATOR_ = '.'; * A per mille character. * @type {string} * @private + * @const */ goog.i18n.NumberFormat.PATTERN_PER_MILLE_ = '\u2030'; @@ -1499,6 +1497,7 @@ goog.i18n.NumberFormat.PATTERN_PER_MILLE_ = '\u2030'; * A percent character. * @type {string} * @private + * @const */ goog.i18n.NumberFormat.PATTERN_PERCENT_ = '%'; @@ -1507,6 +1506,7 @@ goog.i18n.NumberFormat.PATTERN_PERCENT_ = '%'; * A digit character. * @type {string} * @private + * @const */ goog.i18n.NumberFormat.PATTERN_DIGIT_ = '#'; @@ -1515,6 +1515,7 @@ goog.i18n.NumberFormat.PATTERN_DIGIT_ = '#'; * A separator character. * @type {string} * @private + * @const */ goog.i18n.NumberFormat.PATTERN_SEPARATOR_ = ';'; @@ -1523,6 +1524,7 @@ goog.i18n.NumberFormat.PATTERN_SEPARATOR_ = ';'; * An exponent character. * @type {string} * @private + * @const */ goog.i18n.NumberFormat.PATTERN_EXPONENT_ = 'E'; @@ -1531,6 +1533,7 @@ goog.i18n.NumberFormat.PATTERN_EXPONENT_ = 'E'; * A plus character. * @type {string} * @private + * @const */ goog.i18n.NumberFormat.PATTERN_PLUS_ = '+'; @@ -1539,6 +1542,7 @@ goog.i18n.NumberFormat.PATTERN_PLUS_ = '+'; * A generic currency sign character. * @type {string} * @private + * @const */ goog.i18n.NumberFormat.PATTERN_CURRENCY_SIGN_ = '\u00A4'; @@ -1547,6 +1551,7 @@ goog.i18n.NumberFormat.PATTERN_CURRENCY_SIGN_ = '\u00A4'; * A quote character. * @type {string} * @private + * @const */ goog.i18n.NumberFormat.QUOTE_ = '\''; @@ -1833,6 +1838,7 @@ goog.i18n.NumberFormat.FormattedPart; /** * The empty unit, corresponding to a base of 0. * @private {!goog.i18n.NumberFormat.CompactNumberUnit} + * @const */ goog.i18n.NumberFormat.NULL_UNIT_ = { divisorBase: 0, @@ -2138,3 +2144,4 @@ goog.i18n.NumberFormat.prototype.isCurrencyCodeBeforeValue = function() { // If not, we have bigger problems than this. return posCurrSymbol < posCurrValue; }; +}); // End of scope for module data