代码之家  ›  专栏  ›  技术社区  ›  user756659

php strtotime和timezones未按预期运行

  •  2
  • user756659  · 技术社区  · 6 年前

    对时区和计算使用strtotime时产生意外结果。我的PHP设置为UTC。 strtotime("-6 days 00:00:00 America/Chicago"); 似乎给了我们意想不到的结果。我以为通过提供时区,它会在那个时区中被“计算”出来,但事实并非如此。

    在我的示例中,不管PHP的默认时区是什么,我都希望结果是相同的,因为strtotime使用的是“America/Chicago”。取而代之的似乎是以当前日期(根据默认时区)减去天数,然后在提供的时区中计算00:00:00。

    我的格式不对吗?

    我为混乱的代码道歉,但这只是一个演示我所说内容的例子。

    date_default_timezone_set("UTC");
    echo date_default_timezone_get();
    echo "</br>";
    echo "</br>";
    
    echo convertTS(time(), "America/Chicago");
    
    echo "</br>";
    echo "</br>";
    
    $test = strtotime("-6 days 00:00:00 America/Chicago");
    
    echo $test;
    
    echo "</br>";
    
    echo convertTS($test, "America/Chicago");
    
    echo "</br>";
    
    $test = strtotime("-7 days 00:00:00 America/Chicago");
    
    echo $test;
    
    echo "</br>";
    
    echo convertTS($test, "America/Chicago");
    echo "</br>";
    echo "</br>";
    echo "-----------------";
    echo "</br>";
    echo "</br>";
    
    ///now in America/Chicago
    
    date_default_timezone_set("America/Chicago");
    echo date_default_timezone_get();
    echo "</br>";
    echo "</br>";
    
    echo convertTS(time(), "America/Chicago");
    
    echo "</br>";
    echo "</br>";
    
    $test = strtotime("-6 days 00:00:00 America/Chicago");
    
    echo $test;
    
    echo "</br>";
    
    echo convertTS($test, "America/Chicago");
    
    echo "</br>";
    
    $test = strtotime("-7 days 00:00:00 America/Chicago");
    
    echo $test;
    
    echo "</br>";
    
    echo convertTS($test, "America/Chicago");
    
    function convertTS($timestamp, $timezone)
    {
        if( empty($timestamp) )
        {
            return 'n/a';
        }
        else
        {
            //output the time
            $dt = new DateTime('@'.$timestamp);
            $dt->setTimeZone(new DateTimeZone($timezone));
            return $dt->format('D, m/d/y @ g:i:s a T');
        }
    }
    

    我运行时的上述结果:

    UTC
    
    Fri, 01/04/19 @ 11:39:15 pm CST
    
    1546149600
    Sun, 12/30/18 @ 12:00:00 am CST
    1546063200
    Sat, 12/29/18 @ 12:00:00 am CST
    
    -----------------
    
    America/Chicago
    
    Fri, 01/04/19 @ 11:39:15 pm CST
    
    1546063200
    Sat, 12/29/18 @ 12:00:00 am CST
    1545976800
    Fri, 12/28/18 @ 12:00:00 am CST
    

    我做了一个沙盒来重现这个问题 here . 提供时间戳而不是使用 time() 显示默认时区如何根据当前时间影响结果。

    3 回复  |  直到 6 年前
        1
  •  1
  •   Dharman chazomaticus    6 年前

    首先,我们先来解释几个小问题:

    • Unix时间戳总是以UTC为单位
    • 你提供的时间戳 十五亿四千六百五十七万八千 ISO 8601格式为:“ 2019-01-04 05:00:00.000000 “,显然是UTC+00:00
    • CST比UTC晚6小时,即UTC–06:00
    • 你的职能 convertTS 将始终返回与默认时区无关的相同结果
    • 你减去多少天并不重要,所以让我们简单一点,说-2天

    当你打电话给 转换器 函数获取UTC格式的时间戳,并将其转换并显示在CST时区中。当您通过1546578000作为参数时,您将得到“2019-01-03 23:00:00.000000”。

    strtotime 接受2个参数: int strtotime ( string $time [, int $now = time() ] ) . 但是PHP文档说明,除非在该参数中指定时区,否则该函数的两个参数都使用默认时区。在您的示例中,每次都在第一个参数中显式地指定时区。第二个参数读取默认时间戳,并在计算相对日期之前将UTC UNIX时间戳转换为该时区。

    一些关于正在发生的事情的伪代码

    当时区为UTC时:

    date_default_timezone_set("UTC");
    strtotime("-2 days 00:00:00 America/Chicago", 1546578000);
    
    1. 将1546578000转换为 协调世界时 : 2019-01-04 05:00:00.000000
    2. 将时间设置为00:00:00 CST: 2019-01-04 06:00:00.000000 *UTC+00:00*
    3. 减去2天: 2019-01-02 06:00:00.000000
    4. 将时间戳返回为整数

    当默认时区为CST时:

    date_default_timezone_set("America/Chicago");
    strtotime("-2 days 00:00:00 America/Chicago", 1546578000);
    
    1. 将1546578000转换为 科技委 : 2019-01-03 23:00:00.000000
    2. 将时间设置为00:00:00 CST: 2019-01-03 06:00:00.000000 *UTC+00:00*
    3. 减去2天: 2019-01-01 06:00:00.000000
    4. 将时间戳返回为整数,当然是在UTC+00:00中,请参见第一点

    相对日期的PHP解析器并不完美。事实上,我认为当它将UNIX时间戳转换为默认时区时是错误的。。。或者我对它的理解也可能有缺陷。

    我如何收集数据:

    我把代码改成了 DateTime 上课。

    date_default_timezone_set("UTC");
    
    $dt = (new \DateTime())->setTimestamp(1546578000);
    $dt->modify("-2 days 00:00:00 America/Chicago");
    echo "UTC: ".$dt->format('D, m/d/y @ g:i:s a T');
    
    echo "</br>";
    echo "</br>";
    echo "-----------------";
    echo "</br>";
    echo "</br>";
    
    ///now in America/Chicago
    date_default_timezone_set("America/Chicago");
    
    $dt = (new \DateTime())->setTimestamp(1546578000);
    $dt->modify("-2 days 00:00:00 America/Chicago");
    echo "CST: ".$dt->format('D, m/d/y @ g:i:s a T');
    

    然后我使用XDebug调试代码并查看 $dt 变量。
    enter image description here

    更改时区后:
    enter image description here

        2
  •  0
  •   Álvaro González    6 年前

    字符串中的时区被明确地考虑在内:

    date_default_timezone_set('UTC');
    var_dump(date('r', strtotime('-6 days 00:00:00 America/Chicago')));
    var_dump(date('r', strtotime('-6 days 00:00:00 Europe/Madrid')));
    
    string(31) "Sun, 30 Dec 2018 06:00:00 +0000"
    string(31) "Sat, 29 Dec 2018 23:00:00 +0000"
    

    也许令人困惑的是结果是 显示 在配置的时区中:

    date_default_timezone_set('UTC');
    var_dump(date('r', strtotime('-6 days 00:00:00 America/Chicago')));
    date_default_timezone_set('Europe/Madrid');
    var_dump(date('r', strtotime('-6 days 00:00:00 America/Chicago')));
    
    string(31) "Sun, 30 Dec 2018 06:00:00 +0000"
    string(31) "Sun, 30 Dec 2018 07:00:00 +0100"
    

    ... 但实际发生的时刻是一样的:

    date_default_timezone_set('UTC');
    var_dump(strtotime('-6 days 00:00:00 America/Chicago'));
    date_default_timezone_set('Europe/Madrid');
    var_dump(strtotime('-6 days 00:00:00 America/Chicago'));
    
    string(10) "1546149600"
    string(10) "1546149600"
    
        3
  •  0
  •   user756659    6 年前

    在@Dharman指出strotime实际上是如何处理事情(我仍然认为这是错误的)的帮助下,我能够修改一些事情并得出我期望的结果,而不考虑php的默认时区。这提供了我期望的结果。。。其中计算(减去天数并设置为00:00:00)发生在指定的时区中。

    sandbox example

    date_default_timezone_set("UTC");
    
    $time = time();
    //$time = 1546578000;
    $tz = "Europe/Amsterdam";
    
    $dt = new DateTime('@'.$time);
    $dt->setTimeZone(new DateTimeZone('UTC'));
    echo $dt->format('D, m/d/y @ g:i:s a T');
    echo "\n";
    echo $dt->format('U');
    
    echo "\n";
    echo "\n";
    
    $dt = new DateTime('@'.$time);
    $dt->setTimeZone(new DateTimeZone('UTC'));
    $dt->modify("-2 days 00:00:00");
    echo $dt->format('D, m/d/y @ g:i:s a T');
    echo "\n";
    echo $dt->format('U');
    
    echo "\n";
    echo "\n";
    echo "---------------";
    echo "\n";
    echo "\n";
    
    $dt = new DateTime('@'.$time);
    $dt->setTimeZone(new DateTimeZone($tz));
    echo $dt->format('D, m/d/y @ g:i:s a T');
    echo "\n";
    echo $dt->format('U');
    
    echo "\n";
    echo "\n";
    
    $dt = new DateTime('@'.$time);
    $dt->setTimeZone(new DateTimeZone($tz));
    $dt->modify("-2 days 00:00:00");
    echo $dt->format('D, m/d/y @ g:i:s a T');
    echo "\n";
    echo $dt->format('U');
    

    在这种情况下将输出:

    Sat, 01/05/19 @ 6:51:18 pm UTC
    1546714278
    
    Thu, 01/03/19 @ 12:00:00 am UTC
    1546473600
    
    ---------------
    
    Sat, 01/05/19 @ 7:51:18 pm CET
    1546714278
    
    Thu, 01/03/19 @ 12:00:00 am CET
    1546470000