DST 结束时的 Laravel Carbon addHour()

Posted

技术标签:

【中文标题】DST 结束时的 Laravel Carbon addHour()【英文标题】:Laravel Carbon addHour() when DST ends 【发布时间】:2019-01-04 18:20:54 【问题描述】:

我正在使用在 Ubuntu 服务器上运行的 Laravel 5.5。 服务器时区设置为 EEST。 Laravel 时区设置在 config/app.php'timezone' => 'Europe/Sofia',

这里(欧洲/索非亚)夏令时从 3 月 25 日 03:00 开始,到 10 月 28 日 04:00 结束。

当我运行以下代码时:

    $datetime = Carbon::createFromDate(2018, 3, 24);
    $datetime->hour = 23;
    $datetime->minute = 0;
    $datetime->second = 0;
    dump($datetime);
    foreach (range(1,12) as $v) 
        $datetime->addHour(1);
        echo $datetime.'<br /> ';
    

我得到了预期的输出(缺少 03:00 小时):

Carbon @1521925200 #505  date: 2018-03-24 23:00:00.0 Europe/Sofia (+02:00)
2018-03-25 00:00:00
2018-03-25 01:00:00
2018-03-25 02:00:00
2018-03-25 04:00:00
2018-03-25 05:00:00
2018-03-25 06:00:00
2018-03-25 07:00:00
2018-03-25 08:00:00
2018-03-25 09:00:00
2018-03-25 10:00:00
2018-03-25 11:00:00
2018-03-25 12:00:00

现在,当我将日期设置为 DST 结束时:

    $datetime = Carbon::createFromDate(2018, 10, 27);
    $datetime->hour = 23;
    $datetime->minute = 0;
    $datetime->second = 0;
    dump($datetime);
    foreach (range(1,12) as $v) 
        $datetime->addHour(1);
        echo $datetime.'<br /> ';
    

输出是:

Carbon @1540670400 #211  date: 2018-10-27 23:00:00.0 Europe/Sofia (+03:00)

2018-10-28 00:00:00
2018-10-28 01:00:00
2018-10-28 02:00:00
2018-10-28 03:00:00
2018-10-28 04:00:00
2018-10-28 05:00:00
2018-10-28 06:00:00
2018-10-28 07:00:00
2018-10-28 08:00:00
2018-10-28 09:00:00
2018-10-28 10:00:00
2018-10-28 11:00:00

现在我预计第 3 个小时会出现两次。 如果这是 Carbon addHour() 的正确输出,是否有一种在 DST 开始和结束期间获取日期时间的好方法?

【问题讨论】:

【参考方案1】:

首先,我们可以在这里丢弃 Carbon 和 Laravel。这一切都发生在 PHP 核心中。我们还可以验证 PHP 安装所具有的时区信息,尽管它不太可能不正确,除非保加利亚最近改变了它的规定。你可能会得到和我一样的转换:

$tz = new DateTimeZone('Europe/Sofia');
$start = new DateTime('2018-01-01', $tz);
$end = new DateTime('2019-01-01', $tz);
var_dump($tz->getTransitions($start->format('U'), $end->format('U')));
array(3) 
  [0]=>
  array(5) 
    ["ts"]=>
    int(1514757600)
    ["time"]=>
    string(24) "2017-12-31T22:00:00+0000"
    ["offset"]=>
    int(7200)
    ["isdst"]=>
    bool(false)
    ["abbr"]=>
    string(3) "EET"
  
  [1]=>
  array(5) 
    ["ts"]=>
    int(1521939600)
    ["time"]=>
    string(24) "2018-03-25T01:00:00+0000"
    ["offset"]=>
    int(10800)
    ["isdst"]=>
    bool(true)
    ["abbr"]=>
    string(4) "EEST"
  
  [2]=>
  array(5) 
    ["ts"]=>
    int(1540688400)
    ["time"]=>
    string(24) "2018-10-28T01:00:00+0000"
    ["offset"]=>
    int(7200)
    ["isdst"]=>
    bool(false)
    ["abbr"]=>
    string(3) "EET"
  

简而言之,时区在 EET (+0200) 和 EEST (+0300) 之间交换:

2018-03-25 01:00:00 UTC(当地时间 3:00) 2018-10-28 01:00:00 UTC(当地时间 4:00)

故事是 PHP 确实将时区和 DST 考虑在内,但我在实践中发现在跨边界的日期数学方面存在一些微妙的错误。

3 月看起来一切正常:

$datetime = new DateTime('2018-03-25 02:59:59', $tz);
echo $datetime->format('r'), PHP_EOL; 
$datetime->modify('+1 second');
echo $datetime->format('r'), PHP_EOL; 
echo PHP_EOL;
Sun, 25 Mar 2018 02:59:59 +0200
Sun, 25 Mar 2018 04:00:00 +0300

在 10 月,它会提前一小时进行过渡,您不会看到重复的时间,因为它发生在……:59:59:

$datetime = new DateTime('2018-10-28 02:59:59', $tz);
echo $datetime->format('r'), PHP_EOL; 
$datetime->modify('+1 second');
echo $datetime->format('r'), PHP_EOL; 
echo PHP_EOL;
Sun, 28 Oct 2018 02:59:59 +0300
Sun, 28 Oct 2018 03:00:00 +0200

但是,如果您使用 UTC 进行数学运算,则会得到不同的结果:

$datetime = new DateTime('2018-10-28 02:59:59', $tz);
echo $datetime->format('r'), PHP_EOL; 
$datetime->setTimezone($utc);
$datetime->modify('+1 second');
$datetime->setTimezone($tz);
echo $datetime->format('r'), PHP_EOL; 
echo PHP_EOL;
Sun, 28 Oct 2018 02:59:59 +0300
Sun, 28 Oct 2018 03:00:00 +0300

我并没有明确解释为什么会发生这种情况(我不熟悉 PHP 内部),但我怀疑这与 10 月 28 日有两个不同的3:00 local 的事实引起的歧义有关小时,但时区信息 (Europe/Sofia) 不包含足够的信息来确定您的意思。但是,当您从 UTC 转换时,就没有歧义了:

00:59:59 UTC 映射到 EET 01:00:00 UTC 映射到 EEST

我的建议是要么使用它,要么尽可能使用 UTC 进行所有内部计算,并切换到本地时间仅用于显示目的。

【讨论】:

以上是关于DST 结束时的 Laravel Carbon addHour()的主要内容,如果未能解决你的问题,请参考以下文章

Laravel - 获取今天在开始和结束之间的行

Laravel Carbon 简明使用

Laravel、Carbon 和验证

如何在 Laravel 5 中从 Carbon 获取当前时间戳

Laravel 在 where 子句中更改日期格式以匹配 Carbon::now()

text Carbon Laravel Blade