如何在我的时间代码中修复这个非常令人沮丧的时间错误?

问题描述

我有以下两个功能

function time_is_older_than($timestamp,$time_string)
{
    if (strtotime($timestamp) < strtotime('-' . $time_string))
        return true;

    return false;
}

function time_is_younger_than($timestamp,$time_string)
{
    if (strtotime($timestamp) > strtotime('-' . $time_string))
        return true;

    return false;
}

它们使我能够做以下整洁的事情:

if (time_is_older_than($last_time_some_action_happened,'5 minutes'))
    do_it_again();

当我的时区切换为“夏令时”或“冬季”时,他们通常工作 每六个月工作一小时时间”。这意味着时钟会增加或推迟到午夜一小时(根据该时区)。

PHP手册针对strtotime声明了这一点:

函数返回的Unix时间戳不包含有关时区的信息。为了使用日期/时间信息进行计算,您应该使用功能更强大的DateTimeImmutable。

但是,如果我提供了完全相同的日期/时间字符串,例如在末尾添加了“ +08:00”与“ +00:00”,则返回的秒数不同。因此strtotime()在解析提供的时间时确实理解了时区,即使返回的整数显然不包含此信息也是如此。 (不是我期望或要求的。)

我花了无数小时试图调试它,测试无数事物,并且只是坐在这里思考,但是我无法弄清楚到底什么会使我的代码失败,特别是一小时。尤其是我需要更改的内容。为strtotime()设置第二个参数似乎是可能的,但我只是无法使其正常工作。

一段时间以来,我最热的“线索”是strtotime('-' . $time_string)部分使用的时区与提供的时间戳字符串不同,但是我确实在大多数时间提供时区数据! $last_time_some_action_happened的示例可能类似于2020-10-28 02:22:41.123456+01

我将时区设置为date_default_timezone_set()

我怀疑我只需要做一些很小的改动,但是现在我已经进行了这么长时间的实验,即使休息了一段时间,我的大脑也无法清楚地看到这一点。我敢打赌,解决方案非常简单。

请不要告诉我使用DateTimeImmutable。这将从根本上改变我的整个结构,并要求我做事大不相同。也许在某个时候我应该,甚至,但是就目前而言,我只想修复现有代码中这个罕见但仍然非常烦人的错误。 (如果有可能,我非常相信是这种情况。)

解决方法

我能够重现您遇到的问题:

date_default_timezone_set('Pacific/Auckland');

// Daylight saving time 2020 in New Zealand began at 2:00am on Sunday,27 September
$current = strtotime('2020-09-27 02:04:00');

$d1 = strtotime('2020-09-27 02:05:00',$current);
$d2 = strtotime('-5 minutes',$current);

var_dump($d1 > $d2); // false
var_dump(date('Y-m-d H:i:s',$d1)); // 2020-09-27 03:05:00
var_dump(date('Y-m-d H:i:s',$d2)); // 2020-09-27 03:59:00

此人看起来与您有相同的问题,可能似乎是错误。 DateTime::modify and DST switch 解决方案是将日期转换为UTC,然后进行比较:

// Convert to UTC and compare
$d1 = new \DateTime('2020-09-27 02:05:00',new \DateTimeZone('Pacific/Auckland'));

$d2 = new \DateTime('2020-09-27 02:04:00',new \DateTimeZone('Pacific/Auckland'));
$d2->setTimezone(new \DateTimeZone('UTC'));
$d2->modify('-5 minutes');
$d2->setTimezone(new \DateTimeZone('Pacific/Auckland'));

var_dump($d1 > $d2); // true
var_dump($d1->format(\DateTimeInterface::RFC3339_EXTENDED)); // 2020-09-27T03:05:00.000+13:00
var_dump($d2->format(\DateTimeInterface::RFC3339_EXTENDED)); // 2020-09-27T01:59:00.000+12:00

我已经更新了您的功能:

function time_is_older_than($datetime,$time_string)
{
    $d1 = new \DateTime($datetime);
    $d1->setTimezone(new \DateTimeZone('UTC'));
    
    $d2 = new \DateTime();
    $d2->setTimezone(new \DateTimeZone('UTC'));
    $d2->modify('-' . $time_string);
    
    return $d1 < $d2;
}

function time_is_younger_than($datetime,$time_string)
{
    $d1 = new \DateTime($datetime);
    $d1->setTimezone(new \DateTimeZone('UTC'));
    
    $d2 = new \DateTime();
    $d2->setTimezone(new \DateTimeZone('UTC'));
    $d2->modify('-' . $time_string);
    
    return $d1 > $d2;
}
,

您可以考虑以下解决方案: 在时间戳字符串中(例如Thu,2000年12月21日16:01:07 +0200),添加一个时区标记,该标记指定时区,且不夏时制。