Mat*_*int 9 php timezone datetime dst
处理DateTime用户提供的本地值时,由于夏令时转换,很可能会产生无效或模糊的时间.
在其他语言和框架中,通常有一些方法,例如isAmbiguous和isValid时区的某些表示.例如在.NET中,有TimeZoneInfo.IsAmbiguousTime和TimeZoneInfo.IsInvalidTime.
许多其他时区实现具有类似的方法或功能来解决这个问题.例如,在Python中,pytz库将抛出一个AmbiguousTimeError或InvalidTimeError异常,你可以捕获.
PHP有很好的时区支持,但我似乎找不到任何解决这个问题的方法.我能找到的最接近的东西是DateTimeZone :: getTransitions.这提供了原始数据,因此我可以看到一些方法可以在此基础上编写.但它们是否已存在于某个地方?如果没有,任何人都可以提供良好的实施吗?我希望他们的工作方式如下:
$tz = new DateTimeZone('America/New_York');
echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00')); # false
echo $tz->isAmbiguousTime(new DateTime('2013-11-03 01:00:00')); # true
Run Code Online (Sandbox Code Playgroud)
我不知道任何现有的实现,我还没有理由使用这些高级日期/时间功能,所以这里是一个干净的房间实现.
要启用问题中说明的语法,我们将扩展DateTimeZone如下:
class DateTimeZoneEx extends DateTimeZone
{
const MAX_DST_SHIFT = 7200; // let's be generous
// DateTime instead of DateTimeInterface for PHP < 5.5
public function isValidTime(DateTimeInterface $date);
public function isAmbiguousTime(DateTimeInterface $date);
}
Run Code Online (Sandbox Code Playgroud)
为了让分散注意力的细节不会使实现混乱,我将假设$date参数是使用适当的时区创建的; 这与问题中给出的示例代码形成对比.
也就是说,这样不会产生正确的结果:
$tz = new DateTimeZoneEx('America/New_York');
echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00'));
Run Code Online (Sandbox Code Playgroud)
而是由此:
$tz = new DateTimeZoneEx('America/New_York');
echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00', $tz));
Run Code Online (Sandbox Code Playgroud)
当然,由于$tz对象已经知道$this,因此应该很容易扩展方法以便删除此要求.在任何情况下,使界面超级用户友好是超出了这个答案的范围; 今后我将重点关注技术细节.
isValidTime这里的想法是用来getTransitions查看我们感兴趣的日期/时间周围是否有任何转换.getTransitions将返回一个包含一个或两个元素的数组; "开始"时间戳的时区情况将始终存在,如果在其后不久发生转换,则将存在另一个元素.值MAX_DST_SHIFT足够小,没有机会获得第二个过渡/第三个元素.
我们来看看代码:
public function isValidTime(DateTime $date)
{
$ts = $date->getTimestamp();
$transitions = $this->getTransitions(
$ts - self::MAX_DST_SHIFT,
$ts + self::MAX_DST_SHIFT
);
if (count($transitions) == 1) {
// No DST changes around here, so obviously $date is valid
return true;
}
$shift = $transitions[1]['offset'] - $transitions[0]['offset'];
if ($shift < 0) {
// The clock moved backward, so obviously $date is valid
// (although it might be ambiguous)
return true;
}
$compare = new DateTime($date->format('Y-m-d H:i:s'), $this);
return $compare->modify("$shift seconds")->getTimestamp() != $ts;
}
Run Code Online (Sandbox Code Playgroud)
代码的最后一点取决于PHP的日期函数计算无效日期/时间的时间戳这一事实,就像挂钟时间没有移位一样.也就是说,纽约时区的时间戳计算2013-03-10 02:30:00和2013-03-10 03:30:00相同.
不难看出如何利用这一事实:创建一个DateTime等于输入的新实例$date,然后在挂钟时间条件中将其向前移动一个等于DST移位的数量(以秒为单位)(必须将DST纳入其中)帐户进行此调整).如果结果的时间戳(这里是DST规则发挥作用)等于输入的时间戳,则输入是无效的日期/时间.
isAmbiguousTime实现非常相似isValidTime,只有少数细节发生了变化:
public function isAmbiguousTime(DateTime $date)
{
$ts = $date->getTimestamp();
$transitions = $this->getTransitions(
$ts - self::MAX_DST_SHIFT,
$ts + self::MAX_DST_SHIFT);
if (count($transitions) == 1) {
return false;
}
$shift = $transitions[1]['offset'] - $transitions[0]['offset'];
if ($shift > 0) {
// The clock moved forward, so obviously $date is not ambiguous
// (although it might be invalid)
return false;
}
$shift = -$shift;
$compare = new DateTime($date->format('Y-m-d H:i:s'), $this);
return $compare->modify("$shift seconds")->getTimestamp() - $ts > $shift;
}
Run Code Online (Sandbox Code Playgroud)
最后一点取决于PHP日期函数的另一个实现细节:当被要求生成模糊日期/时间的时间戳时,PHP会生成第一个(绝对时间项)出现的时间戳.这意味着最新模糊时间的时间戳和给定DST变化的最早非模糊时间将相差大于DST偏移的量(具体地,差异将在范围[ offset + 1,2 * offset]中,其中offset是绝对值值).
该实现通过再次向前执行"挂钟转换"并检查结果和输入之间的时间戳差异来利用这一点$date.