将 DateInterval 格式化为 ISO8601
Format DateInterval as ISO8601
我目前正在做一个 php 项目,需要将 DateInterval 格式化为 ISO8601(类似这样):
P5D
此格式可用于创建 DateTime 和 DateInterval 对象,但我想不出将 DateInterval 格式化为此格式的方法。有没有?如果不是,什么是轻量级解决方案?
好吧,如果你在构建格式时查看格式规范:
Y years
M months
D days
W weeks. These get converted into days, so can not be combined with D.
H hours
M minutes
S seconds
然后看看你要做的事情(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。这样做的原因是 minutes
和 months
都由 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"));
关于@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
展开 and the 。
添加 ISO 8601 标准持续时间的 T
部分且不需要默认为 P0M
的解决方法是在 P0Y
替换后插入 2 个附加检查通过 Y
.
Y0M
替换为 Y
P0M
替换为 P
- 将
D0H
更改为TD0H
替换为DT
- 删除多余的
Y0M
替换为 Y0M
这是有效的,因为间隔格式将始终生成相同的结构,没有前导 0,并且 str_replace
从左到右遍历数组中的每个替换。
因此,我们有可能收到 P1YT
或 PT
作为 return 值。只需使用 rtrim
删除任何尾随 PT
字符。然后用所需的默认值替换空值,如 Guss 的回答。
ISO 8601 声明:https://3v4l.org/c55kL
兼容PHP 5.3+
使用static keyword是为了提高重复调用函数的性能,因为静态变量在离开函数范围后不会丢失它们的值
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;
}
测试
为了比较,我创建了一个数组,其中包含值为 1 的所有可能的持续时间组合(不包括微秒)。可以在上面的示例 link 中查看。
$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));
结果
PT0S
PHP 7.1+ 微秒 https://3v4l.org/VFN7N
您可以轻松添加微秒,方法是将 S0F
替换为第一个数组值的 S
并将 %fF
添加到您的 DateInterval::format()
.
但是 F
(微秒)目前不是 DateInterval::__construct()
或 ISO 8601 标准支持的间隔规范。
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;
}
测试
$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));
结果
PT0F
PT21F
我目前正在做一个 php 项目,需要将 DateInterval 格式化为 ISO8601(类似这样):
P5D
此格式可用于创建 DateTime 和 DateInterval 对象,但我想不出将 DateInterval 格式化为此格式的方法。有没有?如果不是,什么是轻量级解决方案?
好吧,如果你在构建格式时查看格式规范:
Y years
M months
D days
W weeks. These get converted into days, so can not be combined with D.
H hours
M minutes
S seconds
然后看看你要做的事情(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。这样做的原因是 minutes
和 months
都由 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"));
关于@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
展开
添加 ISO 8601 标准持续时间的 T
部分且不需要默认为 P0M
的解决方法是在 P0Y
替换后插入 2 个附加检查通过 Y
.
Y0M
替换为Y
P0M
替换为P
- 将
D0H
更改为TD0H
替换为DT
- 删除多余的
Y0M
替换为Y0M
这是有效的,因为间隔格式将始终生成相同的结构,没有前导 0,并且 str_replace
从左到右遍历数组中的每个替换。
因此,我们有可能收到 P1YT
或 PT
作为 return 值。只需使用 rtrim
删除任何尾随 PT
字符。然后用所需的默认值替换空值,如 Guss 的回答。
ISO 8601 声明:https://3v4l.org/c55kL
兼容PHP 5.3+
使用static keyword是为了提高重复调用函数的性能,因为静态变量在离开函数范围后不会丢失它们的值
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;
}
测试
为了比较,我创建了一个数组,其中包含值为 1 的所有可能的持续时间组合(不包括微秒)。可以在上面的示例 link 中查看。
$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));
结果
PT0S
PHP 7.1+ 微秒 https://3v4l.org/VFN7N
您可以轻松添加微秒,方法是将 S0F
替换为第一个数组值的 S
并将 %fF
添加到您的 DateInterval::format()
.
但是 F
(微秒)目前不是 DateInterval::__construct()
或 ISO 8601 标准支持的间隔规范。
NOTE: there was a bug in PHP <= 7.2.13 with
DateTime::diff
that prevents the properDateInterval
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;
}
测试
$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));
结果
PT0F
PT21F