How can I convert duration with JavaScript, for example:
PT16H30M
How can I convert duration with JavaScript, for example:
PT16H30M
You could theoretically get an ISO8601 Duration that looks like the following:
P1Y4M3W2DT10H31M3.452S
I wrote the following regular expression to parse this into groups:
(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?
It's not pretty, and someone better versed in regular expressions might be able to write a better one.
The groups boil down into the following:
I wrote the following function to convert it into a nice object:
var iso8601DurationRegex = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
window.parseISO8601Duration = function (iso8601Duration) {
var matches = iso8601Duration.match(iso8601DurationRegex);
return {
sign: matches[1] === undefined ? '+' : '-',
years: matches[2] === undefined ? 0 : matches[2],
months: matches[3] === undefined ? 0 : matches[3],
weeks: matches[4] === undefined ? 0 : matches[4],
days: matches[5] === undefined ? 0 : matches[5],
hours: matches[6] === undefined ? 0 : matches[6],
minutes: matches[7] === undefined ? 0 : matches[7],
seconds: matches[8] === undefined ? 0 : matches[8]
};
};
Used like this:
window.parseISO8601Duration('P1Y4M3W2DT10H31M3.452S');
Hope this helps someone out there.
If you are using momentjs, they have ISO8601 duration parsing functionality available. You'll need a plugin to format it, and it doesn't seem to handle durations that have weeks specified in the period as of the writing of this note.
P1D
is a valid duration, so you should not expect the T
inside the regex. Here is a more valid regex : (-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?(?:T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?)?
–
Misnomer date-fns
that don't even consider this despite giving a conversion the other way around. As usual with JS, I end up having to add a function in my helpers
file while this should be a standard in all time related libraries. Thanks for that. –
Dempsey Moment.js released with version 2.3 a duration support.
const iso8601Duration = "PT16H30M"
moment.duration(iso8601Duration)
// -> { _data: { days: 0, hours: 16, milliseconds: 0, minutes: 30, months: 0, seconds: 0, years: 0} ...
moment.duration(iso8601Duration).asSeconds()
// -> 59400
Read more https://momentjs.com/docs/#/durations/ .
Wrapped up a small package to facilitate this:
import { parse, serialize } from 'tinyduration';
// Basic parsing
const durationObj = parse('P1Y2M3DT4H5M6S');
assert(durationObj, {
years: 1,
months: 2,
days: 3,
hours: 4,
minutes: 5,
seconds: 6
});
// Serialization
assert(serialize(durationObj), 'P1Y2M3DT4H5M6S');
Install using npm install --save tinyduration
or yarn add tinyduration
I have just done this for durations that are even over a year long.
Here is a fiddle.
function convertDuration(t){
//dividing period from time
var x = t.split('T'),
duration = '',
time = {},
period = {},
//just shortcuts
s = 'string',
v = 'variables',
l = 'letters',
// store the information about ISO8601 duration format and the divided strings
d = {
period: {
string: x[0].substring(1,x[0].length),
len: 4,
// years, months, weeks, days
letters: ['Y', 'M', 'W', 'D'],
variables: {}
},
time: {
string: x[1],
len: 3,
// hours, minutes, seconds
letters: ['H', 'M', 'S'],
variables: {}
}
};
//in case the duration is a multiple of one day
if (!d.time.string) {
d.time.string = '';
}
for (var i in d) {
var len = d[i].len;
for (var j = 0; j < len; j++) {
d[i][s] = d[i][s].split(d[i][l][j]);
if (d[i][s].length>1) {
d[i][v][d[i][l][j]] = parseInt(d[i][s][0], 10);
d[i][s] = d[i][s][1];
} else {
d[i][v][d[i][l][j]] = 0;
d[i][s] = d[i][s][0];
}
}
}
period = d.period.variables;
time = d.time.variables;
time.H += 24 * period.D +
24 * 7 * period.W +
24 * 7 * 4 * period.M +
24 * 7 * 4 * 12 * period.Y;
if (time.H) {
duration = time.H + ':';
if (time.M < 10) {
time.M = '0' + time.M;
}
}
if (time.S < 10) {
time.S = '0' + time.S;
}
duration += time.M + ':' + time.S;
alert(duration);
}
Specifically solving DateTime strings which can be used within the HTML5 <time/>
tags, as they are limited to Days, Minutes and Seconds (as only these can be converted to a precise number of seconds, as Months and Years can have varying durations)
function parseDurationString( durationString ){
var stringPattern = /^PT(?:(\d+)D)?(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d{1,3})?)S)?$/;
var stringParts = stringPattern.exec( durationString );
return (
(
(
( stringParts[1] === undefined ? 0 : stringParts[1]*1 ) /* Days */
* 24 + ( stringParts[2] === undefined ? 0 : stringParts[2]*1 ) /* Hours */
)
* 60 + ( stringParts[3] === undefined ? 0 : stringParts[3]*1 ) /* Minutes */
)
* 60 + ( stringParts[4] === undefined ? 0 : stringParts[4]*1 ) /* Seconds */
);
}
Test Data
"PT1D" returns 86400
"PT3H" returns 10800
"PT15M" returns 900
"PT1D12H30M" returns 131400
"PT1D3M15.23S" returns 86595.23
Alternatively, you can use the Duration.fromISO
method from the luxon library.
Here's an example of how to use it:
const { Duration } = require('luxon');
// Parse an ISO 8601 duration string
const duration = Duration.fromISO('P3Y6M4DT12H30M5S');
// Print the total number of seconds in the duration
console.log(duration.as('seconds'));
This will log the total number of seconds in the duration, which in this case would be '117536305'.
Note that the luxon library is part of Moment project btw.
Basic solution to ISO8601 period support.
Due to lack of a 'duration' type in JavaScript and weird date semantics, this uses date arithmetic to apply a 'period' to an 'anchor' date (defaults to current date and time). Default is to add the period.
Specify ago: true to provide a date in the past.
// Adds ISO8601 period: P<dateparts>(T<timeparts>)?
// E.g. period 1 year 3 months 2 days: P1Y3M2D
// E.g. period 1H: PT1H
// E.g. period 2 days 12 hours: P2DT12H
// @param period string: ISO8601 period string
// @param ago bool [optiona] true: Subtract the period, false: add (Default)
// @param anchor Date [optional] Anchor date for period, default is current date
function addIso8601Period(period /*:string */, ago /*: bool? */, anchor /*: Date? */) {
var re = /^P((?<y>\d+)Y)?((?<m>\d+)M)?((?<d>\d+)D)?(T((?<th>\d+)H)?((?<tm>\d+)M)?((?<ts>\d+(.\d+)?)S)?)?$/;
var match = re.exec(period);
var direction = ago || false ? -1 : 1;
anchor = new Date(anchor || new Date());
anchor.setFullYear(anchor.getFullYear() + (match.groups['y'] || 0) * direction);
anchor.setMonth(anchor.getMonth() + (match.groups['m'] || 0) * direction);
anchor.setDate(anchor.getDate() + (match.groups['d'] || 0) * direction);
anchor.setHours(anchor.getHours() + (match.groups['th'] || 0) * direction);
anchor.setMinutes(anchor.getMinutes() + (match.groups['tm'] || 0) * direction);
anchor.setSeconds(anchor.getSeconds() + (match.groups['ts'] || 0) * direction);
return anchor;
}
No warranty. This may have quirks - test for your use case.
This is an update of solution proposed by James Caradoc-Davies
const durationExp = /^(?<sign>\+|-)?P((?<Y>\d+)Y)?((?<M>\d+)M)?((?<D>\d+)D)?T((?<H>\d+)H)?((?<m>\d+)M)?((?<S>\d+(\.\d+))?S)?$/
/**
* # applyISODuration
* Apply ISO 8601 duration string to given date
* - **duration** must be a strng compliant with ISO 8601 duration
* /!\ it doesn't support the Week parameter but it can easily be replaced by adding a number of days
* - **date** can be omit and will then default to Date.now()
* It can be any valid value for Date constructor so you can pass date
* as strings or number as well as Date object
* returns a new Date with the duration applied
*/
const applyISODuration = (duration/*:string*/, date/*?: Date|string|number*/) => {
date = date ? new Date(date) : new Date()
const parts = duration.match(durationExp)?.groups
if (!parts) {
throw new Error(`Invalid duration(${duration})`)
}
const addOrSubstract = parts.sign === '-' ? (value) => -value : (value) => +value;
parts.Y && date.setFullYear(date.getFullYear() + addOrSubstract(parts.Y))
parts.M && date.setMonth(date.getMonth() + addOrSubstract(parts.M))
parts.D && date.setDate(date.getDate() + addOrSubstract(parts.D))
parts.H && date.setHours(date.getHours() + addOrSubstract(parts.H))
parts.m && date.setMinutes(date.getMinutes() + addOrSubstract(parts.m))
parts.s && date.setSeconds(date.getSeconds() + addOrSubstract(parseFloat(parts.S)))
return date
}
usage:
applyISODuration('P2DT12H5.555S')
applyISODuration('P2DT12H5.555S', new Date("2022-06-23T09:52:38.298Z") )
© 2022 - 2024 — McMap. All rights reserved.