选择DateTime夏令时重叠期间的首选偏移量



在观测夏令时的时区中,时钟通常为:

  • 在从冬季夏季的过渡期间向前移动
  • 在从夏季冬季的过渡过程中倒退

例如,在Europe/Paris时区中,UTC偏移量在10月最后一个星期日的3:00 AM夏季转换到冬季期间从+02:00更改为+01:00

换句话说:

2014-10-26上的3:00 AM+02:00)处,时钟被设置回2:00 AM+01:00)。

这意味着在Europe/Paris时区的02:30 AM2014-10-26创建DateTime是不明确的,因为它可以表示:

  • 2014-10-26T02:30+01:00(时间戳1414287000
  • 2014-10-26T02:30+02:00(时间戳1414283400

Java的ZonedDateTime文档很好地解释了这个问题,并且他们的API提供了一种在需要时选择首选偏移的方法。

然而,在PHP中,这种模糊性似乎是通过任意选择冬季时间来解决的:

$dt = new DateTime('2014-10-26T02:30', new DateTimeZone('Europe/Paris'));
echo $dt->format(DateTime::ISO8601); // 2014-10-26T02:30:00+0100
echo $dt->getTimestamp(); // 1414287000
echo $dt->getOffset(); // 3600
echo $dt->getTimeZone->getName(); // Europe/Paris

(我所说的武断,是指我找不到任何关于它的文档)

在根据给定时区的夏令时重叠范围内的日期和时间创建DateTime时,是否有方法选择首选偏移量

或者换句话说:

如何创建具有以下特征的DateTime对象:

echo $dt->format(DateTime::ISO8601); // 2014-10-26T02:30:00+0200
echo $dt->getTimestamp(); // 1414283400
echo $dt->getOffset(); // 7200
echo $dt->getTimeZone->getName(); // Europe/Paris

也就是说,在夏季时间的Europe/Paris时区中表示该日期/时间的对象?

首先,考虑一下PHP中有一个已知的错误会影响您。考虑:

$dt = new DateTime('2014-10-26T02:30', new DateTimeZone('Europe/Paris'));
echo $dt->format(DateTime::ISO8601) . " (" . $dt->getTimeStamp() . ")n";
$dt->setTimeStamp($dt->getTimeStamp() - 3600);
echo $dt->format(DateTime::ISO8601) . " (" . $dt->getTimeStamp() . ")n";

输出:

2014-10-26T02:30:00+0100 (1414287000)
2014-10-26T02:30:00+0100 (1414287000)

尽管您将时间戳向后调整了一个小时以反映夏季时间,PHP还是错误地将其提前到了冬季时间位置。

通过使用UTC作为中介,您可以绕过此进行显示

$tz = new DateTimeZone('Europe/Paris');
$dt = new DateTime('2014-10-26T02:30', $tz);
echo $dt->format(DateTime::ISO8601) . " (" . $dt->getTimeStamp() . ")n";
$ts = $dt->getTimeStamp() - 3600;
$dt = new DateTime("@$ts", new DateTimeZone('UTC'));
$dt->setTimeZone($tz);
echo $dt->format(DateTime::ISO8601) . " (" . $dt->getTimeStamp() . ")n";

输出:

2014-10-26T02:30:00+0100 (1414287000)
2014-10-26T02:30:00+0200 (1414287000)

请注意,即使返回了错误的时间戳(应该是1414283400),它也会保留+0200的期望夏季时间偏移。

现在,让我们来解决知道何时应用这个问题。我们将检查转换,并使用它来决定是否减去一个小时。

// set up the original input values
$tz = new DateTimeZone('Europe/Paris');
$dt = new DateTime('2014-10-26T02:30', $tz);
echo $dt->format(DateTime::ISO8601) . "n";
// check for a transition +/- an hour from the current time stamp
$ts = $dt->getTimestamp();
$transitions = $tz->getTransitions($ts - 3600, $ts + 3600);
if (count($transitions) > 1) {
    // see if we are moving backwards, creating the ambiguity
    $shift = $transitions[1]['offset'] - $transitions[0]['offset'];
    if ($shift < 0)
    {
        // apply the difference in offsets to move back to summer time
        $ts = $ts + $shift;
        $dt = new DateTime("@$ts", new DateTimeZone('UTC'));
        $dt->setTimeZone($tz);
    }
}
echo $dt->format(DateTime::ISO8601) . "n";

输出:

2014-10-26T02:30:00+0100
2014-10-26T02:30:00+0200

你也可以阅读这个相关的问题和答案。

最新更新