【问题标题】:changed laravel app timezone, time got from db did not change更改了 laravel 应用程序时区,从 db 获取的时间没有改变
【发布时间】:2021-02-11 21:31:34
【问题描述】:

假设有一个名为 Request 的 Eloquent 模型,其列 next_check 自动转换为 date (protected $dates = ['next_check'];)。

APP_TIMEZONE 是最初的欧洲/赫尔辛基 (+02:00)

$ artisan tinker
Psy Shell v0.10.4 (PHP 7.4.11 — cli) by Justin Hileman
>>> $pr = Request::find(1);
>>> $pr->next_check = Carbon\Carbon::now();
>>> $pr->save();
>>> Request::find(1)->next_check;
=> Illuminate\Support\Carbon @1603973824 {#2900
     date: 2020-10-29 14:17:04.0 Europe/Helsinki (+02:00),
   }

看起来一切正常,假设我们在赫尔辛基,我们节省了 14:17:04。

现在将时区更改为欧洲/柏林 (+01:00),我会假设返回 13:17:04。

>>> Request::find(1)->next_check;
=> Illuminate\Support\Carbon @1603977424 {#2900
     date: 2020-10-29 14:17:04.0 Europe/Berlin (+01:00),
   }

时区已更改,但仍有返回的时间,该时间最初保存在欧洲/赫尔辛基时间。

这是一种预期的行为吗?我怎样才能获得我想要的行为,即应用时区的更改会导致转换时间?

p.s.:laravel中配置的db时区还是默认的'+00:00'

【问题讨论】:

  • 你需要实现什么?您想根据当前时区向用户显示不同的日期/时间吗?
  • 其实我问这个问题更多是为了了解 Laravel 中的时区处理,并希望相信时代,我明白了。当我的应用程序处于 +02:00 并且我将时间戳保存到数据库时,它应该在内部保存为 UTC 时间戳。我在 phpMyAdmin 中看到的另一个时间是由于时区配置。但是现在当我得到应用程序时区 +01:00 的时间时,不应该将它从数据库 UTC 时间戳转换为我的应用程序时区吗?它只是添加了一个时区而不转换时间,这让我不信任该数据,我正在考虑是否应该切换到 datetime 字段?
  • 也许 laravel 不认为时间戳总是在 UTC 中,而是在应用程序时区?所以我需要在更改应用时区时始终更改数据库时区?
  • 您无法使用现有日期更改数据库时区。请考虑在后端使用 UTC medium.com/@kylekatarnls/… 这是方法。赫尔辛基/柏林时区应该为用户格式化。

标签: laravel timezone php-carbon


【解决方案1】:

“这是预期的行为”的答案是

要使用日期数据“保存”时区,您还必须将其另存为额外的列。

因此,在 DB 中同时包含“2020-10-29 14:17:04.0”和“Europe/Helsinki”可以让您记住用于保存此日期的时区,因此如果更改了,您可以进行转换。

但首先您应该考虑不要更改您的应用程序和数据库时区,两者都应该保持 UTC。

https://medium.com/@kylekatarnls/always-use-utc-dates-and-times-8a8200ca3164

因此,您可以轻松地将它们转换为任何时区,并根据用户时区正确显示,同时在 DB 中保持标准的众所周知的格式。

【讨论】:

    【解决方案2】:

    为了获得预期的行为,我现在找到了解决方案。如果其他人想要完全相同的行为,这可能是解决方案:

    在您的 AppServiceProvider 的引导方法中添加以下调用:

    $this->setUTCOffsetInDBConnection();
    

    setUTCOffsetInDBConnection 方法的代码如下:

    // according to https://www.sitepoint.com/synchronize-php-mysql-timezone-configuration/
    private function setUTCOffsetInDBConnection() {
    
        $now = new \DateTime();
        $mins = $now->getOffset() / 60;
    
        $sgn = ($mins < 0 ? -1 : 1);
        $mins = abs($mins);
        $hrs = floor($mins / 60);
        $mins -= $hrs * 60;
    
        $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins);
    
        DB::statement("SET time_zone='".$offset."';");
    }
    

    这将始终根据您应用的时区同步时区,并将时间戳作为 UTC 保存在数据库中。

    当您现在在 .env 中使用 APP_TIMEZONE=Europe/Berlin 从您的应用中保存时间戳 2020-10-29 15:28:00 时,它将在数据库中保存为 UTC (2020-10-29 15: 28:00)。 当您现在将 APP_TIMEZONE 更改为 Europe/Helsinki 时,您将返回 2020-10-29 16:28:00。

    请注意,您的 phpMyAdmin 可能仍然不会在您的数据库中显示保存的 UTC 时间,因为 phpMyAdmin 在打开连接时也会设置服务器时区。因此,如果您运行 phpMyAdmin 的服务器也在欧洲/柏林,您将看到 2020-10-29 15:28:00 而不是保存在数据库中的 UTC 日期。

    【讨论】:

    • 不要使用时区偏移,它会在其他 DST 季节被破坏。
    【解决方案3】:

    如果您想本地化时间以便在用户的时区显示给用户,您需要转换现有的日期时间:

    $users_timezone = 'Europe/Berlin';
    $datetime->setTimezone($users_timezone);
    

    这实际上会根据应用的时区和转换后的时区之间的差异来改变时间。

    【讨论】:

    • 感谢您的回复。是的,我的意思是我更改了配置中的时区。我不需要知道记录保存在哪个时区。据我了解,时间将保存为mysql时间戳数据类型中的UTC时间戳。因此,当我更改配置时区时,我会假设我访问该字段的时间会改变。所以在我的例子中 13:17 +01:00 而不是 14:17 +02:00
    • 啊,是的,我明白了。这是因为时间并没有改变——你只是把它放到不同的时区,所以唯一改变的就是时区。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-31
    • 1970-01-01
    • 2020-08-04
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    • 2012-01-25
    相关资源
    最近更新 更多