1654 lines
56 KiB
JavaScript
1654 lines
56 KiB
JavaScript
|
/*! Based on 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
|
||
|
*/
|
||
|
|
||
|
module.exports = 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;
|
||
|
|
||
|
// 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("");
|
||
|
}
|
||
|
|
||
|
// 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 `toLocaleString` for formatting.
|
||
|
// `userLocale` option is passed through to `toLocaleString`.
|
||
|
// `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 (!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 featureTestToLocaleStringRounding() {
|
||
|
return (3.55).toLocaleString("en", {
|
||
|
useGrouping: false,
|
||
|
minimumIntegerDigits: 1,
|
||
|
minimumFractionDigits: 1,
|
||
|
maximumFractionDigits: 1
|
||
|
}) === "3.6";
|
||
|
}
|
||
|
|
||
|
function featureTestToLocaleString() {
|
||
|
var passed = true;
|
||
|
|
||
|
// Test locale.
|
||
|
passed = passed && toLocaleStringSupportsLocales();
|
||
|
if (!passed) { return false; }
|
||
|
|
||
|
// Test minimumIntegerDigits.
|
||
|
passed = passed && (1).toLocaleString("en", { minimumIntegerDigits: 1 }) === "1";
|
||
|
passed = passed && (1).toLocaleString("en", { minimumIntegerDigits: 2 }) === "01";
|
||
|
passed = passed && (1).toLocaleString("en", { minimumIntegerDigits: 3 }) === "001";
|
||
|
if (!passed) { return false; }
|
||
|
|
||
|
// Test maximumFractionDigits and minimumFractionDigits.
|
||
|
passed = passed && (99.99).toLocaleString("en", { maximumFractionDigits: 0, minimumFractionDigits: 0 }) === "100";
|
||
|
passed = passed && (99.99).toLocaleString("en", { maximumFractionDigits: 1, minimumFractionDigits: 1 }) === "100.0";
|
||
|
passed = passed && (99.99).toLocaleString("en", { maximumFractionDigits: 2, minimumFractionDigits: 2 }) === "99.99";
|
||
|
passed = passed && (99.99).toLocaleString("en", { maximumFractionDigits: 3, minimumFractionDigits: 3 }) === "99.990";
|
||
|
if (!passed) { return false; }
|
||
|
|
||
|
// Test maximumSignificantDigits.
|
||
|
passed = passed && (99.99).toLocaleString("en", { maximumSignificantDigits: 1 }) === "100";
|
||
|
passed = passed && (99.99).toLocaleString("en", { maximumSignificantDigits: 2 }) === "100";
|
||
|
passed = passed && (99.99).toLocaleString("en", { maximumSignificantDigits: 3 }) === "100";
|
||
|
passed = passed && (99.99).toLocaleString("en", { maximumSignificantDigits: 4 }) === "99.99";
|
||
|
passed = passed && (99.99).toLocaleString("en", { maximumSignificantDigits: 5 }) === "99.99";
|
||
|
if (!passed) { return false; }
|
||
|
|
||
|
// Test grouping.
|
||
|
passed = passed && (1000).toLocaleString("en", { useGrouping: true }) === "1,000";
|
||
|
passed = passed && (1000).toLocaleString("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;
|
||
|
|
||
|
// 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`.
|
||
|
toLocaleStringWorks = featureTestToLocaleString();
|
||
|
toLocaleStringRoundingWorks = toLocaleStringWorks && featureTestToLocaleStringRounding();
|
||
|
|
||
|
// 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;
|
||
|
};
|