Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Format DateInterval as ISO8601

I am currently working on a php project and need to format a DateInterval as ISO8601 (something like this):

P5D

This format can be used to create DateTime and DateInterval objects, but I can't figure out a way to format a DateInterval into this format. Is there any? If not, what might be a lightweight solution to that?

like image 382
Zwirbelbart Avatar asked Nov 18 '15 17:11

Zwirbelbart


1 Answers

To expand on the solution provided by Dave and the solution provided by Guss.

A workaround to add the T portion of the duration of the ISO 8601 standard and not need to default as P0M, is to insert 2 additional checks after the P0Y replaced by Y.

  • Y0M replaced by Y
  • P0M replaced by P
  • change D0H to TD0H replaced by DT
  • remove redundant Y0M replaced by Y0M

This works because the interval format will always generate the same structure, without leading 0's and str_replace walks through each replacement in the arrays from left to right. As a result there is a possibility that we could receive P1YT or PT as a return value. Simply remove any trailing PT characters by using rtrim. Then substitute an empty value with a desired default value, as in the answer by Guss.

ISO 8601 Assertions: https://3v4l.org/c55kL

Compatible with PHP 5.3+

The use of the static keyword is to improve performance of repeated calls to the function, as the static variables do not lose their values after leaving the function scope

function date_interval_iso(DateInterval $interval, $default = 'PT0S') {
    static $f = array('M0S', 'H0M', 'DT0H', 'M0D', 'P0Y', 'Y0M', 'P0M');
    static $r = array('M', 'H', 'DT', 'M', 'P', 'Y', 'P');

    return rtrim(str_replace($f, $r, $interval->format('P%yY%mM%dDT%hH%iM%sS')), 'PT') ?: $default;
}

Test

To compare I created an array containing every possible duration combination (excluding microseconds) with a value of 1. Which can be viewed in the example link above.

$durations ['P1Y', /*...*/ 'P1Y1M1DT1H1M1S'];
$isos = array();
foreach ($durations as $duration) {
    $isos[] = date_interval_iso(new DateInterval($duration));
}

$diff = array_diff($durations, $isos);
if (!empty($diff)) {
    //output any differences
    var_dump($diff);
}

//test 0 duration DateInterval 
$date1 = new DateTime();
echo date_interval_iso($date1->diff($date1));

Result

PT0S

PHP 7.1+ with Microseconds https://3v4l.org/VFN7N

You can add microseconds easily, by including S0F replaced by S of the first array values and adding %fF to your DateInterval::format().

However F (microseconds) is not currently a supported interval specification for DateInterval::__construct() or the ISO 8601 standard.

NOTE: there was a bug in PHP <= 7.2.13 with DateTime::diff that prevents the proper DateInterval retrieval when the difference was less than one second.

function date_interval_iso(DateInterval $interval, string $default = 'PT0F') {
    static $f = ['S0F', 'M0S', 'H0M', 'DT0H', 'M0D', 'P0Y', 'Y0M', 'P0M'];
    static $r = ['S', 'M', 'H', 'DT', 'M', 'P', 'Y', 'P'];

    return rtrim(str_replace($f, $r, $interval->format('P%yY%mM%dDT%hH%iM%sS%fF')), 'PT') ?: $default;
}

Test

$date1 = new DateTimeImmutable();
$date2 = new DateTimeImmutable();

//test 0 duration DateInterval 
echo date_interval_iso($date1->diff($date1));

//test microseconds
echo date_interval_iso($date2->diff($date1));

Result

PT0F
PT21F
like image 50
Will B. Avatar answered Oct 13 '22 17:10

Will B.