closure-builder
Version:
Simple Closure, Soy and JavaScript Build system
326 lines (296 loc) • 11.4 kB
JavaScript
// Copyright 2018 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview RelativeDateTimeFormat provides methods to format simple
* relative dates and times into a string in a user friendly way and a locale
* sensitive manner. Numeric quantities are supported with negative values
* indicating the past, zero meaning now, and positive for the future. Specific
* named times such as "tomorrow" are returned and correctly pluralized results
* are given for relative times without specific names such as "in 5 days" or "3
* weeks ago". The result is localized according to current locale value.
*
* Similar to the ICU4J class com/ibm/icu/text/RelativeDateTimeFormatter:
* http://icu-project.org/apiref/icu4j/com/ibm/icu/text/RelativeDateTimeFormatter.html
*/
goog.module('goog.i18n.RelativeDateTimeFormat');
var MessageFormat = goog.require('goog.i18n.MessageFormat');
var asserts = goog.require('goog.asserts');
var relativeDateTimeSymbols = goog.require('goog.i18n.relativeDateTimeSymbols');
/**
* @constructor
* @param {!RelativeDateTimeFormat.NumericOption=} opt_numeric
* This optional string determines if formatted output is always
* the numeric formatting rather than available relative strings.
* ALWAYS (default) forces numeric results in all uses of this instance.
* AUTO mode uses available relative strings such as "tomorrow", falling
* back to numeric.
* @param {!RelativeDateTimeFormat.Style=} opt_style
* This optional value determines the style of the relative time output.
* Values include LONG, SHORT, NARROW. Default is LONG.
* as part of the resulting formatted string
* @param {!relativeDateTimeSymbols.RelativeDateTimeSymbols=}
* opt_relativeDateTimeSymbols This optional value can be used to set the
* data for to use for this instance rather than obtaining from
* relativedatetimesymbols.
* @final
*/
var RelativeDateTimeFormat = function(
opt_numeric, opt_style, opt_relativeDateTimeSymbols) {
asserts.assert(
goog.isDef(opt_relativeDateTimeSymbols) ||
goog.isDef(relativeDateTimeSymbols.getRelativeDateTimeSymbols()),
'goog.i18n.RelativeDateTimeSymbols or explicit symbols must be defined');
/**
* Flag to force numeric mode in all cases. Normally true.
* @private @type {boolean}
*/
this.alwaysNumeric_ = true;
if (opt_numeric) {
asserts.assert(
opt_numeric == RelativeDateTimeFormat.NumericOption.ALWAYS ||
opt_numeric == RelativeDateTimeFormat.NumericOption.AUTO,
'Invalid opt_numeric value');
if (opt_numeric == RelativeDateTimeFormat.NumericOption.ALWAYS) {
this.alwaysNumeric_ = true;
} else if (opt_numeric == RelativeDateTimeFormat.NumericOption.AUTO) {
this.alwaysNumeric_ = false;
}
}
/** @private @type {!RelativeDateTimeFormat.Style} */
this.style_ = RelativeDateTimeFormat.Style.LONG;
if (opt_style) {
asserts.assert(
opt_style >= RelativeDateTimeFormat.Style.LONG &&
opt_style <= RelativeDateTimeFormat.Style.NARROW,
'Style must be LONG, SHORT, or NARROW');
this.style_ = opt_style;
}
/**
* RelativeDateTimeSymbols object for locale data required by the formatter.
* @private @const {!relativeDateTimeSymbols.RelativeDateTimeSymbols}
*/
this.rdtfSymbols_ = opt_relativeDateTimeSymbols ||
relativeDateTimeSymbols.getRelativeDateTimeSymbols();
};
/**
* Values for setting the numeric mode in the constructor.
* @enum {string}
*/
RelativeDateTimeFormat.NumericOption = {
ALWAYS: 'always',
AUTO: 'auto',
};
/**
* Collection of public style symbols.
* @enum {number}
*/
RelativeDateTimeFormat.Style = {
LONG: 0,
SHORT: 1,
NARROW: 2
};
/**
* Relative unit constants for public use.
* @enum {number}
*/
RelativeDateTimeFormat.Unit = {
YEAR: 0,
QUARTER: 1,
MONTH: 2,
WEEK: 3,
DAY: 4,
HOUR: 5,
MINUTE: 6,
SECOND: 7
};
/**
* Formats a string with the amount and relative unit. If data for the quantity
* is not available in the requested style, then it falls back to next style. If
* not available in any style, then it reverts to formatNumeric for the same
* unit.
* @param {number} quantity A desired offset from current time, negative
* for past, 0 for now, positive for future.
* @param {!RelativeDateTimeFormat.Unit} relativeUnit Type such as HOUR, YEAR,
* QUARTER.
* @return {string} The formatted result.
*/
RelativeDateTimeFormat.prototype.format = function(quantity, relativeUnit) {
asserts.assertNumber(quantity, 'Quantity must be a number');
asserts.assert(
relativeUnit >= RelativeDateTimeFormat.Unit.YEAR &&
relativeUnit <= RelativeDateTimeFormat.Unit.SECOND,
'Unit must be one of the supported values');
/**
* Find the right data based on Unit, quantity, and plural.
*/
var rdtfUnitPattern = this.getUnitStylePattern_(relativeUnit);
var dirString = quantity.toString();
/**
* Special cases to force numeric units, in order
* to match ICU4J as described in
* http://unicode.org/cldr/trac/ticket/9165
* http://bugs.icu-project.org/trac/ticket/12171
*/
/**
* TODO(icu/12171): re-examine this if/when ICU4J and CLDR data are
* updated with correct correct relative strings.
*/
var useNumeric = this.alwaysNumeric_;
if ((relativeUnit == RelativeDateTimeFormat.Unit.MINUTE) ||
(relativeUnit == RelativeDateTimeFormat.Unit.HOUR)) {
useNumeric = true;
}
// Check for force numeric and having relative value with the given quantity.
if (!useNumeric && rdtfUnitPattern && rdtfUnitPattern.R &&
rdtfUnitPattern.R[dirString]) {
return rdtfUnitPattern.R[dirString];
} else {
// Direction data doesn't exist. Fallback to format numeric.
return this.formatNumericInternal_(quantity, rdtfUnitPattern);
}
};
/**
* Format with forced numeric value and relative unit.
* @param {number} quantity The number of units.
* Negative zero will use PAST, while unsiged or positive indicates FUTURE.
* @param {!relativeDateTimeSymbols.StyleElement|undefined} unitStylePattern Has
* PAST and FUTURE fields.
* @return {string} The formatted result.
* @private
*/
RelativeDateTimeFormat.prototype.formatNumericInternal_ = function(
quantity, unitStylePattern) {
if (!unitStylePattern) return '';
/**
* Stores the plural formatting string.
* @type {string}
*/
var relTimeString;
var absQuantity = Math.abs(quantity);
// Apply MessageFormat to the unit with FUTURE or PAST quantity, with test for
// signed zero value.
if (quantity > 0 || (quantity == 0 && (1 / quantity) == Infinity)) {
relTimeString = unitStylePattern.F;
} else {
// Negative zero is interpreted as the past.
relTimeString = unitStylePattern.P;
}
/**
* Formatter for the messages requiring units. Plural formatting needed.
* @type {?MessageFormat}
*/
// Take basic message and wrap with plural message type.
var msgFormatter = new MessageFormat('{N,plural,' + relTimeString + '}');
return msgFormatter.format({'N': absQuantity});
};
/**
* From the data, return the information for the given unit and style.
* @param {number} relativeUnit
* @return {!relativeDateTimeSymbols.StyleElement|undefined} RelativeUnitStyle
* @private
*/
RelativeDateTimeFormat.prototype.getUnitStylePattern_ = function(relativeUnit) {
var unitInfo = this.getUnitPattern_(relativeUnit);
asserts.assertObject(unitInfo);
return this.getStylePattern_(unitInfo);
};
/**
* Use public unit symbol to retrieve data for that unit.
* @param {number|!relativeDateTimeSymbols.RelativeDateTimeFormatStyles} unit
* @return {!relativeDateTimeSymbols.RelativeDateTimeFormatStyles}
* @private
*/
RelativeDateTimeFormat.prototype.getUnitPattern_ = function(unit) {
switch (unit) {
default:
case RelativeDateTimeFormat.Unit.YEAR:
return this.rdtfSymbols_.YEAR;
case RelativeDateTimeFormat.Unit.QUARTER:
return this.rdtfSymbols_.QUARTER;
case RelativeDateTimeFormat.Unit.MONTH:
return this.rdtfSymbols_.MONTH;
case RelativeDateTimeFormat.Unit.WEEK:
return this.rdtfSymbols_.WEEK;
case RelativeDateTimeFormat.Unit.DAY:
return this.rdtfSymbols_.DAY;
case RelativeDateTimeFormat.Unit.HOUR:
return this.rdtfSymbols_.HOUR;
case RelativeDateTimeFormat.Unit.MINUTE:
return this.rdtfSymbols_.MINUTE;
case RelativeDateTimeFormat.Unit.SECOND:
return this.rdtfSymbols_.SECOND;
}
};
/**
* Use public unit symbol to retrieve data for that unit, given the style.
* @param{!relativeDateTimeSymbols.RelativeDateTimeFormatStyles} unit
* @return {!relativeDateTimeSymbols.StyleElement|undefined}
* @private
*/
RelativeDateTimeFormat.prototype.getStylePattern_ = function(unit) {
// Fall back from NARROW to SHORT to LONG as needed.
switch (this.style_) {
case RelativeDateTimeFormat.Style.NARROW:
if (unit.NARROW != undefined) {
return unit.NARROW;
}
case RelativeDateTimeFormat.Style.SHORT:
if (unit.SHORT != undefined) {
return unit.SHORT;
}
case RelativeDateTimeFormat.Style.LONG:
default:
return unit.LONG;
}
};
/**
* Returns the style set for this formatter.
* @return {number} One of LONG, SHORT, NARROW,
*/
RelativeDateTimeFormat.prototype.getFormatStyle = function() {
return this.style_;
};
/**
* Returns the status of the alwaysNumeric field.
* @return {!RelativeDateTimeFormat.NumericOption}
*/
RelativeDateTimeFormat.prototype.getNumericMode = function() {
if (this.alwaysNumeric_) {
return RelativeDateTimeFormat.NumericOption.ALWAYS;
} else {
return RelativeDateTimeFormat.NumericOption.AUTO;
}
};
/**
* Returns relative field for an offset of a given value unit
* if it is defined for the current style.
* If the value does not exist, return undefined.
* For example, is there a -2 offset for DAY in the current locale and style.
* @param{!RelativeDateTimeFormat.Unit} unit
* @param{string|number} offset
* @return{string|undefined}
*/
RelativeDateTimeFormat.prototype.isOffsetDefinedForUnit = function(
unit, offset) {
var rdtfUnitPattern = this.getUnitStylePattern_(unit);
if (typeof (offset) == 'number') {
offset = offset.toString();
}
// Check for force numeric and having relative value with the given quantity.
if (rdtfUnitPattern && rdtfUnitPattern.R && rdtfUnitPattern.R[offset]) {
return rdtfUnitPattern.R[offset];
}
return undefined;
};
exports = RelativeDateTimeFormat;