/    Sign up×
Community /Pin to ProfileBookmark

Best way to convert iso_8601 duration?

Hi Guys,
I was wondering what the best approach is to converting into a readable format a date/time duration that looks like this:

P1DT13H22M15S

It’s in [URL=”http://en.wikipedia.org/wiki/ISO_8601″]iso_8601[/URL] format. Basically:

Any number between T and H is the number of hours
Any number between H and M is minutes
Any number between M and S is seconds
Any number between P and D is number of days

and

P followed by T means the duration doesn’t include a day (is less than a day).

Is there anything in PHP that will do this conversion automatically? Otherwise, is preg_match the best option?

Can anyone suggest a regex? ?

Thanks!

to post a comment
PHP

8 Comments(s)

Copy linkTweet thisAlerts:
@SodbusterJan 22.2009 — In the off-chance that you have v.5.3.0 installed, and even though you asked for a regex, here's a function:[code=php]$duration = 'P1DT13H22M15S';
$duration_array = duration_to_array($duration);
print_r($duration_array);

function interval_to_array($dur)
{
$di = new DateInterval($dur);
$dt = new DateTime('1970-01-01 00:00:00');
$str = date_add($dt, $di)->format('YmdHis');
$arr['years'] = substr($str, 0, 4) - 1970;
$arr['months'] = substr($str, 4, 2) - 1;
$days = substr($str, 6, 2) - 1;
$arr['weeks'] = floor($days / 7);
$arr['days'] = $days % 7;
$arr['hours'] = (int) substr($str, 8, 2);
$arr['minutes'] = (int) substr($str, 10, 2);
$arr['seconds'] = (int) substr($str, -2);
return $arr;
}[/code]
[CODE]Array
(
[years] => 0
[months] => 0
[weeks] => 0
[days] => 1
[hours] => 13
[minutes] => 22
[seconds] => 15
}[/CODE]
Copy linkTweet thisAlerts:
@NogDogJan 22.2009 — This seems to work:
[code=php]
<?php
/**
* Parse an ISO 8601 duration string
* @return array
* @param string $str
**/
function parseDuration($str)
{
$result = array();
preg_match('/^(?:P)([^T]*)(?:T)?(.*)?$/', trim($str), $sections);
if(!empty($sections[1]))
{
preg_match_all('/(d+)([YMWD])/', $sections[1], $parts, PREG_SET_ORDER);
$units = array('Y' => 'years', 'M' => 'months', 'W' => 'weeks', 'D' => 'days');
foreach($parts as $part)
{
$result[$units[$part[2]]] = $part[1];
}
}
if(!empty($sections[2]))
{
preg_match_all('/(d+)([HMS])/', $sections[2], $parts, PREG_SET_ORDER);
$units = array('H' => 'hours', 'M' => 'minutes', 'S' => 'seconds');
foreach($parts as $part)
{
$result[$units[$part[2]]] = $part[1];
}
}
return($result);
}
?>
[/code]
Copy linkTweet thisAlerts:
@SodbusterJan 22.2009 — This, too:[code=php]$interval = 'P1DT13H22M15S';
$array = interval_breakdown($interval);
print_r($array);

function interval_breakdown($int)
{
$tokens = array('T' => array('H' => 'hours', 'M' => 'minutes', 'S' => 'seconds'),
'P' => array('Y' => 'years', 'M' => 'months', 'W' => 'weeks', 'D' => 'days'));
$split = explode('T', $int);
foreach ($tokens as $tok => $per) {
if ($str = strrchr($int, $tok)) {
$time = str_replace($tok, '', $str);
foreach ($per as $key => $val) {
if (strpos($str, $key) !== false) {
$tmp = strtok($time, $key);
$ret[$val] = '';
for ($c = 0; $c < strlen($tmp); $c++) {
if (ctype_digit($tmp[$c])) {
$ret[$val] .= $tmp[$c];
}
}
$time = str_replace($ret[$val], '', $time);
}
}
$int = str_replace($str, '', $int);
}
}
return $ret;
}[/code]
Copy linkTweet thisAlerts:
@ricklauthorJan 22.2009 — awesome. thanks, guys! i'll be giving those a try.
Copy linkTweet thisAlerts:
@ricklauthorJan 22.2009 — I wanted to avoid using loops and conditional statements, so I tried this, which seems to work.

[CODE]
$patterns[0] = '/P/';
$patterns[1] = '/PT/';
$patterns[2] = '/T/';
$patterns[3] = '/D/';
$patterns[4] = '/H/';
$patterns[5] = '/M/';
$patterns[6] = '/S/';
$replacements[0] = '';
$replacements[1] = '';
$replacements[2] = '';
$replacements[3] = ' days ';
$replacements[4] = ' hours ';
$replacements[5] = ' minutes ';
$replacements[6] = ' seconds ';

$time_left = preg_replace($patterns, $replacements, $duration);
[/CODE]
Copy linkTweet thisAlerts:
@NogDogJan 22.2009 — I wanted to avoid using loops and conditional statements,...[/quote]
Why? (I'm not saying either way is better or worse, just that neither is inherently so.)
...so I tried this, which seems to work.
[CODE]
$patterns[0] = '/P/';
$patterns[1] = '/PT/';
$patterns[2] = '/T/';
$patterns[3] = '/D/';
$patterns[4] = '/H/';
$patterns[5] = '/M/';
$patterns[6] = '/S/';
$replacements[0] = '';
$replacements[1] = '';
$replacements[2] = '';
$replacements[3] = ' days ';
$replacements[4] = ' hours ';
$replacements[5] = ' minutes ';
$replacements[6] = ' seconds ';

$time_left = preg_replace($patterns, $replacements, $duration);
[/CODE]
[/QUOTE]


Does this mean you are sure there will never be year, month, and/or week values in the duration string? (That's part of the reason I went to the contortions I did in order to differentiate the "M" for months and the "M" for minutes.) But if you're sure the fields will always be the same and you're happy with the result, then it should be fine. As one of the tenets of XP says, "Do the simplest thing that works," (and re-factor later as needed).
Copy linkTweet thisAlerts:
@ricklauthorJan 23.2009 — Why? (I'm not saying either way is better or worse, just that neither is inherently so.)[/QUOTE]

I just thought that loops and conditionals would be more resource intensive (more processor cycles). Is that not the case?


Does this mean you are sure there will never be year, month, and/or week values in the duration string? (That's part of the reason I went to the contortions I did in order to differentiate the "M" for months and the "M" for minutes.) But if you're sure the fields will always be the same and you're happy with the result, then it should be fine. As one of the tenets of XP says, "Do the simplest thing that works," (and re-factor later as needed).[/QUOTE]


In the case of the data I'm dealing with, yes, there are no year, month or week values. I should have specified that. Sorry! ? I'm sure somebody else will find it useful though. That's the beauty of public forums!

Thanks again for the help!
Copy linkTweet thisAlerts:
@NogDogJan 23.2009 — A mature language like PHP is generally pretty good at optimizing things when it compiles your code, so common things like foreach loops may be more efficient than you think. While you call preg_replace() once, it will have to do its own internal looping to go through each element of your search array. Now whether that is faster or slower I have no idea at this point, and most likely whichever way is faster in this particular instance is only going to be so in terms of very small units of time (e.g. probably measured in microseconds, which are [i]really[/i] small ? ). So I'd go with whatever (a) works and (b) is easiest for you to deal with now and a year from now when you need to update the code for some reason.
×

Success!

Help @rickl spread the word by sharing this article on Twitter...

Tweet This
Sign in
Forgot password?
Sign in with TwitchSign in with GithubCreate Account
about: ({
version: 0.1.9 BETA 6.10,
whats_new: community page,
up_next: more Davinci•003 tasks,
coming_soon: events calendar,
social: @webDeveloperHQ
});

legal: ({
terms: of use,
privacy: policy
});
changelog: (
version: 0.1.9,
notes: added community page

version: 0.1.8,
notes: added Davinci•003

version: 0.1.7,
notes: upvote answers to bounties

version: 0.1.6,
notes: article editor refresh
)...
recent_tips: (
tipper: @meenaratha,
tipped: article
amount: 1000 SATS,

tipper: @meenaratha,
tipped: article
amount: 1000 SATS,

tipper: @AriseFacilitySolutions09,
tipped: article
amount: 1000 SATS,
)...