質問

There are plenty of questions asking how to do this the other way (converting from this format), but I can't find anything on how to output in the ISO 8601 duration format in PHP.

So I have a heap of duration strings in human readable format - I want to convert them into the ISO 8601 format on the fly to print the durations for HTML5 microdata. Below is a sample of some of the strings coming in, and how they should be formatted

"1 hour 30 minutes" --> PT1H30M
"5 minutes" --> PT5M
"2 hours" --> PT2H

I can push the string into an interval object in PHP:

date_interval_create_from_date_string("1 hour 30 minutes");

but there doesn't seem to be a ISO 8601 output option

How should I approach this?

役に立ちましたか?

解決

I'd convert it to a number first, then work with that.

First, use strtotime():

$time = strtotime("1 hour 30 minutes", 0);

Then you can parse it for duration, and output in PnYnMnDTnHnMnS format. I'd use the following method (inspired by http://csl.sublevel3.org/php-secs-to-human-text/):

function time_to_iso8601_duration($time) {
    $units = array(
        "Y" => 365*24*3600,
        "D" =>     24*3600,
        "H" =>        3600,
        "M" =>          60,
        "S" =>           1,
    );

    $str = "P";
    $istime = false;

    foreach ($units as $unitName => &$unit) {
        $quot  = intval($time / $unit);
        $time -= $quot * $unit;
        $unit  = $quot;
        if ($unit > 0) {
            if (!$istime && in_array($unitName, array("H", "M", "S"))) { // There may be a better way to do this
                $str .= "T";
                $istime = true;
            }
            $str .= strval($unit) . $unitName;
        }
    }

    return $str;
}

The result: http://codepad.org/1fHNlB6e

他のヒント

Here is a simplified version of Eric's time_to_iso8601_duration() function. It doesn't loose precision (365 days approximation of a year) and is about 5x faster. The output is less pretty but still ISO 8601 compatible according to this page.

function iso8601_duration($seconds)
{
    $days = floor($seconds / 86400);
    $seconds = $seconds % 86400;

    $hours = floor($seconds / 3600);
    $seconds = $seconds % 3600;

    $minutes = floor($seconds / 60);
    $seconds = $seconds % 60;

    return sprintf('P%dDT%dH%dM%dS', $days, $hours, $minutes, $seconds);
}

Another approach is to write function based on the DateInterval object. The ISO 8601 duration format is well described.

The advantage of this approach is that it only outputs the relevant (present) representations and it supports empty durations (commonly denoted as 'PT0S' or 'P0D').

function dateIntervalToISO860Duration(\DateInterval $d) {
    $duration = 'P';
    if (!empty($d->y)) {
        $duration .= "{$d->y}Y";
    }
    if (!empty($d->m)) {
        $duration .= "{$d->m}M";
    }
    if (!empty($d->d)) {
        $duration .= "{$d->d}D";
    }
    if (!empty($d->h) || !empty($d->i) || !empty($d->s)) {
        $duration .= 'T';
        if (!empty($d->h)) {
            $duration .= "{$d->h}H";
        }
        if (!empty($d->i)) {
            $duration .= "{$d->i}M";
        }
        if (!empty($d->s)) {
            $duration .= "{$d->s}S";
        }
    }
    if ($duration === 'P') {
        $duration = 'PT0S'; // Empty duration (zero seconds)
    }
    return $duration;
}

An example:

echo dateIntervalToISO860Duration(
    date_diff(
        new DateTime('2017-01-25 18:30:22'), 
        new DateTime('2019-03-11 07:12:17')
    )
);

Output: P2Y1M13DT12H41M55S

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top