我不知道任何现有的实现,我还没有理由使用诸如此类的高级日期/时间功能,所以这里是一个无尘室实现。
为了启用问题中说明的语法,我们将扩展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);
}
为了避免分散注意力的细节使实现变得混乱,我将假设$date
参数是使用正确的时区创建的;这与问题中给出的示例代码形成对比。
也就是说,这样不会产生正确的结果:
$tz = new DateTimeZoneEx('America/New_York');
echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00'));
而是这样:
$tz = new DateTimeZoneEx('America/New_York');
echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00', $tz));
当然,由于$tz
对象已经知道 as $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;
}
代码的最后一点取决于 PHP 的日期函数计算无效日期/时间的时间戳,就好像挂钟时间没有移动一样。也就是说,为纽约时区计算的时间戳将是相同的2013-03-10 02:30:00
。2013-03-10 03:30:00
不难看出如何利用这一事实:创建一个DateTime
等于 input 的新实例,然后以挂钟时间术语$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;
}
最后一点取决于 PHP 日期函数的另一个实现细节:当要求为不明确的日期/时间生成时间戳时,PHP 会生成第一次(以绝对时间而言)出现的时间戳。这意味着给定 DST 更改的最新模糊时间和最早非模糊时间的时间戳将相差大于 DST 偏移量(具体而言,差异将在 [ offset + 1
, 2 * offset
] 范围内,其中offset
是绝对价值)。
该实现通过再次向前执行“挂钟移位”并检查结果和输入之间的时间戳差异来利用这一点$date
。
请参阅实际代码。