30

I'm trying to convert ISO 8601 string to seconds in JS/Node. The best I could come up with was:

function convert_time(duration) {
    var a = duration.match(/\d+/g)
    var duration = 0

    if(a.length == 3) {
        duration = duration + parseInt(a[0]) * 3600;
        duration = duration + parseInt(a[1]) * 60;
        duration = duration + parseInt(a[2]);
    }

    if(a.length == 2) {
        duration = duration + parseInt(a[0]) * 60;
        duration = duration + parseInt(a[1]);
    }

    if(a.length == 1) {
        duration = duration + parseInt(a[0]);
    }
    return duration
}

It works when I input strings such as "PT48S", "PT3M20S" or "PT3H2M31S", but fails miserably if the string is "PT1H11S". Does anyone have a better idea?

Andrus Asumets
  • 539
  • 1
  • 6
  • 10

17 Answers17

31

If you're using moment.js you can simply call...

moment.duration('PT15M33S').asMilliseconds();

= 933000 ms

EDIT 2021: While this works, and still gets upvotes, I wouldn't advise including moment.js just for this. I'd recommend using a regex answer like @redgetan's

Mulhoon
  • 1,852
  • 21
  • 26
  • 1
    To avoid moment.js, you can try https://www.npmjs.com/package/duration-fns, and it works like so: `import * as duration from 'duration-fns'; duration.toSeconds('PT10H4M10S'); // 36250` – zenoh Oct 05 '21 at 23:31
30
function YTDurationToSeconds(duration) {
  var match = duration.match(/PT(\d+H)?(\d+M)?(\d+S)?/);

  match = match.slice(1).map(function(x) {
    if (x != null) {
        return x.replace(/\D/, '');
    }
  });

  var hours = (parseInt(match[0]) || 0);
  var minutes = (parseInt(match[1]) || 0);
  var seconds = (parseInt(match[2]) || 0);

  return hours * 3600 + minutes * 60 + seconds;
}

works for these cases:

PT1H
PT23M
PT45S
PT1H23M
PT1H45S
PT23M45S
PT1H23M45S
dillib
  • 359
  • 1
  • 2
  • 11
redgetan
  • 943
  • 1
  • 8
  • 12
24

I suggest this little hack to prevent your problematic case:

function convert_time(duration) {
    var a = duration.match(/\d+/g);

    if (duration.indexOf('M') >= 0 && duration.indexOf('H') == -1 && duration.indexOf('S') == -1) {
        a = [0, a[0], 0];
    }

    if (duration.indexOf('H') >= 0 && duration.indexOf('M') == -1) {
        a = [a[0], 0, a[1]];
    }
    if (duration.indexOf('H') >= 0 && duration.indexOf('M') == -1 && duration.indexOf('S') == -1) {
        a = [a[0], 0, 0];
    }

    duration = 0;

    if (a.length == 3) {
        duration = duration + parseInt(a[0]) * 3600;
        duration = duration + parseInt(a[1]) * 60;
        duration = duration + parseInt(a[2]);
    }

    if (a.length == 2) {
        duration = duration + parseInt(a[0]) * 60;
        duration = duration + parseInt(a[1]);
    }

    if (a.length == 1) {
        duration = duration + parseInt(a[0]);
    }
    return duration
}

Fiddle

Paul Rad
  • 4,820
  • 1
  • 22
  • 23
  • You've got an excellent hack. I'd suggest adding `code` if (duration.indexOf('H') >= 0 && duration.indexOf('M') == -1 && duration.indexOf('S') == -1) { a = [a[0], 0, 0] } `code` so it would work with "PT1H" as well – Andrus Asumets Mar 03 '14 at 14:49
  • Np :) updated post - if you want validate this answer for the support ;) – Paul Rad Mar 03 '14 at 15:12
  • 2
    This, I think, includes all possibilities: http://jsfiddle.net/andrusasumets/W4Bru/ – Andrus Asumets Mar 03 '14 at 16:00
  • @PaulRad How do I do it in Java.I am stuck with the regular expression part. – Ansh Jun 03 '14 at 07:16
  • 1
    This solution fails for cases like "PT1H22M". Just wrote up a solution that works correctly for all cases: https://gist.github.com/denniszhao/8972cd4ae637cf10fe01 – Dennis Apr 13 '15 at 20:56
  • This will solve all cases with essentially one line of code https://stackoverflow.com/a/54472422/1669091 – chad steele Feb 01 '19 at 03:28
11

Here's my solution:

function parseDuration(duration) {
    var matches = duration.match(/[0-9]+[HMS]/g);

    var seconds = 0;

    matches.forEach(function (part) {
        var unit = part.charAt(part.length-1);
        var amount = parseInt(part.slice(0,-1));

        switch (unit) {
            case 'H':
                seconds += amount*60*60;
                break;
            case 'M':
                seconds += amount*60;
                break;
            case 'S':
                seconds += amount;
                break;
            default:
                // noop
        }
    });

    return seconds;
}
Chris Z-S
  • 141
  • 2
  • 5
  • 1
    i used you answer and converted to PHP here : http://stackoverflow.com/questions/19562195/converting-youtube-data-api-v3-video-duration-format-to-standard-time-in-php/26178914#26178914 – Yahia Oct 03 '14 at 12:27
3

My solution:

function convert_time(duration) {
  var total = 0;
  var hours = duration.match(/(\d+)H/);
  var minutes = duration.match(/(\d+)M/);
  var seconds = duration.match(/(\d+)S/);
  if (hours) total += parseInt(hours[1]) * 3600;
  if (minutes) total += parseInt(minutes[1]) * 60;
  if (seconds) total += parseInt(seconds[1]);
  return total;
}

Fiddle

John
  • 375
  • 1
  • 17
3

You can find a very simple PHP solution here - How To Convert Youtube API Time (ISO 8601 String Video Duration) to Seconds In PHP - Code

This function convert_time() takes one parameter as input - the Youtube API Time (Video Duration) which is in ISO 8601 string format and returns its duration in seconds.

function convert_time($str) 
{
    $n = strlen($str);
    $ans = 0;
    $curr = 0;
    for($i=0; $i<$n; $i++)
    {
        if($str[$i] == 'P' || $str[$i] == 'T')
        {

        }
        else if($str[$i] == 'H')
        {
            $ans = $ans + 3600*$curr;
            $curr = 0;
        }
        else if($str[$i] == 'M')
        {
            $ans = $ans + 60*$curr;
            $curr = 0;
        }
        else if($str[$i] == 'S')
        {
            $ans = $ans + $curr;
            $curr = 0;
        }
        else
        {
            $curr = 10*$curr + $str[$i];
        }
    }
    return($ans);
}

Testing Some Inputs:

"PT2M23S" => 143
"PT2M" => 120
"PT28S" => 28
"PT5H22M31S" => 19351
"PT3H" => 10800
"PT1H6M" => 3660
"PT1H6S" => 3606
2

Here's @redgetan 's solution in ES6.

I also fixed it for years, weeks and days.

https://www.digi.com/resources/documentation/digidocs/90001437-13/reference/r_iso_8601_duration_format.htm

// Copied from:
// https://stackoverflow.com/questions/22148885/converting-youtube-data-api-v3-video-duration-format-to-seconds-in-javascript-no
function parseISO8601Duration(duration) {
    const match = duration.match(/P(\d+Y)?(\d+W)?(\d+D)?T(\d+H)?(\d+M)?(\d+S)?/)
    // An invalid case won't crash the app.
    if (!match) {
        console.error(`Invalid YouTube video duration: ${duration}`)
        return 0
    }
    const [
        years,
        weeks,
        days,
        hours,
        minutes,
        seconds
    ] = match.slice(1).map(_ => _ ? parseInt(_.replace(/\D/, '')) : 0)
  return (((years * 365 + weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds
}

if (parseISO8601Duration('PT1H') !== 3600) {
    throw new Error()
}

if (parseISO8601Duration('PT23M') !== 1380) {
    throw new Error()
}

if (parseISO8601Duration('PT45S') !== 45) {
    throw new Error()
}

if (parseISO8601Duration('PT1H23M') !== 4980) {
    throw new Error()
}

if (parseISO8601Duration('PT1H45S') !== 3645) {
    throw new Error()
}

if (parseISO8601Duration('PT1H23M45S') !== 5025) {
    throw new Error()
}

if (parseISO8601Duration('P43W5DT5M54S') !== 26438754) {
    throw new Error()
}

if (parseISO8601Duration('P1Y43W5DT5M54S') !== 57974754) {
    throw new Error()
}
catamphetamine
  • 4,489
  • 30
  • 25
1

I've written a CoffeeScript variation (you can easily compile it at coffeescript.org when desired)

DIFFERENCE: the returning duration comes in a human readable format (e.g. 04:20, 01:05:48)

String.prototype.parseDuration = ->
    m = @.match /[0-9]+[HMS]/g
    res = ""
    fS = fM = !1
    for part in m
        unit = part.slice -1
        val = part.slice 0, part.length - 1
        switch unit
            when "H" then res += val.zeros( 2 ) + ":"
            when "M"
                fM = 1
                res += val.zeros( 2 ) + ":"
            when "S"
                fS = 1
                res += if fM then val.zeros 2 else "00:" + val.zeros 2

     if !fS then res += "00"
     res

I've also implemented this helper function to fill < 10 values with a leading zero:

String.prototype.zeros = ( x ) ->
    len = @length
    if !x or len >= x then return @
    zeros = ""
    zeros += "0" for [0..(x-len-1)]
    zeros + @

3nj0y!!!

faebster
  • 727
  • 7
  • 13
1

I realize eval is unpopular, but here's the easiest and fastest approach I can imagine. Enjoy.

function formatDuration(x) {
   return eval(x.replace('PT','').replace('H','*3600+').replace('M','*60+').replace('S', '+').slice(0, -1));
}
chad steele
  • 828
  • 9
  • 13
  • I kinda doubt regex is faster, but here's a smaller code version return eval(x.replace(/[PTS]/, '').replace('H', '*3600+').replace('M', '*60+').slice(0,-1)); – chad steele Feb 01 '19 at 03:38
1

I think using moment.js will be an easier solution. But if someone is looking for a custom solution, here is a simple regex one for you:

var regex = /PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/;
var regex_result = regex.exec("PT1H11S"); //Can be anything like PT2M23S / PT2M / PT28S / PT5H22M31S / PT3H/ PT1H6M /PT1H6S
var hours = parseInt(regex_result[1] || 0);
var minutes = parseInt(regex_result[2] || 0);
var seconds = parseInt(regex_result[3] || 0);
var total_seconds = hours * 60 * 60 + minutes * 60 + seconds;
1

ES6:

const durationToSec = formatted =>
  formatted
    .match(/PT(?:(\d*)H)?(?:(\d*)M)?(?:(\d*)S)?/)
    .slice(1)
    .map(v => (!v ? 0 : v))
    .reverse()
    .reduce((acc, v, k) => (acc += v * 60 ** k), 0);
Marina
  • 855
  • 6
  • 4
  • 1
    Although this code might solve the problem, a good answer should also explain what it does and how it helps. – Suraj Kumar Mar 26 '20 at 12:39
  • It returns number of seconds as well as the solutions above. The difference is that this solution is more concise – Marina Mar 27 '20 at 13:37
0

I ran into issues with the above solution. I decided to write it as obtuse as possible. I also use my own "getIntValue" in place of parseInt for extra sanity.

Just thought other searching might appreciate the update.

Fiddle

    function convertYouTubeTimeFormatToSeconds(timeFormat) {

    if ( timeFormat === null || timeFormat.indexOf("PT") !== 0 ) {
        return 0;
    }

    // match the digits into an array
    // each set of digits into an item
    var digitArray      = timeFormat.match(/\d+/g);
    var totalSeconds    = 0;

    // only 1 value in array
    if (timeFormat.indexOf('H') > -1 && timeFormat.indexOf('M') == -1 && timeFormat.indexOf('S') == -1) {
        totalSeconds    += getIntValue(digitArray[0]) * 60 * 60;
    }

    else if (timeFormat.indexOf('H') == -1 && timeFormat.indexOf('M') > -1 && timeFormat.indexOf('S') == -1) {
        totalSeconds    += getIntValue(digitArray[0]) * 60;
    }

    else if (timeFormat.indexOf('H') == -1 && timeFormat.indexOf('M') == -1 && timeFormat.indexOf('S') > -1) {
        totalSeconds    += getIntValue(digitArray[0]);
    }


    // 2 values in array
    else if (timeFormat.indexOf('H') > -1 && timeFormat.indexOf('M') > -1 && timeFormat.indexOf('S') == -1) {
        totalSeconds    += getIntValue(digitArray[0]) * 60 * 60;
        totalSeconds    += getIntValue(digitArray[1]) * 60;
    }

    else if (timeFormat.indexOf('H') > -1 && timeFormat.indexOf('M') == -1 && timeFormat.indexOf('S') > -1) {
        totalSeconds    += getIntValue(digitArray[0]) * 60 * 60;
        totalSeconds    += getIntValue(digitArray[1]);
    }

    else if (timeFormat.indexOf('H') == -1 && timeFormat.indexOf('M') > -1 && timeFormat.indexOf('S') > -1) {
        totalSeconds    += getIntValue(digitArray[0]) * 60;
        totalSeconds    += getIntValue(digitArray[1]);
    }


    // all 3 values
    else if (timeFormat.indexOf('H') > -1 && timeFormat.indexOf('M') > -1 && timeFormat.indexOf('S') > -1) {
        totalSeconds    += getIntValue(digitArray[0]) * 60 * 60;
        totalSeconds    += getIntValue(digitArray[1]) * 60;
        totalSeconds    += getIntValue(digitArray[2]);
    }

//  console.log(timeFormat, totalSeconds);

    return totalSeconds;
}
function getIntValue(value) {
    if (value === null) {
        return 0;
    }

    else {

        var intValue = 0;
        try {
            intValue        = parseInt(value);
            if (isNaN(intValue)) {
                intValue    = 0;
            }
        } catch (ex) { }

        return Math.floor(intValue);
    }
}
bladnman
  • 2,591
  • 1
  • 25
  • 20
  • specify "with the above" solution... and let people know what the 'issue' was, don't just judge. my coffee variation works like a charm, and can be easily compiled to js.. I implemented it, because I needed another output format as provided in the first solution. thx ;) – faebster Dec 05 '14 at 17:58
0

Python

It works by parsing the input string 1 character at a time, if the character is numerical it simply adds it (string add, not mathematical add) to the current value being parsed. If it is one of 'wdhms' the current value is assigned to the appropriate variable (week, day, hour, minute, second), and value is then reset ready to take the next value. Finally it sum the number of seconds from the 5 parsed values.

def ytDurationToSeconds(duration): #eg P1W2DT6H21M32S
    week = 0
    day  = 0
    hour = 0
    min  = 0
    sec  = 0

    duration = duration.lower()

    value = ''
    for c in duration:
        if c.isdigit():
            value += c
            continue

        elif c == 'p':
            pass
        elif c == 't':
            pass
        elif c == 'w':
            week = int(value) * 604800
        elif c == 'd':
            day = int(value)  * 86400
        elif c == 'h':
            hour = int(value) * 3600
        elif c == 'm':
            min = int(value)  * 60
        elif c == 's':
            sec = int(value)

        value = ''

    return week + day + hour + min + sec
  • 1
    While this answer technically answers the question, it would be helpful for our future visitors to include what it is actually doing – Ferrybig Feb 02 '16 at 15:59
0

This is not java specific, but i would like to add JAVA snippet as that may helpful to other users

String duration = "PT1H23M45S";
Pattern pattern = Pattern.compile("PT(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?");
Matcher matcher = pattern.matcher(duration);
long sec = 0;
long min = 0;
long hour = 0;
if (matcher.find())
{
    if(matcher.group(1)!=null)
        hour = NumberUtils.toInt(matcher.group(1));
    if(matcher.group(2)!=null)
        min = NumberUtils.toInt(matcher.group(2));
    if(matcher.group(3)!=null)
        sec = NumberUtils.toInt(matcher.group(3));

}
long totalSec = (hour*3600)+(min*60)+sec;
System.out.println(totalSec);
Nirmal- thInk beYond
  • 11,847
  • 8
  • 35
  • 46
0

Assuming the input is valid, we can use the regex exec method to iterate on the string and extract the group sequentially:

const YOUTUBE_TIME_RE = /(\d+)([HMS])/g;
const YOUTUBE_TIME_UNITS = {
    'H': 3600,
    'M': 60,
    'S': 1
}

/**
 * Returns the # of seconds in a youtube time string
 */
function parseYoutubeDate(date: string): number {
    let ret = 0;
    let match: RegExpExecArray;
    while (match = YOUTUBE_TIME_RE.exec(date)) {
        ret += (YOUTUBE_TIME_UNITS[match[2]]) * Number(match[1]);
    }
    return ret;
}
dfl
  • 1,089
  • 8
  • 8
0

Kotlin version:

private val youtubeDurationPattern: Pattern =
    Pattern.compile("PT(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?")

fun String.parseDuration(): Int {
    val matcher: Matcher = youtubeDurationPattern.matcher(this)
    if (!matcher.find()) {
        throw IllegalStateException("Cannot parse $this.")
    }
    val hour = matcher.group(1)?.toInt() ?: 0
    val min = matcher.group(2)?.toInt() ?: 0
    val sec = matcher.group(3)?.toInt() ?: 0
    return hour * 3600 + min * 60 + sec
}

and test:

    @Test
    fun testParseDuration() {
        assertEquals(10 * 60, "PT10M".parseDuration())
        assertEquals(10 * 60 + 30, "PT10M30S".parseDuration())
        assertEquals(30, "PT30S".parseDuration())
        assertEquals(2 * 3600 + 3 * 60 + 16, "PT2H3M16S".parseDuration())
    }
Vojtěch
  • 11,312
  • 31
  • 103
  • 173
0

You can use the luxon library to parse ISO 8601 duration. Here is the code -

import { Duration } from "luxon";

const isoDurationString = 'PT1H30M'; 
const duration = Duration.fromISO(isoDurationString);
Meera Datey
  • 1,913
  • 14
  • 15