google-closure-library
Version:
Google's common JavaScript library
1,343 lines (1,229 loc) • 48.9 kB
JavaScript
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Functions for dealing with date/time formatting.
*/
/**
* Namespace for i18n date/time formatting functions
*/
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.DayPeriods');
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 DayPeriods = goog.module.get('goog.i18n.DayPeriods');
const LocaleFeature = goog.module.get('goog.i18n.LocaleFeature');
const NativeLocaleDigits = goog.module.get('goog.i18n.NativeLocaleDigits');
/**
* 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
* https://unicode-org.github.io/icu/userguide/format_parse/datetime/#date-field-symbol-table}
* <pre>
* Symbol Meaning Presentation Example
* ------ ------- ------------ -------
* G# era designator (Text) AD
* y# year (Number) 1996
* Y year (week of year) (Number) 1997
* u* extended year (Number) 4601
* Q# quarter (Text) Q3 & 3rd quarter
* M month in year (Text & Number) July & 07
* L month in year (standalone) (Text & Number) July & 07
* d day in month (Number) 10
* h hour in am/pm (1~12) (Number) 12
* H hour in day (0~23) (Number) 0
* m minute in hour (Number) 30
* s second in minute (Number) 55
* S fractional second (Number) 978
* E# day of week (Text) Tue & Tuesday
* e* day of week (local 1~7) (Number) 2
* c# day of week (standalone) (Text & Number) 2 & Tues & Tuesday & T
* D* day in year (Number) 189
* F* day of week in month (Number) 2 (2nd Wed in July)
* w week in year (Number) 27
* W* week in month (Number) 2
* a am/pm marker (Text) PM
* b am/pm/noon/midnight (Text) Noon
* B flexible day periods (Text) de l’après-midi'
* k hour in day (1~24) (Number) 24
* K hour in am/pm (0~11) (Number) 0
* z time zone (Text) Pacific Standard Time
* Z# time zone (RFC 822) (Number) -0800
* v# time zone (generic) (Text) America/Los_Angeles
* V# time zone (Text) Los Angeles Time
* g* Julian day (Number) 2451334
* A* milliseconds in day (Number) 69540000
* ' escape for text (Delimiter) 'Date='
* '' single quote (Literal) 'o''clock'
*
* Item marked with '*' are not supported yet.
* Item marked with '#' works different than java
*
* The count of pattern letters determine the format.
* (Text): 4 or more, use full form, <4, use short or abbreviated form if it
* exists. (e.g., "EEEE" produces "Monday", "EEE" produces "Mon")
*
* (Number): the minimum number of digits. Shorter numbers are zero-padded to
* this amount (e.g. if "m" produces "6", "mm" produces "06"). Year is handled
* specially; that is, if the count of 'y' is 2, the Year will be truncated to
* 2 digits. (e.g., if "yyyy" produces "1997", "yy" produces "97".) Unlike other
* fields, fractional seconds are padded on the right with zero.
*
* :(Text & Number) 3 or over, use text, otherwise use number. (e.g., "M"
* produces "1", "MM" produces "01", "MMM" produces "Jan", and "MMMM" produces
* "January".)
*
* Any characters in the pattern that are not in the ranges of ['a'..'z'] and
* ['A'..'Z'] will be treated as quoted text. For instance, characters like ':',
* '.', ' ', '#' and '@' will appear in the resulting time text even they are
* not embraced within single quotes.
* </pre>
*/
/**
* Construct a DateTimeFormat object based on current locale.
* @constructor
* @param {string|number} pattern pattern specification or pattern type.
* @param {!Object=} opt_dateTimeSymbols Optional symbols to use for this
* instance rather than the global symbols.
* You can use some of the predefined SHORT / MEDIUM / LONG / FULL patterns,
* or the common patterns defined in goog.i18n.DateTimePatterns.
* Examples:
* <code><pre>
* let fmt = new goog.i18n.DateTimeFormat(
* goog.i18n.DateTimeFormat.Format.FULL_DATE);
* let fmt = new goog.i18n.DateTimeFormat(
* goog.i18n.DateTimePatterns.MONTH_DAY_YEAR_MEDIUM);
* </pre></code>
*
* {@see goog.i18n.DateTimeFormat.Format}
* {@see goog.i18n.DateTimePatterns}
* @final
*/
goog.i18n.DateTimeFormat = function(pattern, opt_dateTimeSymbols) {
'use strict';
goog.asserts.assert(pattern !== undefined, 'Pattern must be defined');
goog.asserts.assert(
opt_dateTimeSymbols !== undefined ||
goog.i18n.DateTimeSymbols !== undefined,
'goog.i18n.DateTimeSymbols or explicit symbols must be defined');
/**
* Remember if the implementation is ECMAScript
* @type {?goog.global.Intl.DateTimeFormat}
* @private
*/
this.intlFormatter_ = null;
/**
* Remember the pattern applied for resetting Intl formatter.
* @type {number|string}
* @private @constant
*/
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 {
/**
* 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);
}
}
};
/**
* Enum to identify predefined Date/Time format pattern. The format pattern to
* output mapping can be found at go/closure-localization#fconst.
* @enum {number}
*/
goog.i18n.DateTimeFormat.Format = {
FULL_DATE: 0,
LONG_DATE: 1,
MEDIUM_DATE: 2,
SHORT_DATE: 3,
FULL_TIME: 4,
LONG_TIME: 5,
MEDIUM_TIME: 6,
SHORT_TIME: 7,
FULL_DATETIME: 8,
LONG_DATETIME: 9,
MEDIUM_DATETIME: 10,
SHORT_DATETIME: 11,
WEEKDAY_MONTH_DAY_FULL: 12 // From FULL_DATE by removing year pattern
};
/**
* regular expression pattern for parsing pattern string
* @type {!Array<!RegExp>}
* @private @const
*/
goog.i18n.DateTimeFormat.TOKENS_ = [
// quote string
/^\'(?:[^\']|\'\')*(\'|$)/,
// pattern chars
/^(?:G+|y+|Y+|M+|k+|S+|E+|a+|b+|B+|h+|K+|H+|c+|L+|Q+|d+|m+|s+|v+|V+|w+|z+|Z+)/,
/^[^\'GyYMkSEabBhKHcLQdmsvVwzZ]+/ // and all the other chars
];
/**
* These are token types, corresponding to above token definitions.
* @enum {number}
* @private
*/
goog.i18n.DateTimeFormat.PartTypes_ = {
QUOTED_STRING: 0,
FIELD: 1,
LITERAL: 2
};
/**
* @param {!goog.date.DateLike} date
* @return {number}
* @private
*/
goog.i18n.DateTimeFormat.getHours_ = function(date) {
'use strict';
return /** @type {?} */ (date).getHours ? /** @type {?} */ (date).getHours() :
0;
};
/**
* @param {!goog.date.DateLike} date
* @return {number}
* @private
*/
goog.i18n.DateTimeFormat.getMinutes_ = function(date) {
'use strict';
return /** @type {?} */ (date).getMinutes ?
/** @type {?} */ (date).getMinutes() :
0;
};
/**
* Apply specified pattern to this formatter object.
* @param {string} pattern String specifying how the date should be formatted.
* @private
*/
goog.i18n.DateTimeFormat.prototype.applyPattern_ = function(pattern) {
'use strict';
if (goog.i18n.DateTimeFormat.removeRlmInPatterns_) {
// Remove RLM unicode control character from pattern.
pattern = pattern.replace(/\u200f/g, '');
}
// lex the pattern, once for all uses
while (pattern) {
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) {
let part = m[0];
pattern = pattern.substring(part.length);
if (i == goog.i18n.DateTimeFormat.PartTypes_.QUOTED_STRING) {
if (part == '\'\'') {
part = '\''; // '' -> '
} else {
part = part.substring(
1,
m[1] == '\'' ? part.length - 1 : part.length); // strip quotes
part = part.replace(/\'\'/g, '\'');
}
}
this.patternParts_.push({text: part, type: i});
break;
}
}
if (previousPattern === pattern) {
// On every iteration, part of the pattern string must be consumed.
throw new Error('Malformed pattern part: ' + pattern);
}
}
};
/**
* Format the given date object according to preset pattern and current locale.
* @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.
* @return {string} Formatted string for the given date.
* Throws an error if the date is null or if one tries to format a date-only
* object (for instance goog.date.Date) using a pattern with time fields.
*/
goog.i18n.DateTimeFormat.prototype.format = function(date, opt_timeZone) {
'use strict';
if (!date) throw new Error('The date to format must be non-null.');
// We don't want to write code to calculate each date field because we
// want to maximize performance and minimize code size.
// JavaScript only provide API to render local time.
// Suppose target date is: 16:00 GMT-0400
// OS local time is: 12:00 GMT-0800
// We want to create a Local Date Object : 16:00 GMT-0800, and fix the
// time zone display ourselves.
// Thing get a little bit tricky when daylight time transition happens. For
// example, suppose OS timeZone is America/Los_Angeles, it is impossible to
// represent "2006/4/2 02:30" even for those timeZone that has no transition
// at this time. Because 2:00 to 3:00 on that day does not exist in
// America/Los_Angeles time zone. To avoid calculating date field through
// 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.
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 {
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;
return this.intlFormatter_.format(realdate);
} 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('');
}
};
/**
* 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;
case goog.i18n.DateTimeFormat.Format.WEEKDAY_MONTH_DAY_FULL:
options.weekday = 'long';
options.month = 'long';
options.day = 'numeric';
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
* locale specific repository.
* @param {number} formatType A number that identified the predefined pattern.
* @private
*/
goog.i18n.DateTimeFormat.prototype.applyStandardPattern_ = function(
formatType) {
'use strict';
let pattern;
if (formatType < 4) {
pattern = this.dateTimeSymbols_.DATEFORMATS[formatType];
} else if (formatType < 8) {
pattern = this.dateTimeSymbols_.TIMEFORMATS[formatType - 4];
} else if (formatType < 12) {
pattern = this.dateTimeSymbols_.DATETIMEFORMATS[formatType - 8];
pattern = pattern.replace(
'{1}', this.dateTimeSymbols_.DATEFORMATS[formatType - 8]);
pattern = pattern.replace(
'{0}', this.dateTimeSymbols_.TIMEFORMATS[formatType - 8]);
} else if (
formatType === goog.i18n.DateTimeFormat.Format.WEEKDAY_MONTH_DAY_FULL) {
// WEEKDAY_MONTH_DAY_FULL is derived from FULL_DATE removing year patterns
pattern =
this.removeYearFormatFromPattern_(this.dateTimeSymbols_.DATEFORMATS[0]);
} else {
// Default
this.applyStandardPattern_(goog.i18n.DateTimeFormat.Format.MEDIUM_DATETIME);
return;
}
this.applyPattern_(pattern);
};
/**
* Localizes a string potentially containing numbers, replacing ASCII digits
* with native digits if specified so by the locale. Leaves other characters.
* @param {string} input the string to be localized, using ASCII digits.
* @return {string} localized string, potentially using native digits.
* @private
*/
goog.i18n.DateTimeFormat.prototype.localizeNumbers_ = function(input) {
'use strict';
return goog.i18n.DateTimeFormat.localizeNumbers(input, this.dateTimeSymbols_);
};
/**
* If the usage of Ascii digits should be enforced regardless of locale.
* @type {boolean}
* @private
*/
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`.
* @type {boolean}
* @private
*/
goog.i18n.DateTimeFormat.removeRlmInPatterns_ = false;
/**
* Sets if the usage of Ascii digits in formatting should be enforced in
* formatted date/time even for locales where native digits are indicated.
* Also sets whether to remove RLM unicode control characters when using
* standard enumerated patterns (they exist e.g. in standard d/M/y for Arabic).
* Production code should call this once before any `DateTimeFormat`
* object is instantiated.
* Caveats:
* * Enforcing ASCII digits affects all future formatting by new or existing
* `DateTimeFormat` objects.
* * Removal of RLM characters only applies to `DateTimeFormat` objects
* instantiated after this call.
* @param {boolean} enforceAsciiDigits Whether Ascii digits should be enforced.
*/
goog.i18n.DateTimeFormat.setEnforceAsciiDigits = function(enforceAsciiDigits) {
'use strict';
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
// optional argument here or to the `DateTimeFormat` constructor to
// enable an alternative behavior.
goog.i18n.DateTimeFormat.removeRlmInPatterns_ = enforceAsciiDigits;
};
/**
* @return {boolean} Whether enforcing ASCII digits for all locales. See
* `#setEnforceAsciiDigits` for more details.
*/
goog.i18n.DateTimeFormat.isEnforceAsciiDigits = function() {
'use strict';
return goog.i18n.DateTimeFormat.enforceAsciiDigits_;
};
/**
* Localizes a string potentially containing numbers, replacing ASCII digits
* with native digits if specified so by the locale. Leaves other characters.
* @param {number|string} input the string to be localized, using ASCII digits.
* @param {!Object=} opt_dateTimeSymbols Optional symbols to use rather than
* the global symbols.
* @return {string} localized string, potentially using native digits.
* @suppress {strictMissingProperties} Part of the go/strict_warnings_migration
*/
goog.i18n.DateTimeFormat.localizeNumbers = function(
input, opt_dateTimeSymbols) {
'use strict';
input = String(input);
const dateTimeSymbols = opt_dateTimeSymbols || goog.i18n.DateTimeSymbols;
if (dateTimeSymbols.ZERODIGIT === undefined ||
goog.i18n.DateTimeFormat.enforceAsciiDigits_) {
return input;
}
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) :
input.charAt(i));
}
return parts.join('');
};
/**
* Formats Era field according to pattern specified.
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatEra_ = function(count, date) {
'use strict';
const value = date.getFullYear() > 0 ? 1 : 0;
return count >= 4 ? this.dateTimeSymbols_.ERANAMES[value] :
this.dateTimeSymbols_.ERAS[value];
};
/**
* Formats Year field according to pattern specified
* JavaScript Date object seems incapable handling 1BC and
* year before. It can show you year 0 which does not exists.
* following we just keep consistent with javascript's
* toString method. But keep in mind those things should be
* unsupported.
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatYear_ = function(count, date) {
'use strict';
let value = date.getFullYear();
if (value < 0) {
value = -value;
}
if (count == 2) {
// See comment about special casing 'yy' at the start of the file, this
// matches ICU and CLDR behaviour. See also:
// http://icu-project.org/apiref/icu4j/com/ibm/icu/text/SimpleDateFormat.html
// http://www.unicode.org/reports/tr35/tr35-dates.html
value = value % 100;
}
return this.localizeNumbers_(goog.string.padNumber(value, count));
};
/**
* Formats Year (Week of Year) field according to pattern specified
* JavaScript Date object seems incapable handling 1BC and
* year before. It can show you year 0 which does not exists.
* following we just keep consistent with javascript's
* toString method. But keep in mind those things should be
* unsupported.
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatYearOfWeek_ = function(count, date) {
'use strict';
let value = goog.date.getYearOfWeek(
date.getFullYear(), date.getMonth(), date.getDate(),
this.dateTimeSymbols_.FIRSTWEEKCUTOFFDAY,
this.dateTimeSymbols_.FIRSTDAYOFWEEK);
if (value < 0) {
value = -value;
}
if (count == 2) {
// See comment about special casing 'yy' at the start of the file, this
// matches ICU and CLDR behaviour. See also:
// http://icu-project.org/apiref/icu4j/com/ibm/icu/text/SimpleDateFormat.html
// http://www.unicode.org/reports/tr35/tr35-dates.html
value = value % 100;
}
return this.localizeNumbers_(goog.string.padNumber(value, count));
};
/**
* Formats Month field according to pattern specified
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatMonth_ = function(count, date) {
'use strict';
const value = date.getMonth();
switch (count) {
case 5:
return this.dateTimeSymbols_.NARROWMONTHS[value];
case 4:
return this.dateTimeSymbols_.MONTHS[value];
case 3:
return this.dateTimeSymbols_.SHORTMONTHS[value];
default:
return this.localizeNumbers_(goog.string.padNumber(value + 1, count));
}
};
/**
* Validates is the goog.date.DateLike object to format has a time.
* DateLike means Date|goog.date.Date, and goog.date.DateTime inherits
* from goog.date.Date. But goog.date.Date does not have time related
* members (getHours, getMinutes, getSeconds).
* Formatting can be done, if there are no time placeholders in the pattern.
*
* @param {!goog.date.DateLike} date the object to validate.
* @private
*/
goog.i18n.DateTimeFormat.validateDateHasTime_ = function(date) {
'use strict';
let maybeHasTime = /** @type {?} */ (date);
if (maybeHasTime.getHours && maybeHasTime.getSeconds &&
maybeHasTime.getMinutes) {
return;
}
// if (date instanceof Date || date instanceof goog.date.DateTime)
throw new Error(
'The date to format has no time (probably a goog.date.Date). ' +
'Use Date or goog.date.DateTime, or use a pattern without time fields.');
};
/**
* Formats (1..24) Hours field according to pattern specified
*
* @param {number} count Number of time pattern char repeats. This controls
* how a field should be formatted.
* @param {!goog.date.DateLike} date It holds the date object to be formatted.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.format24Hours_ = function(count, date) {
'use strict';
goog.i18n.DateTimeFormat.validateDateHasTime_(date);
const hours = goog.i18n.DateTimeFormat.getHours_(date) || 24;
return this.localizeNumbers_(goog.string.padNumber(hours, count));
};
/**
* Formats Fractional seconds field according to pattern
* specified
*
* @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.
*
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatFractionalSeconds_ = function(
count, date) {
'use strict';
// Fractional seconds left-justify, append 0 for precision beyond 3
const value =
/** @type {!Date|!goog.date.DateTime} */ (date).getMilliseconds() / 1000;
return this.localizeNumbers_(
value.toFixed(Math.min(3, count)).slice(2) +
(count > 3 ? goog.string.padNumber(0, count - 3) : ''));
};
/**
* Formats Day of week field according to pattern specified
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatDayOfWeek_ = function(count, date) {
'use strict';
const value = date.getDay();
return count >= 4 ? this.dateTimeSymbols_.WEEKDAYS[value] :
this.dateTimeSymbols_.SHORTWEEKDAYS[value];
};
/**
* Formats Am/Pm field according to pattern specified
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatAmPm_ = function(count, date) {
'use strict';
goog.i18n.DateTimeFormat.validateDateHasTime_(date);
const hours = goog.i18n.DateTimeFormat.getHours_(date);
// Must implement this with fallback if no data is found.
return this.dateTimeSymbols_.AMPMS[hours >= 12 && hours < 24 ? 1 : 0];
};
/**
* Formats am/pm/noon/midnight field according to pattern specified with 'b'
* Handle noon and midnight if dayPeriod has data.
* Otherwise, fallback to AM/PM for the locale.
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatAmPmNoonMidnight_ = function(
count, date) {
'use strict';
goog.i18n.DateTimeFormat.validateDateHasTime_(date);
const hours = goog.i18n.DateTimeFormat.getHours_(date);
const minutes = goog.i18n.DateTimeFormat.getMinutes_(date);
/** {?goog.i18n.DayPeriods} */
const dayPeriods = goog.i18n.DayPeriods.getDayPeriods();
if (dayPeriods && minutes === 0) {
// Check for noon & midnight data.
if (dayPeriods.midnight && hours == 0) {
return dayPeriods.midnight.formatNames[0];
} else if (dayPeriods.noon && hours === 12) {
return dayPeriods.noon.formatNames[0];
}
}
// Must implement this with fallback if no data is found.
return this.dateTimeSymbols_.AMPMS[hours >= 12 && hours < 24 ? 1 : 0];
};
/**
* Formats flexible day periods according to pattern specified with 'B'.
* Return string for flexible day period when data is available.
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatFlexibleDayPeriods_ = function(
count, date) {
'use strict';
goog.i18n.DateTimeFormat.validateDateHasTime_(date);
const hours = goog.i18n.DateTimeFormat.getHours_(date);
const minutes = goog.i18n.DateTimeFormat.getMinutes_(date);
// String in HH:MM format for comparing.
const fmtTime = hours.toString(10).padStart(2, '0') + ':' +
minutes.toString().padStart(2, '0');
let period;
/** {?goog.i18n.DayPeriods} */
const dayPeriods = goog.i18n.DayPeriods.getDayPeriods();
if (dayPeriods) {
// Match time to ranges in DayPeriods to give the particular range.
const keys = Object.keys(dayPeriods);
for (let index = 0; index < keys.length; index++) {
let testPeriod = dayPeriods[keys[index]];
if (fmtTime === testPeriod.at) {
period = keys[index]; // A particular time.
break;
}
// Check if the period straddles midnight
if (testPeriod.before > testPeriod.from) {
if (fmtTime >= testPeriod.from && fmtTime < testPeriod.before) {
period = keys[index]; // A particular time.
}
} else {
// Check before and after 00:00.
// Two tests needed
if (fmtTime >= testPeriod.from && fmtTime < '24:00' ||
fmtTime >= '00:00' && fmtTime < testPeriod.before) {
period = keys[index]; // A particular time.
break;
}
}
}
if (period) {
// Get string for the period
return dayPeriods[period].formatNames[0]; // Pick first style
}
}
// Fall back to 'a' when no data is defined.
return this.dateTimeSymbols_.AMPMS[hours >= 12 && hours < 24 ? 1 : 0];
};
/**
* Formats (1..12) Hours field according to pattern specified
*
* @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.
* @return {string} formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.format1To12Hours_ = function(count, date) {
'use strict';
goog.i18n.DateTimeFormat.validateDateHasTime_(date);
const hours = goog.i18n.DateTimeFormat.getHours_(date) % 12 || 12;
return this.localizeNumbers_(goog.string.padNumber(hours, count));
};
/**
* Formats (0..11) Hours field according to pattern specified
*
* @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.
* @return {string} formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.format0To11Hours_ = function(count, date) {
'use strict';
goog.i18n.DateTimeFormat.validateDateHasTime_(date);
const hours = goog.i18n.DateTimeFormat.getHours_(date) % 12;
return this.localizeNumbers_(goog.string.padNumber(hours, count));
};
/**
* Formats (0..23) Hours field according to pattern specified
*
* @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.
* @return {string} formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.format0To23Hours_ = function(count, date) {
'use strict';
goog.i18n.DateTimeFormat.validateDateHasTime_(date);
const hours = goog.i18n.DateTimeFormat.getHours_(date);
return this.localizeNumbers_(goog.string.padNumber(hours, count));
};
/**
* Formats Standalone weekday field according to pattern specified
*
* @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.
* @return {string} formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatStandaloneDay_ = function(
count, date) {
'use strict';
const value = date.getDay();
switch (count) {
case 5:
return this.dateTimeSymbols_.STANDALONENARROWWEEKDAYS[value];
case 4:
return this.dateTimeSymbols_.STANDALONEWEEKDAYS[value];
case 3:
return this.dateTimeSymbols_.STANDALONESHORTWEEKDAYS[value];
default:
return this.localizeNumbers_(goog.string.padNumber(value, 1));
}
};
/**
* Formats Standalone Month field according to pattern specified
*
* @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.
* @return {string} formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatStandaloneMonth_ = function(
count, date) {
'use strict';
const value = date.getMonth();
switch (count) {
case 5:
return this.dateTimeSymbols_.STANDALONENARROWMONTHS[value];
case 4:
return this.dateTimeSymbols_.STANDALONEMONTHS[value];
case 3:
return this.dateTimeSymbols_.STANDALONESHORTMONTHS[value];
default:
return this.localizeNumbers_(goog.string.padNumber(value + 1, count));
}
};
/**
* Formats Quarter field according to pattern specified
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatQuarter_ = function(count, date) {
'use strict';
const value = Math.floor(date.getMonth() / 3);
return count < 4 ? this.dateTimeSymbols_.SHORTQUARTERS[value] :
this.dateTimeSymbols_.QUARTERS[value];
};
/**
* Formats Date field according to pattern specified
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatDate_ = function(count, date) {
'use strict';
return this.localizeNumbers_(goog.string.padNumber(date.getDate(), count));
};
/**
* Formats Minutes field according to pattern specified
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatMinutes_ = function(count, date) {
'use strict';
goog.i18n.DateTimeFormat.validateDateHasTime_(date);
return this.localizeNumbers_(
goog.string.padNumber(goog.i18n.DateTimeFormat.getMinutes_(date), count));
};
/**
* Formats Seconds field according to pattern specified
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatSeconds_ = function(count, date) {
'use strict';
goog.i18n.DateTimeFormat.validateDateHasTime_(date);
return this.localizeNumbers_(goog.string.padNumber(
/** @type {!goog.date.DateTime} */ (date).getSeconds(), count));
};
/**
* Formats the week of year field according to pattern specified
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatWeekOfYear_ = function(count, date) {
'use strict';
const weekNum = goog.date.getWeekNumber(
date.getFullYear(), date.getMonth(), date.getDate(),
this.dateTimeSymbols_.FIRSTWEEKCUTOFFDAY,
this.dateTimeSymbols_.FIRSTDAYOFWEEK);
return this.localizeNumbers_(goog.string.padNumber(weekNum, count));
};
/**
* Formats TimeZone field following RFC
*
* @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.
* @return {string} Formatted string that represent this field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatTimeZoneRFC_ = function(
count, date, opt_timeZone) {
'use strict';
opt_timeZone = opt_timeZone ||
goog.i18n.TimeZone.createTimeZone(date.getTimezoneOffset());
// RFC 822 formats should be kept in ASCII, but localized GMT formats may need
// to use native digits.
return count < 4 ? opt_timeZone.getRFCTimeZoneString(date) :
this.localizeNumbers_(opt_timeZone.getGMTString(date));
};
/**
* Generate GMT timeZone string for given date
* @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.
* @return {string} GMT timeZone string.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatTimeZone_ = function(
count, date, opt_timeZone) {
'use strict';
opt_timeZone = opt_timeZone ||
goog.i18n.TimeZone.createTimeZone(date.getTimezoneOffset());
return count < 4 ? opt_timeZone.getShortName(date) :
opt_timeZone.getLongName(date);
};
/**
* 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.
* @return {string} GMT timeZone string.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatTimeZoneId_ = function(
date, opt_timeZone) {
'use strict';
opt_timeZone = opt_timeZone ||
goog.i18n.TimeZone.createTimeZone(date.getTimezoneOffset());
return opt_timeZone.getTimeZoneId();
};
/**
* Generate localized, location dependent time zone id
* @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.
* @return {string} GMT timeZone string.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatTimeZoneLocationId_ = function(
count, date, opt_timeZone) {
'use strict';
opt_timeZone = opt_timeZone ||
goog.i18n.TimeZone.createTimeZone(date.getTimezoneOffset());
return count <= 2 ? opt_timeZone.getTimeZoneId() :
opt_timeZone.getGenericLocation(date);
};
/**
* Formatting one date field.
* @param {string} patternStr The pattern string for the field being formatted.
* @param {!goog.date.DateLike} date represents the real date to be formatted.
* @param {!goog.date.DateLike} dateForDate used to resolve date fields
* 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.
* @return {string} string representation for the given field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.formatField_ = function(
patternStr, date, dateForDate, dateForTime, opt_timeZone) {
'use strict';
const count = patternStr.length;
/** {?goog.i18n.DayPeriods} */
const dayPeriods = goog.i18n.DayPeriods.getDayPeriods();
switch (patternStr.charAt(0)) {
case 'G':
return this.formatEra_(count, dateForDate);
case 'y':
return this.formatYear_(count, dateForDate);
case 'Y':
return this.formatYearOfWeek_(count, dateForDate);
case 'M':
return this.formatMonth_(count, dateForDate);
case 'k':
return this.format24Hours_(count, dateForTime);
case 'S':
return this.formatFractionalSeconds_(count, dateForTime);
case 'E':
return this.formatDayOfWeek_(count, dateForDate);
case 'a':
return this.formatAmPm_(count, dateForTime);
case 'b':
if (dayPeriods) {
return this.formatAmPmNoonMidnight_(count, dateForTime);
} else {
return this.formatAmPm_(count, dateForTime);
}
case 'B':
if (dayPeriods) {
return this.formatFlexibleDayPeriods_(count, dateForTime);
} else {
return this.formatAmPm_(count, dateForTime);
}
case 'h':
return this.format1To12Hours_(count, dateForTime);
case 'K':
return this.format0To11Hours_(count, dateForTime);
case 'H':
return this.format0To23Hours_(count, dateForTime);
case 'c':
return this.formatStandaloneDay_(count, dateForDate);
case 'L':
return this.formatStandaloneMonth_(count, dateForDate);
case 'Q':
return this.formatQuarter_(count, dateForDate);
case 'd':
return this.formatDate_(count, dateForDate);
case 'm':
return this.formatMinutes_(count, dateForTime);
case 's':
return this.formatSeconds_(count, dateForTime);
case 'v':
return this.formatTimeZoneId_(date, opt_timeZone);
case 'V':
return this.formatTimeZoneLocationId_(count, date, opt_timeZone);
case 'w':
return this.formatWeekOfYear_(count, dateForTime);
case 'z':
return this.formatTimeZone_(count, date, opt_timeZone);
case 'Z':
return this.formatTimeZoneRFC_(count, date, opt_timeZone);
default:
return '';
}
};
/**
* Removes year formatting from full date pattern for implementing
* special case for WEEKDAY_MONTH_DAY_FULL, deriving from
* FULL date pattern.
* @param {string} patternStr The pattern string for the field being formatted.
* @return {string} Modified pattern string without year field.
* @private
*/
goog.i18n.DateTimeFormat.prototype.removeYearFormatFromPattern_ = function(
patternStr) {
// Remove all around the year except E*, d*, M*, e.g., space, punctuation
const yearPattern = /[^EMd]*yy*[^EMd]*/;
return patternStr.replace(yearPattern, '');
};
}); // End of scope for module data