Format plugin for the Moment Duration object.
This is a plugin to the Moment.js JavaScript date library to add comprehensive formatting to Moment Durations.
Format template grammar is patterned on the existing Moment Date format template grammar, with a few modifications because durations are fundamentally different from dates.
This plugin does not have any dependencies beyond Moment.js itself, and may be used in the browser and in Node.js.
Where it is available and functional, this plugin uses either Intl.NumberFormat#format
or Number#toLocaleString
to render formatted numerical output. Unfortunately, many environments do not fully implement the full suite of options in their respective specs, and some provide a buggy implementation.
This plugin runs a feature test for each formatter, and will revert to a fallback function to render formatted numerical output if the feature test fails. To force this plugin to always use the fallback number format function, set useToLocaleString
to false
. The fallback number format function output can be localized using options detailed at the bottom of this page. You should, in general, specify the fallback number formatting options if the default "en"
locale formatting would be unacceptable on some devices or in some environments.
This plugin is tested using BrowserStack on a range of Android devices with OS versions from 2.2 to 7, and on a range of iOS devices with OS versions from 4.3 to 11. Also tested on Chrome, Firefox, IE 8-11, and Edge browsers.
Please raise an issue if you notice formatting issues or anomalies in any environment!
A few items remain to finish off Version 2:
Add type definitions to support TypeScript, publish NuGet package, and support whatever other packaging options are in use these days.
Testing of the plugin should be modernized, ideally to match the Moment.js testing setup.
Having implemented version 2 of the moment-duration-format plugin, there are some obvious improvements for a version 3.
The ideas below are logged as issues and tagged with the 3.0.0 milestone. If you have ideas or comments about what you'd like to see, please log an issue on this project!
The fallback number formatting localization options should be included with the Moment Locale object extensions this plugin already adds for localizing duration unit labels. This would put all of the localization configuration in one place.
moment-duration-format and its fallback number formatting function do not follow the same API as Number#toLocaleString
for significant digits and faction digits. The fallback function should be updated to use the toLocaleString
API, and the plugin should expose the API options directly rather than hiding some of the options and masking them behind precision
and useSignificantDigits
options.
Exposing the fallback number formatting function as well as the formatter feature test function would facilitate testing and allow them to be used outside of the context of formatting durations.
The plugin depends on moment.js, which is not specified as a package dependency in the currently published version.
Node.js
npm install moment-duration-format
Bower
bower install moment-duration-format
Browser
<script src="path/to/moment-duration-format.js"></script>
This plugin will always try to install itself on the root.moment
instance, if it exists.
This plugin will install its setup function to root.momentDurationFormatSetup
so that it may be later called on any moment instance.
When using this plugin in the browser, if you do not include moment.js on your page first, you need to manually call window.momentDurationFormatSetup
on your moment instance once it is created.
To use this plugin as a module, use the require
function.
var moment = require("moment");
var momentDurationFormatSetup = require("moment-duration-format");
The plugin exports the init function so that duration format can be initialized on other moment instances.
To use this plugin with any other moment.js package, for example moment-timezone
, manually call the exported setup function to install the plugin into the desired package.
var moment = require("moment-timezone");
var momentDurationFormatSetup = require("moment-duration-format");
momentDurationFormatSetup(moment);
typeof moment.duration.fn.format === "function";
// true
typeof moment.duration.format === "function";
// true
moment.duration.fn.format
The duration.fn.format
method can format any moment duration. If no template or other arguments are provided, the default template function will generate a template string based on the duration's value.
moment.duration(123, "minutes").format();
// "2:03:00"
moment.duration(123, "months").format();
// "10 years, 3 months"
The duration format method may be called with three optional arguments, and returns a formatted string.
moment.duration(value, units).format([template] [, precision] [, settings])
// formattedString
moment.duration.format
The duration.format
method allows coordinated formatting of multiple moment durations at once. This function accepts an array of durations as its first argument, then the same three optional arguments as the duration.fn.format
function. This function returns an array of formatted strings.
moment.duration.format(durationsArray, [template] [, precision] [, settings]);
// formattedStringsArray
All of the options that are available to the single duration format function can be used with the multiple duration format function. A single settings object is used to format each of the individual durations.
moment.duration.format([
moment.duration(1, "second"),
moment.duration(1, "minute"),
moment.duration(1, "hour")
], "d [days] hh:mm:ss");
// ["0:00:01", "0:01:00", "1:00:00"]
Invalid durations are treated as having a value of 0
for formatting.
var invalidDuration = moment.duration(NaN, "second");
invalidDuration.isValid();
// false
invalidDuration.format();
// "0 seconds"
template
(string|function) is the string used to create the formatted output, or a function that returns the string to be used as the format template.
moment.duration(123, "minutes").format("h:mm");
// "2:03"
The template string is parsed for moment token characters, which are replaced with the duration's value for each unit type. The moment tokens are:
years: Y or y
months: M
weeks: W or w
days: D or d
hours: H or h
minutes: m
seconds: s
ms: S
Escape token characters within the template string using square brackets.
moment.duration(123, "minutes").format("h [hrs], m [min]");
// "2 hrs, 3 mins"
For some time duration formats, a zero-padded value is required. Use multiple token characters together to create the correct amount of padding.
moment.duration(3661, "seconds").format("h:mm:ss");
// "1:01:01"
moment.duration(15, "seconds").format("sss [s]");
// "015 s"
When the format template is trimmed, token length on the largest-magnitude rendered token can be trimmed as well. See sections trim and forceLength below for more details.
moment.duration(123, "seconds").format("h:mm:ss");
// "2:03"
Token length of 2
for milliseconds is a special case, most likely used to render milliseconds as part of a timer output, such as mm:ss:SS
. In this case, the milliseconds value is padded to three digits then truncated from the left to render a two digit output.
moment.duration(9, "milliseconds").format("mm:ss:SS", {
trim: false
});
// "00:00:00"
moment.duration(10, "milliseconds").format("mm:ss:SS", {
trim: false
});
// "00:00:01"
moment.duration(999, "milliseconds").format("mm:ss:SS", {
trim: false
});
// "00:00:99"
moment.duration(1011, "milliseconds").format("mm:ss:SS", {
trim: false
});
// "00:01:01"
Tokens can appear multiple times in the format template, but all instances must share the same length. If they do not, all instances will be rendered at the length of the first token of that type.
moment.duration(15, "seconds").format("ssss sss ss s");
// "0015 0015 0015 0015"
moment.duration(15, "seconds").format("s ss sss ssss");
// "15 15 15 15"
The default template function attempts to format a duration based on its magnitude. The larger the duration value, the larger the units of the formatted output will be.
For some duration values, the default template function will default trim
to "both"
if that option is not set in the settings object (more on that below).
The default template function uses auto-localized unit labels (more on that below, also).
moment.duration(100, "milliseconds").format();
// "100 milliseconds"
moment.duration(100, "seconds").format();
// "1:40"
moment.duration(100, "days").format();
// "3 months, 9 days"
moment.duration(100, "weeks").format();
// "1 year, 10 months, 30 days"
moment.duration(100, "months").format();
// "8 years, 4 months"
Use a custom template function if you need runtime control over the template string. Template functions are executed with a this
binding of the settings object, and have access to the underlying duration object via this.duration
. Any of the settings may be accessed or modified by the template function.
This custom template function uses a different template based on the value of the duration:
function customTemplate() {
return this.duration.asSeconds() >= 86400 ? "w [weeks], d [days]" : "hh:mm:ss";
}
moment.duration(65, 'seconds').format(customTemplate, {
trim: false
});
// "00:01:05"
moment.duration(1347840, 'seconds').format(customTemplate, {
trim: false
});
// "2 weeks, 2 days"
To ensure user-friendly formatted output, punctuation characters are trimmed from the beginning and end of the formatted output. Specifically, leading and trailing period .
, comma ,
, colon :
, and space
characters are removed.
precision
(number) defines the number of decimal fraction or integer digits to display for the final value.
The default precision value is 0
.
moment.duration(123, "minutes").format("h [hrs]");
// "2 hrs"
Positive precision defines the number of decimal fraction digits to display.
moment.duration(123, "minutes").format("h [hrs]", 2);
// "2.05 hrs"
Negative precision defines the number of integer digits to truncate to zero.
moment.duration(223, "minutes").format("m [min]", -2);
// "200 mins"
settings
is an object that can override any of the default moment duration format options.
Both the template
and precision
arguments may be specified as properties of a single settings
object argument, or they may be passed separately along with an optional settings object.
moment.duration(123, "minutes").format({
template: "h [hrs]",
precision: 2
});
// "2.05 hrs"
The default trim
behaviour is "large"
.
Largest-magnitude tokens are automatically trimmed when they have no value.
moment.duration(123, "minutes").format("d[d] h:mm:ss");
// "2:03:00"
Trimming also functions when the format string is oriented with token magnitude increasing from left to right.
moment.duration(123, "minutes").format("s [seconds], m [minutes], h [hours], d [days]");
// "0 seconds, 3 minutes, 2 hours"
To stop trimming altogether, set { trim: false }
.
moment.duration(123, "minutes").format("d[d] h:mm:ss", {
trim: false
});
// "0d 2:03:00"
When formatting multiple durations using moment.duration.format
, trimming for all of the durations is coordinated on the union of the set of durations.
moment.duration.format([
moment.duration(1, "minute"),
moment.duration(1, "hour"),
moment.duration(1, "day")
], "y [years], w [weeks], d [days], h [hours], m [minutes]");
// [
// "0 days, 0 hours, 1 minute",
// "0 days, 1 hour, 0 minutes",
// "1 day, 0 hours, 0 minutes"
// ]
trim
can be a string, a delimited list of strings, an array of strings, or a boolean. Accepted values are as follows:
"large"
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. This is the default trim
value.
moment.duration(123, "minutes").format("d[d] h:mm:ss");
// "2:03:00"
moment.duration(123, "minutes").format("d[d] h:mm:ss", {
trim: "large"
});
// "2:03:00"
moment.duration(0, "minutes").format("d[d] h:mm:ss", {
trim: "large"
});
// "0"
"small"
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.
moment.duration(123, "minutes").format("d[d] h:mm:ss", {
trim: "small"
});
// "0d 2:03"
moment.duration(0, "minutes").format("d[d] h:mm:ss", {
trim: "small"
});
// "0d"
"both"
Execute "large"
trim then "small"
trim.
moment.duration(123, "minutes").format("d[d] h[h] m[m] s[s]", {
trim: "both"
});
// "2h 3m"
moment.duration(0, "minutes").format("d[d] h[h] m[m] s[s]", {
trim: "both"
});
// "0s"
"mid"
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"
.
moment.duration(1441, "minutes").format("w[w] d[d] h[h] m[m] s[s]", {
trim: "mid"
});
// "0w 1d 1m 0s"
moment.duration(1441, "minutes").format("w[w] d[d] h[h] m[m] s[s]", {
trim: "large mid"
});
// "1d 1m 0s"
moment.duration(1441, "minutes").format("w[w] d[d] h[h] m[m] s[s]", {
trim: "small mid"
});
// "0w 1d 1m"
moment.duration(1441, "minutes").format("w[w] d[d] h[h] m[m] s[s]", {
trim: "both mid"
});
// "1d 1m"
moment.duration(0, "minutes").format("w[w] d[d] h[h] m[m] s[s]", {
trim: "both mid"
});
// "0s"
"final"
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"
.
moment.duration(0, "minutes").format("d[d] h:mm:ss", {
trim: "large final"
});
// ""
moment.duration(0, "minutes").format("d[d] h:mm:ss", {
trim: "small final"
});
// ""
moment.duration(0, "minutes").format("d[d] h[h] m[m] s[s]", {
trim: "both final"
});
// ""
"all"
Trim all zero-value tokens. Shorthand for "both mid final"
.
moment.duration(0, "minutes").format("d[d] h[h] m[m] s[s]", {
trim: "all"
});
// ""
"left"
Maps to "large"
to support this plugin's version 1 API.
"right"
Maps to "large"
to support this plugin's version 1 API.
true
Maps to "large"
.
null
Maps to "large"
.
false
Disables trimming.
Set largest
to a positive integer to output only the n
largest-magnitude moment tokens, starting with the largest-magnitude token that has a value.
Using the largest
option defaults trim
to "all"
.
moment.duration(7322, "seconds").format("d [days], h [hours], m [minutes], s [seconds]", {
largest: 2
});
// "2 hours, 2 minutes"
moment.duration(1216800, "seconds").format("y [years], w [weeks], d [days], h [hours], m [minutes], s [seconds]", {
largest: 3
});
// "2 weeks, 2 hours"
Setting trim
to a different value, or using stopTrim
can change the starting token as well as the remaining output.
moment.duration(1216800, "seconds").format("y [years], w [weeks], d [days], h [hours], m [minutes], s [seconds]", {
largest: 3,
trim: "both"
});
// "2 weeks, 0 days, 2 hours"
moment.duration(1216800, "seconds").format("y [years], w [weeks], d [days], h [hours], m [minutes], s [seconds]", {
largest: 3,
trim: "both",
stopTrim: "m"
});
// "2 weeks, 0 days, 2 hours"
moment.duration(1216800, "seconds").format("y [years], w [weeks], d [days], h [hours], m [minutes], s [seconds]", {
largest: 4,
trim: false