edp445/node_modules/moment-duration-format/lib/moment-duration-format.js

1738 lines
60 KiB
JavaScript

/*! Moment Duration Format v2.2.2
* https://github.com/jsmreese/moment-duration-format
* Date: 2018-02-16
*
* Duration format plugin function for the Moment.js library
* http://momentjs.com/
*
* Copyright 2018 John Madhavan-Reese
* Released under the MIT license
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['moment'], factory);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but only CommonJS-like
// enviroments that support module.exports, like Node.
try {
module.exports = factory(require('moment'));
} catch (e) {
// If moment is not available, leave the setup up to the user.
// Like when using moment-timezone or similar moment-based package.
module.exports = factory;
}
}
if (root) {
// Globals.
root.momentDurationFormatSetup = root.moment ? factory(root.moment) : factory;
}
})(this, function (moment) {
// `Number#tolocaleString` is tested on plugin initialization.
// If the feature test passes, `toLocaleStringWorks` will be set to `true` and the
// native function will be used to generate formatted output. If the feature
// test fails, the fallback format function internal to this plugin will be
// used.
var toLocaleStringWorks = false;
// `Number#toLocaleString` rounds incorrectly for select numbers in Microsoft
// environments (Edge, IE11, Windows Phone) and possibly other environments.
// If the rounding test fails and `toLocaleString` will be used for formatting,
// the plugin will "pre-round" number values using the fallback number format
// function before passing them to `toLocaleString` for final formatting.
var toLocaleStringRoundingWorks = false;
// `Intl.NumberFormat#format` is tested on plugin initialization.
// If the feature test passes, `intlNumberFormatRoundingWorks` will be set to
// `true` and the native function will be used to generate formatted output.
// If the feature test fails, either `Number#tolocaleString` (if
// `toLocaleStringWorks` is `true`), or the fallback format function internal
// to this plugin will be used.
var intlNumberFormatWorks = false;
// `Intl.NumberFormat#format` rounds incorrectly for select numbers in Microsoft
// environments (Edge, IE11, Windows Phone) and possibly other environments.
// If the rounding test fails and `Intl.NumberFormat#format` will be used for
// formatting, the plugin will "pre-round" number values using the fallback number
// format function before passing them to `Intl.NumberFormat#format` for final
// formatting.
var intlNumberFormatRoundingWorks = false;
// Token type names in order of descending magnitude.
var types = "escape years months weeks days hours minutes seconds milliseconds general".split(" ");
var bubbles = [
{
type: "seconds",
targets: [
{ type: "minutes", value: 60 },
{ type: "hours", value: 3600 },
{ type: "days", value: 86400 },
{ type: "weeks", value: 604800 },
{ type: "months", value: 2678400 },
{ type: "years", value: 31536000 }
]
},
{
type: "minutes",
targets: [
{ type: "hours", value: 60 },
{ type: "days", value: 1440 },
{ type: "weeks", value: 10080 },
{ type: "months", value: 44640 },
{ type: "years", value: 525600 }
]
},
{
type: "hours",
targets: [
{ type: "days", value: 24 },
{ type: "weeks", value: 168 },
{ type: "months", value: 744 },
{ type: "years", value: 8760 }
]
},
{
type: "days",
targets: [
{ type: "weeks", value: 7 },
{ type: "months", value: 31 },
{ type: "years", value: 365 }
]
},
{
type: "months",
targets: [
{ type: "years", value: 12 }
]
}
];
// stringIncludes
function stringIncludes(str, search) {
if (search.length > str.length) {
return false;
}
return str.indexOf(search) !== -1;
}
// repeatZero(qty)
// Returns "0" repeated `qty` times.
// `qty` must be a integer >= 0.
function repeatZero(qty) {
var result = "";
while (qty) {
result += "0";
qty -= 1;
}
return result;
}
function stringRound(digits) {
var digitsArray = digits.split("").reverse();
var i = 0;
var carry = true;
while (carry && i < digitsArray.length) {
if (i) {
if (digitsArray[i] === "9") {
digitsArray[i] = "0";
} else {
digitsArray[i] = (parseInt(digitsArray[i], 10) + 1).toString();
carry = false;
}
} else {
if (parseInt(digitsArray[i], 10) < 5) {
carry = false;
}
digitsArray[i] = "0";
}
i += 1;
}
if (carry) {
digitsArray.push("1");
}
return digitsArray.reverse().join("");
}
// cachedNumberFormat
// Returns an `Intl.NumberFormat` instance for the given locale and configuration.
// On first use of a particular configuration, the instance is cached for fast
// repeat access.
function cachedNumberFormat(locale, options) {
// Create a sorted, stringified version of `options`
// for use as part of the cache key
var optionsString = map(
keys(options).sort(),
function(key) {
return key + ':' + options[key];
}
).join(',');
// Set our cache key
var cacheKey = locale + '+' + optionsString;
// If we don't have this configuration cached, configure and cache it
if (!cachedNumberFormat.cache[cacheKey]) {
cachedNumberFormat.cache[cacheKey] = Intl.NumberFormat(locale, options);
}
// Return the cached version of this configuration
return cachedNumberFormat.cache[cacheKey];
}
cachedNumberFormat.cache = {};
// formatNumber
// Formats any number greater than or equal to zero using these options:
// - userLocale
// - useToLocaleString
// - useGrouping
// - grouping
// - maximumSignificantDigits
// - minimumIntegerDigits
// - fractionDigits
// - groupingSeparator
// - decimalSeparator
//
// `useToLocaleString` will use `Intl.NumberFormat` or `toLocaleString` for formatting.
// `userLocale` option is passed through to the formatting function.
// `fractionDigits` is passed through to `maximumFractionDigits` and `minimumFractionDigits`
// Using `maximumSignificantDigits` will override `minimumIntegerDigits` and `fractionDigits`.
function formatNumber(number, options, userLocale) {
var useToLocaleString = options.useToLocaleString;
var useGrouping = options.useGrouping;
var grouping = useGrouping && options.grouping.slice();
var maximumSignificantDigits = options.maximumSignificantDigits;
var minimumIntegerDigits = options.minimumIntegerDigits || 1;
var fractionDigits = options.fractionDigits || 0;
var groupingSeparator = options.groupingSeparator;
var decimalSeparator = options.decimalSeparator;
if (useToLocaleString && userLocale) {
var localeStringOptions = {
minimumIntegerDigits: minimumIntegerDigits,
useGrouping: useGrouping
};
if (fractionDigits) {
localeStringOptions.maximumFractionDigits = fractionDigits;
localeStringOptions.minimumFractionDigits = fractionDigits;
}
// toLocaleString output is "0.0" instead of "0" for HTC browsers
// when maximumSignificantDigits is set. See #96.
if (maximumSignificantDigits && number > 0) {
localeStringOptions.maximumSignificantDigits = maximumSignificantDigits;
}
if (intlNumberFormatWorks) {
if (!intlNumberFormatRoundingWorks) {
var roundingOptions = extend({}, options);
roundingOptions.useGrouping = false;
roundingOptions.decimalSeparator = ".";
number = parseFloat(formatNumber(number, roundingOptions), 10);
}
return cachedNumberFormat(userLocale, localeStringOptions).format(number);
} else {
if (!toLocaleStringRoundingWorks) {
var roundingOptions = extend({}, options);
roundingOptions.useGrouping = false;
roundingOptions.decimalSeparator = ".";
number = parseFloat(formatNumber(number, roundingOptions), 10);
}
return number.toLocaleString(userLocale, localeStringOptions);
}
}
var numberString;
// Add 1 to digit output length for floating point errors workaround. See below.
if (maximumSignificantDigits) {
numberString = number.toPrecision(maximumSignificantDigits + 1);
} else {
numberString = number.toFixed(fractionDigits + 1);
}
var integerString;
var fractionString;
var exponentString;
var temp = numberString.split("e");
exponentString = temp[1] || "";
temp = temp[0].split(".");
fractionString = temp[1] || "";
integerString = temp[0] || "";
// Workaround for floating point errors in `toFixed` and `toPrecision`.
// (3.55).toFixed(1); --> "3.5"
// (123.55 - 120).toPrecision(2); --> "3.5"
// (123.55 - 120); --> 3.549999999999997
// (123.55 - 120).toFixed(2); --> "3.55"
// Round by examing the string output of the next digit.
// *************** Implement String Rounding here ***********************
// Check integerString + fractionString length of toPrecision before rounding.
// Check length of fractionString from toFixed output before rounding.
var integerLength = integerString.length;
var fractionLength = fractionString.length;
var digitCount = integerLength + fractionLength;
var digits = integerString + fractionString;
if (maximumSignificantDigits && digitCount === (maximumSignificantDigits + 1) || !maximumSignificantDigits && fractionLength === (fractionDigits + 1)) {
// Round digits.
digits = stringRound(digits);
if (digits.length === digitCount + 1) {
integerLength = integerLength + 1;
}
// Discard final fractionDigit.
if (fractionLength) {
digits = digits.slice(0, -1);
}
// Separate integer and fraction.
integerString = digits.slice(0, integerLength);
fractionString = digits.slice(integerLength);
}
// Trim trailing zeroes from fractionString because toPrecision outputs
// precision, not significant digits.
if (maximumSignificantDigits) {
fractionString = fractionString.replace(/0*$/, "");
}
// Handle exponent.
var exponent = parseInt(exponentString, 10);
if (exponent > 0) {
if (fractionString.length <= exponent) {
fractionString = fractionString + repeatZero(exponent - fractionString.length);
integerString = integerString + fractionString;
fractionString = "";
} else {
integerString = integerString + fractionString.slice(0, exponent);
fractionString = fractionString.slice(exponent);
}
} else if (exponent < 0) {
fractionString = (repeatZero(Math.abs(exponent) - integerString.length) + integerString + fractionString);
integerString = "0";
}
if (!maximumSignificantDigits) {
// Trim or pad fraction when not using maximumSignificantDigits.
fractionString = fractionString.slice(0, fractionDigits);
if (fractionString.length < fractionDigits) {
fractionString = fractionString + repeatZero(fractionDigits - fractionString.length);
}
// Pad integer when using minimumIntegerDigits
// and not using maximumSignificantDigits.
if (integerString.length < minimumIntegerDigits) {
integerString = repeatZero(minimumIntegerDigits - integerString.length) + integerString;
}
}
var formattedString = "";
// Handle grouping.
if (useGrouping) {
temp = integerString;
var group;
while (temp.length) {
if (grouping.length) {
group = grouping.shift();
}
if (formattedString) {
formattedString = groupingSeparator + formattedString;
}
formattedString = temp.slice(-group) + formattedString;
temp = temp.slice(0, -group);
}
} else {
formattedString = integerString;
}
// Add decimalSeparator and fraction.
if (fractionString) {
formattedString = formattedString + decimalSeparator + fractionString;
}
return formattedString;
}
// durationLabelCompare
function durationLabelCompare(a, b) {
if (a.label.length > b.label.length) {
return -1;
}
if (a.label.length < b.label.length) {
return 1;
}
// a must be equal to b
return 0;
}
// durationGetLabels
function durationGetLabels(token, localeData) {
var labels = [];
each(keys(localeData), function (localeDataKey) {
if (localeDataKey.slice(0, 15) !== "_durationLabels") {
return;
}
var labelType = localeDataKey.slice(15).toLowerCase();
each(keys(localeData[localeDataKey]), function (labelKey) {
if (labelKey.slice(0, 1) === token) {
labels.push({
type: labelType,
key: labelKey,
label: localeData[localeDataKey][labelKey]
});
}
});
});
return labels;
}
// durationPluralKey
function durationPluralKey(token, integerValue, decimalValue) {
// Singular for a value of `1`, but not for `1.0`.
if (integerValue === 1 && decimalValue === null) {
return token;
}
return token + token;
}
var engLocale = {
durationLabelsStandard: {
S: 'millisecond',
SS: 'milliseconds',
s: 'second',
ss: 'seconds',
m: 'minute',
mm: 'minutes',
h: 'hour',
hh: 'hours',
d: 'day',
dd: 'days',
w: 'week',
ww: 'weeks',
M: 'month',
MM: 'months',
y: 'year',
yy: 'years'
},
durationLabelsShort: {
S: 'msec',
SS: 'msecs',
s: 'sec',
ss: 'secs',
m: 'min',
mm: 'mins',
h: 'hr',
hh: 'hrs',
d: 'dy',
dd: 'dys',
w: 'wk',
ww: 'wks',
M: 'mo',
MM: 'mos',
y: 'yr',
yy: 'yrs'
},
durationTimeTemplates: {
HMS: 'h:mm:ss',
HM: 'h:mm',
MS: 'm:ss'
},
durationLabelTypes: [
{ type: "standard", string: "__" },
{ type: "short", string: "_" }
],
durationPluralKey: durationPluralKey
};
// isArray
function isArray(array) {
return Object.prototype.toString.call(array) === "[object Array]";
}
// isObject
function isObject(obj) {
return Object.prototype.toString.call(obj) === "[object Object]";
}
// findLast
function findLast(array, callback) {
var index = array.length;
while (index -= 1) {
if (callback(array[index])) { return array[index]; }
}
}
// find
function find(array, callback) {
var index = 0;
var max = array && array.length || 0;
var match;
if (typeof callback !== "function") {
match = callback;
callback = function (item) {
return item === match;
};
}
while (index < max) {
if (callback(array[index])) { return array[index]; }
index += 1;
}
}
// each
function each(array, callback) {
var index = 0,
max = array.length;
if (!array || !max) { return; }
while (index < max) {
if (callback(array[index], index) === false) { return; }
index += 1;
}
}
// map
function map(array, callback) {
var index = 0,
max = array.length,
ret = [];
if (!array || !max) { return ret; }
while (index < max) {
ret[index] = callback(array[index], index);
index += 1;
}
return ret;
}
// pluck
function pluck(array, prop) {
return map(array, function (item) {
return item[prop];
});
}
// compact
function compact(array) {
var ret = [];
each(array, function (item) {
if (item) { ret.push(item); }
});
return ret;
}
// unique
function unique(array) {
var ret = [];
each(array, function (_a) {
if (!find(ret, _a)) { ret.push(_a); }
});
return ret;
}
// intersection
function intersection(a, b) {
var ret = [];
each(a, function (_a) {
each(b, function (_b) {
if (_a === _b) { ret.push(_a); }
});
});
return unique(ret);
}
// rest
function rest(array, callback) {
var ret = [];
each(array, function (item, index) {
if (!callback(item)) {
ret = array.slice(index);
return false;
}
});
return ret;
}
// initial
function initial(array, callback) {
var reversed = array.slice().reverse();
return rest(reversed, callback).reverse();
}
// extend
function extend(a, b) {
for (var key in b) {
if (b.hasOwnProperty(key)) { a[key] = b[key]; }
}
return a;
}
// keys
function keys(a) {
var ret = [];
for (var key in a) {
if (a.hasOwnProperty(key)) { ret.push(key); }
}
return ret;
}
// any
function any(array, callback) {
var index = 0,
max = array.length;
if (!array || !max) { return false; }
while (index < max) {
if (callback(array[index], index) === true) { return true; }
index += 1;
}
return false;
}
// flatten
function flatten(array) {
var ret = [];
each(array, function(child) {
ret = ret.concat(child);
});
return ret;
}
function toLocaleStringSupportsLocales() {
var number = 0;
try {
number.toLocaleString('i');
} catch (e) {
return e.name === 'RangeError';
}
return false;
}
function featureTestFormatterRounding(formatter) {
return formatter(3.55, "en", {
useGrouping: false,
minimumIntegerDigits: 1,
minimumFractionDigits: 1,
maximumFractionDigits: 1
}) === "3.6";
}
function featureTestFormatter(formatter) {
var passed = true;
// Test minimumIntegerDigits.
passed = passed && formatter(1, "en", { minimumIntegerDigits: 1 }) === "1";
passed = passed && formatter(1, "en", { minimumIntegerDigits: 2 }) === "01";
passed = passed && formatter(1, "en", { minimumIntegerDigits: 3 }) === "001";
if (!passed) { return false; }
// Test maximumFractionDigits and minimumFractionDigits.
passed = passed && formatter(99.99, "en", { maximumFractionDigits: 0, minimumFractionDigits: 0 }) === "100";
passed = passed && formatter(99.99, "en", { maximumFractionDigits: 1, minimumFractionDigits: 1 }) === "100.0";
passed = passed && formatter(99.99, "en", { maximumFractionDigits: 2, minimumFractionDigits: 2 }) === "99.99";
passed = passed && formatter(99.99, "en", { maximumFractionDigits: 3, minimumFractionDigits: 3 }) === "99.990";
if (!passed) { return false; }
// Test maximumSignificantDigits.
passed = passed && formatter(99.99, "en", { maximumSignificantDigits: 1 }) === "100";
passed = passed && formatter(99.99, "en", { maximumSignificantDigits: 2 }) === "100";
passed = passed && formatter(99.99, "en", { maximumSignificantDigits: 3 }) === "100";
passed = passed && formatter(99.99, "en", { maximumSignificantDigits: 4 }) === "99.99";
passed = passed && formatter(99.99, "en", { maximumSignificantDigits: 5 }) === "99.99";
if (!passed) { return false; }
// Test grouping.
passed = passed && formatter(1000, "en", { useGrouping: true }) === "1,000";
passed = passed && formatter(1000, "en", { useGrouping: false }) === "1000";
if (!passed) { return false; }
return true;
}
// durationsFormat(durations [, template] [, precision] [, settings])
function durationsFormat() {
var args = [].slice.call(arguments);
var settings = {};
var durations;
// Parse arguments.
each(args, function (arg, index) {
if (!index) {
if (!isArray(arg)) {
throw "Expected array as the first argument to durationsFormat.";
}
durations = arg;
}
if (typeof arg === "string" || typeof arg === "function") {
settings.template = arg;
return;
}
if (typeof arg === "number") {
settings.precision = arg;
return;
}
if (isObject(arg)) {
extend(settings, arg);
}
});
if (!durations || !durations.length) {
return [];
}
settings.returnMomentTypes = true;
var formattedDurations = map(durations, function (dur) {
return dur.format(settings);
});
// Merge token types from all durations.
var outputTypes = intersection(types, unique(pluck(flatten(formattedDurations), "type")));
var largest = settings.largest;
if (largest) {
outputTypes = outputTypes.slice(0, largest);
}
settings.returnMomentTypes = false;
settings.outputTypes = outputTypes;
return map(durations, function (dur) {
return dur.format(settings);
});
}
// durationFormat([template] [, precision] [, settings])
function durationFormat() {
var args = [].slice.call(arguments);
var settings = extend({}, this.format.defaults);
// Keep a shadow copy of this moment for calculating remainders.
// Perform all calculations on positive duration value, handle negative
// sign at the very end.
var asMilliseconds = this.asMilliseconds();
var asMonths = this.asMonths();
// Treat invalid durations as having a value of 0 milliseconds.
if (typeof this.isValid === "function" && this.isValid() === false) {
asMilliseconds = 0;
asMonths = 0;
}
var isNegative = asMilliseconds < 0;
// Two shadow copies are needed because of the way moment.js handles
// duration arithmetic for years/months and for weeks/days/hours/minutes/seconds.
var remainder = moment.duration(Math.abs(asMilliseconds), "milliseconds");
var remainderMonths = moment.duration(Math.abs(asMonths), "months");
// Parse arguments.
each(args, function (arg) {
if (typeof arg === "string" || typeof arg === "function") {
settings.template = arg;
return;
}
if (typeof arg === "number") {
settings.precision = arg;
return;
}
if (isObject(arg)) {
extend(settings, arg);
}
});
var momentTokens = {
years: "y",
months: "M",
weeks: "w",
days: "d",
hours: "h",
minutes: "m",
seconds: "s",
milliseconds: "S"
};
var tokenDefs = {
escape: /\[(.+?)\]/,
years: /\*?[Yy]+/,
months: /\*?M+/,
weeks: /\*?[Ww]+/,
days: /\*?[Dd]+/,
hours: /\*?[Hh]+/,
minutes: /\*?m+/,
seconds: /\*?s+/,
milliseconds: /\*?S+/,
general: /.+?/
};
// Types array is available in the template function.
settings.types = types;
var typeMap = function (token) {
return find(types, function (type) {
return tokenDefs[type].test(token);
});
};
var tokenizer = new RegExp(map(types, function (type) {
return tokenDefs[type].source;
}).join("|"), "g");
// Current duration object is available in the template function.
settings.duration = this;
// Eval template function and cache template string.
var template = typeof settings.template === "function" ? settings.template.apply(settings) : settings.template;
// outputTypes and returnMomentTypes are settings to support durationsFormat().
// outputTypes is an array of moment token types that determines
// the tokens returned in formatted output. This option overrides
// trim, largest, stopTrim, etc.
var outputTypes = settings.outputTypes;
// returnMomentTypes is a boolean that sets durationFormat to return
// the processed momentTypes instead of formatted output.
var returnMomentTypes = settings.returnMomentTypes;
var largest = settings.largest;
// Setup stopTrim array of token types.
var stopTrim = [];
if (!outputTypes) {
if (isArray(settings.stopTrim)) {
settings.stopTrim = settings.stopTrim.join("");
}
// Parse stopTrim string to create token types array.
if (settings.stopTrim) {
each(settings.stopTrim.match(tokenizer), function (token) {
var type = typeMap(token);
if (type === "escape" || type === "general") {
return;
}
stopTrim.push(type);
});
}
}
// Cache moment's locale data.
var localeData = moment.localeData();
if (!localeData) {
localeData = {};
}
// Fall back to this plugin's `eng` extension.
each(keys(engLocale), function (key) {
if (typeof engLocale[key] === "function") {
if (!localeData[key]) {
localeData[key] = engLocale[key];
}
return;
}
if (!localeData["_" + key]) {
localeData["_" + key] = engLocale[key];
}
});
// Replace Duration Time Template strings.
// For locale `eng`: `_HMS_`, `_HM_`, and `_MS_`.
each(keys(localeData._durationTimeTemplates), function (item) {
template = template.replace("_" + item + "_", localeData._durationTimeTemplates[item]);
});
// Determine user's locale.
var userLocale = settings.userLocale || moment.locale();
var useLeftUnits = settings.useLeftUnits;
var usePlural = settings.usePlural;
var precision = settings.precision;
var forceLength = settings.forceLength;
var useGrouping = settings.useGrouping;
var trunc = settings.trunc;
// Use significant digits only when precision is greater than 0.
var useSignificantDigits = settings.useSignificantDigits && precision > 0;
var significantDigits = useSignificantDigits ? settings.precision : 0;
var significantDigitsCache = significantDigits;
var minValue = settings.minValue;
var isMinValue = false;
var maxValue = settings.maxValue;
var isMaxValue = false;
// formatNumber fallback options.
var useToLocaleString = settings.useToLocaleString;
var groupingSeparator = settings.groupingSeparator;
var decimalSeparator = settings.decimalSeparator;
var grouping = settings.grouping;
useToLocaleString = useToLocaleString && (toLocaleStringWorks || intlNumberFormatWorks);
// Trim options.
var trim = settings.trim;
if (isArray(trim)) {
trim = trim.join(" ");
}
if (trim === null && (largest || maxValue || useSignificantDigits)) {
trim = "all";
}
if (trim === null || trim === true || trim === "left" || trim === "right") {
trim = "large";
}
if (trim === false) {
trim = "";
}
var trimIncludes = function (item) {
return item.test(trim);
};
var rLarge = /large/;
var rSmall = /small/;
var rBoth = /both/;
var rMid = /mid/;
var rAll = /^all|[^sm]all/;
var rFinal = /final/;
var trimLarge = largest > 0 || any([rLarge, rBoth, rAll], trimIncludes);
var trimSmall = any([rSmall, rBoth, rAll], trimIncludes);
var trimMid = any([rMid, rAll], trimIncludes);
var trimFinal = any([rFinal, rAll], trimIncludes);
// Parse format string to create raw tokens array.
var rawTokens = map(template.match(tokenizer), function (token, index) {
var type = typeMap(token);
if (token.slice(0, 1) === "*") {
token = token.slice(1);
if (type !== "escape" && type !== "general") {
stopTrim.push(type);
}
}
return {
index: index,
length: token.length,
text: "",
// Replace escaped tokens with the non-escaped token text.
token: (type === "escape" ? token.replace(tokenDefs.escape, "$1") : token),
// Ignore type on non-moment tokens.
type: ((type === "escape" || type === "general") ? null : type)
};
});
// Associate text tokens with moment tokens.
var currentToken = {
index: 0,
length: 0,
token: "",
text: "",
type: null
};
var tokens = [];
if (useLeftUnits) {
rawTokens.reverse();
}
each(rawTokens, function (token) {
if (token.type) {
if (currentToken.type || currentToken.text) {
tokens.push(currentToken);
}
currentToken = token;
return;
}
if (useLeftUnits) {
currentToken.text = token.token + currentToken.text;
} else {
currentToken.text += token.token;
}
});
if (currentToken.type || currentToken.text) {
tokens.push(currentToken);
}
if (useLeftUnits) {
tokens.reverse();
}
// Find unique moment token types in the template in order of
// descending magnitude.
var momentTypes = intersection(types, unique(compact(pluck(tokens, "type"))));
// Exit early if there are no moment token types.
if (!momentTypes.length) {
return pluck(tokens, "text").join("");
}
// Calculate values for each moment type in the template.
// For processing the settings, values are associated with moment types.
// Values will be assigned to tokens at the last step in order to
// assume nothing about frequency or order of tokens in the template.
momentTypes = map(momentTypes, function (momentType, index) {
// Is this the least-magnitude moment token found?
var isSmallest = ((index + 1) === momentTypes.length);
// Is this the greatest-magnitude moment token found?
var isLargest = (!index);
// Get the raw value in the current units.
var rawValue;
if (momentType === "years" || momentType === "months") {
rawValue = remainderMonths.as(momentType);
} else {
rawValue = remainder.as(momentType);
}
var wholeValue = Math.floor(rawValue);
var decimalValue = rawValue - wholeValue;
var token = find(tokens, function (token) {
return momentType === token.type;
});
if (isLargest && maxValue && rawValue > maxValue) {
isMaxValue = true;
}
if (isSmallest && minValue && Math.abs(settings.duration.as(momentType)) < minValue) {
isMinValue = true;
}
// Note the length of the largest-magnitude moment token:
// if it is greater than one and forceLength is not set,
// then default forceLength to `true`.
//
// Rationale is this: If the template is "h:mm:ss" and the
// moment value is 5 minutes, the user-friendly output is
// "5:00", not "05:00". We shouldn't pad the `minutes` token
// even though it has length of two if the template is "h:mm:ss";
//
// If the minutes output should always include the leading zero
// even when the hour is trimmed then set `{ forceLength: true }`
// to output "05:00". If the template is "hh:mm:ss", the user
// clearly wanted everything padded so we should output "05:00";
//
// If the user wants the full padded output, they can use
// template "hh:mm:ss" and set `{ trim: false }` to output
// "00:05:00".
if (isLargest && forceLength === null && token.length > 1) {
forceLength = true;
}
// Update remainder.
remainder.subtract(wholeValue, momentType);
remainderMonths.subtract(wholeValue, momentType);
return {
rawValue: rawValue,
wholeValue: wholeValue,
// Decimal value is only retained for the least-magnitude
// moment type in the format template.
decimalValue: isSmallest ? decimalValue : 0,
isSmallest: isSmallest,
isLargest: isLargest,
type: momentType,
// Tokens can appear multiple times in a template string,
// but all instances must share the same length.
tokenLength: token.length
};
});
var truncMethod = trunc ? Math.floor : Math.round;
var truncate = function (value, places) {
var factor = Math.pow(10, places);
return truncMethod(value * factor) / factor;
};
var foundFirst = false;
var bubbled = false;
var formatValue = function (momentType, index) {
var formatOptions = {
useGrouping: useGrouping,
groupingSeparator: groupingSeparator,
decimalSeparator: decimalSeparator,
grouping: grouping,
useToLocaleString: useToLocaleString
};
if (useSignificantDigits) {
if (significantDigits <= 0) {
momentType.rawValue = 0;
momentType.wholeValue = 0;
momentType.decimalValue = 0;
} else {
formatOptions.maximumSignificantDigits = significantDigits;
momentType.significantDigits = significantDigits;
}
}
if (isMaxValue && !bubbled) {
if (momentType.isLargest) {
momentType.wholeValue = maxValue;
momentType.decimalValue = 0;
} else {
momentType.wholeValue = 0;
momentType.decimalValue = 0;
}
}
if (isMinValue && !bubbled) {
if (momentType.isSmallest) {
momentType.wholeValue = minValue;
momentType.decimalValue = 0;
} else {
momentType.wholeValue = 0;
momentType.decimalValue = 0;
}
}
if (momentType.isSmallest || momentType.significantDigits && momentType.significantDigits - momentType.wholeValue.toString().length <= 0) {
// Apply precision to least significant token value.
if (precision < 0) {
momentType.value = truncate(momentType.wholeValue, precision);
} else if (precision === 0) {
momentType.value = truncMethod(momentType.wholeValue + momentType.decimalValue);
} else { // precision > 0
if (useSignificantDigits) {
if (trunc) {
momentType.value = truncate(momentType.rawValue, significantDigits - momentType.wholeValue.toString().length);
} else {
momentType.value = momentType.rawValue;
}
if (momentType.wholeValue) {
significantDigits -= momentType.wholeValue.toString().length;
}
} else {
formatOptions.fractionDigits = precision;
if (trunc) {
momentType.value = momentType.wholeValue + truncate(momentType.decimalValue, precision);
} else {
momentType.value = momentType.wholeValue + momentType.decimalValue;
}
}
}
} else {
if (useSignificantDigits && momentType.wholeValue) {
// Outer Math.round required here to handle floating point errors.
momentType.value = Math.round(truncate(momentType.wholeValue, momentType.significantDigits - momentType.wholeValue.toString().length));
significantDigits -= momentType.wholeValue.toString().length;
} else {
momentType.value = momentType.wholeValue;
}
}
if (momentType.tokenLength > 1 && (forceLength || foundFirst)) {
formatOptions.minimumIntegerDigits = momentType.tokenLength;
if (bubbled && formatOptions.maximumSignificantDigits < momentType.tokenLength) {
delete formatOptions.maximumSignificantDigits;
}
}
if (!foundFirst && (momentType.value > 0 || trim === "" /* trim: false */ || find(stopTrim, momentType.type) || find(outputTypes, momentType.type))) {
foundFirst = true;
}
momentType.formattedValue = formatNumber(momentType.value, formatOptions, userLocale);
formatOptions.useGrouping = false;
formatOptions.decimalSeparator = ".";
momentType.formattedValueEn = formatNumber(momentType.value, formatOptions, "en");
if (momentType.tokenLength === 2 && momentType.type === "milliseconds") {
momentType.formattedValueMS = formatNumber(momentType.value, {
minimumIntegerDigits: 3,
useGrouping: false
}, "en").slice(0, 2);
}
return momentType;
};
// Calculate formatted values.
momentTypes = map(momentTypes, formatValue);
momentTypes = compact(momentTypes);
// Bubble rounded values.
if (momentTypes.length > 1) {
var findType = function (type) {
return find(momentTypes, function (momentType) {
return momentType.type === type;
});
};
var bubbleTypes = function (bubble) {
var bubbleMomentType = findType(bubble.type);
if (!bubbleMomentType) {
return;
}
each(bubble.targets, function (target) {
var targetMomentType = findType(target.type);
if (!targetMomentType) {
return;
}
if (parseInt(bubbleMomentType.formattedValueEn, 10) === target.value) {
bubbleMomentType.rawValue = 0;
bubbleMomentType.wholeValue = 0;
bubbleMomentType.decimalValue = 0;
targetMomentType.rawValue += 1;
targetMomentType.wholeValue += 1;
targetMomentType.decimalValue = 0;
targetMomentType.formattedValueEn = targetMomentType.wholeValue.toString();
bubbled = true;
}
});
};
each(bubbles, bubbleTypes);
}
// Recalculate formatted values.
if (bubbled) {
foundFirst = false;
significantDigits = significantDigitsCache;
momentTypes = map(momentTypes, formatValue);
momentTypes = compact(momentTypes);
}
if (outputTypes && !(isMaxValue && !settings.trim)) {
momentTypes = map(momentTypes, function (momentType) {
if (find(outputTypes, function (outputType) {
return momentType.type === outputType;
})) {
return momentType;
}
return null;
});
momentTypes = compact(momentTypes);
} else {
// Trim Large.
if (trimLarge) {
momentTypes = rest(momentTypes, function (momentType) {
// Stop trimming on:
// - the smallest moment type
// - a type marked for stopTrim
// - a type that has a whole value
return !momentType.isSmallest && !momentType.wholeValue && !find(stopTrim, momentType.type);
});
}
// Largest.
if (largest && momentTypes.length) {
momentTypes = momentTypes.slice(0, largest);
}
// Trim Small.
if (trimSmall && momentTypes.length > 1) {
momentTypes = initial(momentTypes, function (momentType) {
// Stop trimming on:
// - a type marked for stopTrim
// - a type that has a whole value
// - the largest momentType
return !momentType.wholeValue && !find(stopTrim, momentType.type) && !momentType.isLargest;
});
}
// Trim Mid.
if (trimMid) {
momentTypes = map(momentTypes, function (momentType, index) {
if (index > 0 && index < momentTypes.length - 1 && !momentType.wholeValue) {
return null;
}
return momentType;
});
momentTypes = compact(momentTypes);
}
// Trim Final.
if (trimFinal && momentTypes.length === 1 && !momentTypes[0].wholeValue && !(!trunc && momentTypes[0].isSmallest && momentTypes[0].rawValue < minValue)) {
momentTypes = [];
}
}
if (returnMomentTypes) {
return momentTypes;
}
// Localize and pluralize unit labels.
each(tokens, function (token) {
var key = momentTokens[token.type];
var momentType = find(momentTypes, function (momentType) {
return momentType.type === token.type;
});
if (!key || !momentType) {
return;
}
var values = momentType.formattedValueEn.split(".");
values[0] = parseInt(values[0], 10);
if (values[1]) {
values[1] = parseFloat("0." + values[1], 10);
} else {
values[1] = null;
}
var pluralKey = localeData.durationPluralKey(key, values[0], values[1]);
var labels = durationGetLabels(key, localeData);
var autoLocalized = false;
var pluralizedLabels = {};
// Auto-Localized unit labels.
each(localeData._durationLabelTypes, function (labelType) {
var label = find(labels, function (label) {
return label.type === labelType.type && label.key === pluralKey;
});
if (label) {
pluralizedLabels[label.type] = label.label;
if (stringIncludes(token.text, labelType.string)) {
token.text = token.text.replace(labelType.string, label.label);
autoLocalized = true;
}
}
});
// Auto-pluralized unit labels.
if (usePlural && !autoLocalized) {
labels.sort(durationLabelCompare);
each(labels, function (label) {
if (pluralizedLabels[label.type] === label.label) {
if (stringIncludes(token.text, label.label)) {
// Stop checking this token if its label is already
// correctly pluralized.
return false;
}
// Skip this label if it is correct, but not present in
// the token's text.
return;
}
if (stringIncludes(token.text, label.label)) {
// Replece this token's label and stop checking.
token.text = token.text.replace(label.label, pluralizedLabels[label.type]);
return false;
}
});
}
});
// Build ouptut.
tokens = map(tokens, function (token) {
if (!token.type) {
return token.text;
}
var momentType = find(momentTypes, function (momentType) {
return momentType.type === token.type;
});
if (!momentType) {
return "";
}
var out = "";
if (useLeftUnits) {
out += token.text;
}
if (isNegative && isMaxValue || !isNegative && isMinValue) {
out += "< ";
isMaxValue = false;
isMinValue = false;
}
if (isNegative && isMinValue || !isNegative && isMaxValue) {
out += "> ";
isMaxValue = false;
isMinValue = false;
}
if (isNegative && (momentType.value > 0 || trim === "" || find(stopTrim, momentType.type) || find(outputTypes, momentType.type))) {
out += "-";
isNegative = false;
}
if (token.type === "milliseconds" && momentType.formattedValueMS) {
out += momentType.formattedValueMS;
} else {
out += momentType.formattedValue;
}
if (!useLeftUnits) {
out += token.text;
}
return out;
});
// Trim leading and trailing comma, space, colon, and dot.
return tokens.join("").replace(/(,| |:|\.)*$/, "").replace(/^(,| |:|\.)*/, "");
}
// defaultFormatTemplate
function defaultFormatTemplate() {
var dur = this.duration;
var findType = function findType(type) {
return dur._data[type];
};
var firstType = find(this.types, findType);
var lastType = findLast(this.types, findType);
// Default template strings for each duration dimension type.
switch (firstType) {
case "milliseconds":
return "S __";
case "seconds": // Fallthrough.
case "minutes":
return "*_MS_";
case "hours":
return "_HMS_";
case "days": // Possible Fallthrough.
if (firstType === lastType) {
return "d __";
}
case "weeks":
if (firstType === lastType) {
return "w __";
}
if (this.trim === null) {
this.trim = "both";
}
return "w __, d __, h __";
case "months": // Possible Fallthrough.
if (firstType === lastType) {
return "M __";
}
case "years":
if (firstType === lastType) {
return "y __";
}
if (this.trim === null) {
this.trim = "both";
}
return "y __, M __, d __";
default:
if (this.trim === null) {
this.trim = "both";
}
return "y __, d __, h __, m __, s __";
}
}
// init
function init(context) {
if (!context) {
throw "Moment Duration Format init cannot find moment instance.";
}
context.duration.format = durationsFormat;
context.duration.fn.format = durationFormat;
context.duration.fn.format.defaults = {
// Many options are defaulted to `null` to distinguish between
// 'not set' and 'set to `false`'
// trim
// Can be a string, a delimited list of strings, an array of strings,
// or a boolean.
// "large" - will trim largest-magnitude zero-value tokens until
// finding a token with a value, a token identified as 'stopTrim', or
// the final token of the format string.
// "small" - will trim smallest-magnitude zero-value tokens until
// finding a token with a value, a token identified as 'stopTrim', or
// the final token of the format string.
// "both" - will execute "large" trim then "small" trim.
// "mid" - will trim any zero-value tokens that are not the first or
// last tokens. Usually used in conjunction with "large" or "both".
// e.g. "large mid" or "both mid".
// "final" - will trim the final token if it is zero-value. Use this
// option with "large" or "both" to output an empty string when
// formatting a zero-value duration. e.g. "large final" or "both final".
// "all" - Will trim all zero-value tokens. Shorthand for "both mid final".
// "left" - maps to "large" to support plugin's version 1 API.
// "right" - maps to "large" to support plugin's version 1 API.
// `false` - template tokens are not trimmed.
// `true` - treated as "large".
// `null` - treated as "large".
trim: null,
// stopTrim
// A moment token string, a delimited set of moment token strings,
// or an array of moment token strings. Trimming will stop when a token
// listed in this option is reached. A "*" character in the format
// template string will also mark a moment token as stopTrim.
// e.g. "d [days] *h:mm:ss" will always stop trimming at the 'hours' token.
stopTrim: null,
// largest
// Set to a positive integer to output only the "n" largest-magnitude
// moment tokens that have a value. All lesser-magnitude moment tokens
// will be ignored. This option takes effect even if `trim` is set
// to `false`.
largest: null,
// maxValue
// Use `maxValue` to render generalized output for large duration values,
// e.g. `"> 60 days"`. `maxValue` must be a positive integer and is
/// applied to the greatest-magnitude moment token in the format template.
maxValue: null,
// minValue
// Use `minValue` to render generalized output for small duration values,
// e.g. `"< 5 minutes"`. `minValue` must be a positive integer and is
// applied to the least-magnitude moment token in the format template.
minValue: null,
// precision
// If a positive integer, number of decimal fraction digits to render.
// If a negative integer, number of integer place digits to truncate to 0.
// If `useSignificantDigits` is set to `true` and `precision` is a positive
// integer, sets the maximum number of significant digits used in the
// formatted output.
precision: 0,
// trunc
// Default behavior rounds final token value. Set to `true` to
// truncate final token value, which was the default behavior in
// version 1 of this plugin.
trunc: false,
// forceLength
// Force first moment token with a value to render at full length
// even when template is trimmed and first moment token has length of 1.
forceLength: null,
// userLocale
// Formatted numerical output is rendered using `toLocaleString`
// and the locale of the user's environment. Set this option to render
// numerical output using a different locale. Unit names are rendered
// and detected using the locale set in moment.js, which can be different
// from the locale of user's environment.
userLocale: null,
// usePlural
// Will automatically singularize or pluralize unit names when they
// appear in the text associated with each moment token. Standard and
// short unit labels are singularized and pluralized, based on locale.
// e.g. in english, "1 second" or "1 sec" would be rendered instead
// of "1 seconds" or "1 secs". The default pluralization function
// renders a plural label for a value with decimal precision.
// e.g. "1.0 seconds" is never rendered as "1.0 second".
// Label types and pluralization function are configurable in the
// localeData extensions.
usePlural: true,
// useLeftUnits
// The text to the right of each moment token in a format string
// is treated as that token's units for the purposes of trimming,
// singularizing, and auto-localizing.
// e.g. "h [hours], m [minutes], s [seconds]".
// To properly singularize or localize a format string such as
// "[hours] h, [minutes] m, [seconds] s", where the units appear
// to the left of each moment token, set useLeftUnits to `true`.
// This plugin is not tested in the context of rtl text.
useLeftUnits: false,
// useGrouping
// Enables locale-based digit grouping in the formatted output. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
useGrouping: true,
// useSignificantDigits
// Treat the `precision` option as the maximum significant digits
// to be rendered. Precision must be a positive integer. Significant
// digits extend across unit types,
// e.g. "6 hours 37.5 minutes" represents 4 significant digits.
// Enabling this option causes token length to be ignored. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
useSignificantDigits: false,
// template
// The template string used to format the duration. May be a function
// or a string. Template functions are executed with the `this` binding
// of the settings object so that template strings may be dynamically
// generated based on the duration object (accessible via `this.duration`)
// or any of the other settings. Leading and trailing space, comma,
// period, and colon characters are trimmed from the resulting string.
template: defaultFormatTemplate,
// useToLocaleString
// Set this option to `false` to ignore the `toLocaleString` feature
// test and force the use of the `formatNumber` fallback function
// included in this plugin.
useToLocaleString: true,
// formatNumber fallback options.
// When `toLocaleString` is detected and passes the feature test, the
// following options will have no effect: `toLocaleString` will be used
// for formatting and the grouping separator, decimal separator, and
// integer digit grouping will be determined by the user locale.
// groupingSeparator
// The integer digit grouping separator used when using the fallback
// formatNumber function.
groupingSeparator: ",",
// decimalSeparator
// The decimal separator used when using the fallback formatNumber
// function.
decimalSeparator: ".",
// grouping
// The integer digit grouping used when using the fallback formatNumber
// function. Must be an array. The default value of `[3]` gives the
// standard 3-digit thousand/million/billion digit groupings for the
// "en" locale. Setting this option to `[3, 2]` would generate the
// thousand/lakh/crore digit groupings used in the "en-IN" locale.
grouping: [3]
};
context.updateLocale('en', engLocale);
}
// Run feature tests for `Number#toLocaleString`.
var toLocaleStringFormatter = function(number, locale, options) {
return number.toLocaleString(locale, options);
};
toLocaleStringWorks = toLocaleStringSupportsLocales() && featureTestFormatter(toLocaleStringFormatter);
toLocaleStringRoundingWorks = toLocaleStringWorks && featureTestFormatterRounding(toLocaleStringFormatter);
// Run feature tests for `Intl.NumberFormat#format`.
var intlNumberFormatFormatter = function(number, locale, options) {
if (typeof window !== 'undefined' && window && window.Intl && window.Intl.NumberFormat) {
return window.Intl.NumberFormat(locale, options).format(number);
}
};
intlNumberFormatWorks = featureTestFormatter(intlNumberFormatFormatter);
intlNumberFormatRoundingWorks = intlNumberFormatWorks && featureTestFormatterRounding(intlNumberFormatFormatter);
// Initialize duration format on the global moment instance.
init(moment);
// Return the init function so that duration format can be
// initialized on other moment instances.
return init;
});