How to detect Ambiguous and Invalid DateTime in PHP?
处理用户提供的本地
在其他语言和框架中,在时区的某些表示上通常有诸如
许多其他时区实现具有类似的方法或功能来解决这个问题。 例如,在Python中,pytz库将抛出一个可以捕获的
PHP有很好的时区支持,但我似乎找不到任何解决这个问题的方法。 我能找到的最接近的东西是DateTimeZone :: getTransitions。 这提供了原始数据,因此我可以看到一些方法可以在此基础上编写。 但他们已经存在于某个地方吗? 如果没有,任何人都可以提供良好的实施吗? 我希望他们的工作方式如下:
1 2 3 | $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 |
我不知道任何现有的实现,我还没有理由使用这些高级日期/时间功能,所以这里是一个干净的房间实现。
为了启用问题中说明的语法,我们将扩展
1 2 3 4 5 6 7 8 9 | 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); } |
为了让分散注意力的细节不会使实现变得混乱,我将假设
也就是说,这样不会产生正确的结果:
1 2 | $tz = new DateTimeZoneEx('America/New_York'); echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00')); |
而是由此:
1 2 | $tz = new DateTimeZoneEx('America/New_York'); echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00', $tz)); |
当然,由于
这里的想法是使用
我们来看看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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; } |
代码的最后一点取决于PHP的日期函数计算无效日期/时间的时间戳这一事实,就像挂钟时间没有移位一样。也就是说,在纽约时区,为
不难看出如何利用这一事实:创建一个等于输入
实现非常类似于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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; } |
最后一点取决于PHP日期函数的另一个实现细节:当被要求生成模糊日期/时间的时间戳时,PHP会生成第一个(绝对时间项)出现的时间戳。这意味着最新模糊时间的时间戳和给定DST变化的最早非模糊时间将相差大于DST偏移的量(具体地,差异将在[
该实现通过再次向前执行"挂钟转换"并检查结果与输入
查看实际代码。