将DateInterval格式化为ISO8601

时间:2015-11-18 17:58:30

标签: php datetime iso8601 dateinterval

我目前正在开发一个php项目,需要将DateInterval格式化为ISO8601(类似这样):

P5D

此格式可用于创建DateTime和DateInterval对象,但我无法找到将DateInterval格式化为此格式的方法。有没有?如果没有,那可能是一个轻量级的解决方案呢?

3 个答案:

答案 0 :(得分:4)

solution provided by Davesolution provided by Guss上进行扩展。

一种解决方法是在ISO 8601标准有效期内添加T部分,而无需默认为P0M,它是在P0Y之后插入{{ {1}}。

  • Y替换为Y0M
  • Y替换为P0M
  • P更改为D0H,由TD0H
  • 删除由DT取代的冗余Y0M

之所以可行,是因为间隔格式将始终生成相同的结构,而不会导致前导0,并且Y0M从左到右遍历数组中的每个替换项。 结果,我们可能会收到str_replaceP1YT作为返回值。只需使用PT删除所有结尾的PT字符。然后,将空值替换为所需的默认值,如Guss的答案。

ISO 8601断言:https://3v4l.org/c55kL

与PHP 5.3+兼容

使用static keyword是为了提高重复调用函数的性能,因为静态变量在离开函数范围后不会丢失其值

rtrim

测试

为进行比较,我创建了一个数组,其中包含每个可能的持续时间组合(微秒除外),其值为1。可以在上面的示例链接中查看。

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;
}

结果

$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));

具有微秒https://3v4l.org/VFN7N的PHP 7.1 +

通过将PT0S 替换为第一个数组值的S0F,并将S添加到%fF,可以轻松地增加微秒。

但是DateInterval::format()或ISO 8601标准目前不支持F(微秒)。

注意:有一个bug in PHP <= 7.2.13 with DateTime::diff 当差异小于一秒时,将阻止正确的DateInterval::__construct()检索。

DateInterval

测试

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;
}

结果

$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));

答案 1 :(得分:2)

好吧,如果您在构建格式时查看格式规范:

  

Y年

     

M个月

     

D天

     W周。这些转换为天,因此不能与D组合。

     

H小时

     

M分钟

     

秒秒

然后看看你必须使用什么(http://php.net/manual/en/dateinterval.format.php),看起来你会做的是:

$dateInterval = new DateInterval( /* whatever */ );
$format = $dateInterval->format("P%yY%mM%dD%hH%iM%sS");
//P0Y0M5D0H0M0S
//now, we need to remove anything that is a zero, but make sure to not remove
//something like 10D or 20D
$format = str_replace(["M0S", "H0M", "D0H", "M0D", "Y0M", "P0Y"], ["M", "H", "D", "M", "Y0M", "P"], $format);
echo $format;
//P0M5D

现在,我做的不同之处是我总是包括月份,即使它是0.这样做的原因是minutesmonths都是由M代表 - 如果我们总是包括月份,那么如果有一分钟,我们知道它是分钟。否则,我们必须做一堆逻辑,看看是否需要将P更改为PT,以便它知道此实例中的M代表Minute

例如:

// For 3 Months
new DateInterval("P3M");
// For 3 Minutes
new DateInterval("PT3M"));

但我们做了:

// For 3 Months
new DateInterval("P3M");
// For 3 Minutes
new DateInterval("P0M3M"));

答案 2 :(得分:2)

关于@dave,我以解决一些问题的方式重新实现了他的解决方案,特别是要求总是留下一个月的字段。从Wikipedia article on ISO-8601来看,T指定似乎是可选的,即使它完全没有从上面提到的实现中删除。通过介绍它,我们可以解决我们的大多数问题并制作一些更清晰的代码:

function date_interval_iso_format(DateInterval $interval) {
    list($date,$time) = explode("T",$interval->format("P%yY%mM%dDT%hH%iM%sS"));
    // now, we need to remove anything that is a zero, but make sure to not remove
    // something like 10D or 20D
    $res =
        str_replace([ 'M0D', 'Y0M', 'P0Y' ], [ 'M', 'Y', 'P' ], $date) .
        rtrim(str_replace([ 'M0S', 'H0M', 'T0H'], [ 'M', 'H', 'T' ], "T$time"),"T");
    if ($res == 'P') // edge case - if we remove everything, DateInterval will hate us later
        return 'PT0S';
    return $res;
}

请注意,如果不需要,我们会删除T,因此现在M的两种用法都正常工作:

"P5M" == date_interval_iso_format(new DateInterval("P5M")); // => true
"PT5M" == date_interval_iso_format(new DateInterval("PT5M")); // => true