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

How to fix this very frustrating time bug in my time code?

我有这两个功能:

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 说明了这一点:

The Unix timestamp that this function returns does not contain information about time zones. In order to do calculations with date/time information, you should use the more capable DateTimeImmutable.

但是,如果我提供完全相同的 date/time 字符串,例如在末尾添加“+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。这将从根本上改变我的整个结构,并要求我以非常不同的方式做事。也许我 应该 ,甚至 ,在某些时候,但现在,我只想修复我的这个罕见但仍然非常烦人的错误现有代码。 (如果可能的话,我非常相信是这样的。)

您能否考虑一个解决方案: 在时间戳字符串中(如 Thu, 21 Dec 2000 16:01:07 +0200),添加一个 timezone 标签,指定不区分夏令时的时区。

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

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;
}