26

How can I convert duration with JavaScript, for example:

PT16H30M

Basil Bourque
  • 262,936
  • 84
  • 758
  • 1,028
cvelinho
  • 466
  • 1
  • 4
  • 13
  • 4
    What have you tried? What do you want to convert it to? This may be a dupe of http://stackoverflow.com/questions/4829569/help-parsing-iso-8601-date-in-javascript – Dutts Feb 18 '13 at 10:30
  • How from this format get time 16:30; – cvelinho Feb 18 '13 at 10:36
  • 1
    There is a package for that: https://www.npmjs.com/package/iso8601-duration – str Aug 27 '18 at 09:01

7 Answers7

37

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:

  1. Sign
  2. Years
  3. Months
  4. Weeks
  5. Days
  6. Hours
  7. Minutes
  8. Seconds

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.


Update

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.

Community
  • 1
  • 1
crush
  • 16,427
  • 8
  • 57
  • 98
6
"PT16H30M".replace(/PT(\d+)H(\d+)M/, "$1:$2");
Bergi
  • 572,313
  • 128
  • 898
  • 1,281
5

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/ .

Sir hennihau
  • 923
  • 12
  • 21
5

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

See: https://www.npmjs.com/package/tinyduration

Melle
  • 6,511
  • 1
  • 26
  • 31
3

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);
}
  • 1
    this is cool, only bug is that when minutes are not provided, it outputs three zeros instead of two. – wiherek Jul 03 '14 at 13:47
  • 1
    Thank you, I didn't notice that. I did manage to fix this "by mistake" when rewriting the script. Updating now. – Miko Lukasik Jul 11 '14 at 18:47
2

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
Luke Stevenson
  • 10,282
  • 2
  • 25
  • 41
0

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.