【问题标题】:Should MySQL have its timezone set to UTC?MySQL 是否应该将其时区设置为 UTC?
【发布时间】:2013-10-02 04:04:56
【问题描述】:

跟进问题 https://serverfault.com/questions/191331/should-servers-have-their-timezone-set-to-gmt-utc

MySQL 时区应该设置为 UTC 还是应该设置为与服务器或 PHP 设置相同的时区? (如果不是UTC)

有什么好处和坏处?

【问题讨论】:

标签: mysql time utc


【解决方案1】:

似乎服务器上的时区并不重要,只要您为当前时区设置了正确的时间,知道您存储的日期时间列的时区,并了解夏令时的问题时间。

另一方面,如果您可以控制所使用服务器的时区,那么您可以在内部将所有内容设置为 UTC,而不必担心时区和 DST,至少在存储内部时间方面是这样。

以下是我收集的一些关于如何将时区作为我自己和其他人的备忘单形式的笔记,这些笔记可能会影响该人将为他/她的服务器选择哪个时区以及他/她将如何存储日期和时间。

MySQL 时区备忘单

注意事项:

  1. 更改时区不会更改存储的日期时间或 时间戳,但它会选择不同的日期时间 时间戳列

  2. 警告! UTC 有闰秒,这些看起来像 '2012-06-30 23:59:60' 并且可以 随机添加,提前6个月通知,由于放缓 地球自转

  3. GMT 会混淆秒数,这就是发明 UTC 的原因。

  4. 警告!不同的区域时区可能会产生相同的日期时间值,因为 到夏令时

  5. 由于a limitation,时间戳列仅支持 1970-01-01 00:00:01 到 2038-01-19 03:14:07 UTC 日期。

  6. 在内部 MySQL timestamp column 存储为 UTC 但 选择日期时,MySQL 会自动将其转换为 当前会话时区。

    当在时间戳中存储日期时,MySQL 将假定日期 位于当前会话时区并将其转换为 UTC 存储。

  7. MySQL 可以在 datetime 列中存储部分日期,这些看起来像 "2013-00-00 04:00:00"

  8. 如果您将日期时间列设置为,MySQL 将存储“0000-00-00 00:00:00” NULL,除非您在指定时将列设置为允许为空 创建它。

  9. Read this

选择 UTC 格式的时间戳列

无论当前 MySQL 会话在哪个时区:

SELECT 
CONVERT_TZ(`timestamp_field`, @@session.time_zone, '+00:00') AS `utc_datetime` 
FROM `table_name`

您还可以将服务器或全局或当前会话时区设置为 UTC,然后像这样选择时间戳:

SELECT `timestamp_field` FROM `table_name`

以 UTC 选择当前日期时间:

SELECT UTC_TIMESTAMP();
SELECT UTC_TIMESTAMP;
SELECT CONVERT_TZ(NOW(), @@session.time_zone, '+00:00');

示例结果:2015-03-24 17:02:41

在会话时区中选择当前日期时间

SELECT NOW();
SELECT CURRENT_TIMESTAMP;
SELECT CURRENT_TIMESTAMP();

选择服务器启动时设置的时区

SELECT @@system_time_zone;

例如,返回莫斯科时间的“MSK”或“+04:00”,存在(或曾经)一个 MySQL 错误,如果设置为数字偏移量,它将不会调整夏令时

获取当前时区

SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP);

如果您的时区是 +2:00,它将返回 02:00:00。

获取当前 UNIX 时间戳(以秒为单位):

SELECT UNIX_TIMESTAMP(NOW());
SELECT UNIX_TIMESTAMP();

获取时间戳列作为 UNIX 时间戳

SELECT UNIX_TIMESTAMP(`timestamp`) FROM `table_name`

获取 UTC 日期时间列作为 UNIX 时间戳

SELECT UNIX_TIMESTAMP(CONVERT_TZ(`utc_datetime`, '+00:00', @@session.time_zone)) FROM `table_name`

从一个正的 UNIX 时间戳整数获取当前时区日期时间

SELECT FROM_UNIXTIME(`unix_timestamp_int`) FROM `table_name`

从 UNIX 时间戳获取 UTC 日期时间

SELECT CONVERT_TZ(FROM_UNIXTIME(`unix_timestamp_int`), @@session.time_zone, '+00:00') 
FROM `table_name`

从一个负的 UNIX 时间戳整数中获取当前时区日期时间

SELECT DATE_ADD('1970-01-01 00:00:00',INTERVAL -957632400 SECOND) 

在 MySQL 中有 3 个地方可以设置时区:

注意:时区可以设置为 2 种格式:

  1. 与 UTC 的偏移量:“+00:00”、“+10:00”或“-6:00”
  2. 作为命名时区:“Europe/Helsinki”、“US/Eastern”或“MET”

命名时区只能在时区信息表中使用 在 mysql 数据库中已创建并填充。

在“my.cnf”文件中

default_time_zone='+00:00'

timezone='UTC'

@@global.time_zone 变量

查看它们设置为什么值

SELECT @@global.time_zone;

要为其设置一个值,请使用其中一个:

SET GLOBAL time_zone = '+8:00';
SET GLOBAL time_zone = 'Europe/Helsinki';
SET @@global.time_zone='+00:00';

@@session.time_zone 变量

SELECT @@session.time_zone;

要设置它,请使用其中一个:

SET time_zone = 'Europe/Helsinki';
SET time_zone = "+00:00";
SET @@session.time_zone = "+00:00";

“@@global.time_zone 变量”和“@@session.time_zone 变量”都可能返回“SYSTEM”,这意味着它们使用“my.cnf”中设置的时区。

要使时区名称起作用(即使是默认时区),您必须设置需要填充的时区信息表: http://dev.mysql.com/doc/refman/5.1/en/time-zone-support.html

注意:你不能这样做,因为它会返回 NULL:

SELECT 
CONVERT_TZ(`timestamp_field`, TIMEDIFF(NOW(), UTC_TIMESTAMP), '+00:00') AS `utc_datetime` 
FROM `table_name`

设置 mysql 时区表

要使CONVERT_TZ 工作,您需要填充时区表

SELECT * FROM mysql.`time_zone` ;
SELECT * FROM mysql.`time_zone_leap_second` ;
SELECT * FROM mysql.`time_zone_name` ;
SELECT * FROM mysql.`time_zone_transition` ;
SELECT * FROM mysql.`time_zone_transition_type` ;

如果它们是空的,则通过运行此命令来填充它们

mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql

如果此命令给出错误“data too long for column 'abbreviation' at row 1”,则可能是由于在时区缩写末尾附加了一个 NULL 字符

解决方法是运行这个

mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
(if the above gives error "data too long for column 'abbreviation' at row 1")
mysql_tzinfo_to_sql /usr/share/zoneinfo > /tmp/zut.sql

echo "SET SESSION SQL_MODE = '';" > /tmp/mysql_tzinfo_to.sql
cat /tmp/zut.sql >> /tmp/mysql_tzinfo_to.sql

mysql --defaults-file=/etc/mysql/my.cnf --user=verifiedscratch -p mysql < /tmp/mysql_tzinfo_to.sql

(确保您的服务器 dst 规则是最新的 zdump -v Europe/Moscow | grep 2011 https://chrisjean.com/updating-daylight-saving-time-on-linux/

查看每个时区的完整 DST(夏令时)转换历史

SELECT 
tzn.Name AS tz_name,
tztt.Abbreviation AS tz_abbr,
tztt.Is_DST AS is_dst,
tztt.`Offset` AS `offset`,
DATE_ADD('1970-01-01 00:00:00',INTERVAL tzt.Transition_time SECOND)  AS transition_date
FROM mysql.`time_zone_transition` tzt
INNER JOIN mysql.`time_zone_transition_type` tztt USING(Time_zone_id, Transition_type_id)
INNER JOIN mysql.`time_zone_name` tzn USING(Time_zone_id)
-- WHERE tzn.Name LIKE 'Europe/Moscow' -- Moscow has weird DST changes
ORDER BY tzt.Transition_time ASC

CONVERT_TZ 还会根据上表中的规则和您使用的日期应用任何必要的 DST 更改。

注意:
根据docs,您为time_zone设置的值不会改变,例如如果您将其设置为“+01:00”,那么time_zone将设置为UTC的偏移量,它不遵循DST,所以它将全年保持不变。

只有命名的timezones 会在夏令时更改时间。

CET 等缩写将始终是冬季时间,CEST 将是夏季时间,而 +01:00 将始终是 UTC 时间 + 1 小时,两者都不会随 DST 改变。

system 时区将是安装 mysql 的主机的时区(除非 mysql 无法确定)

您可以阅读有关使用 DST 的更多信息here

传奇人物 Jon Skeet 何时不使用 UTC:https://codeblog.jonskeet.uk/2019/03/27/storing-utc-is-not-a-silver-bullet/ (例如未来的预定事件,它代表一个时间,而不是一个瞬间)

相关问题:

来源:

【讨论】:

  • 因此,如果我将列类型设置为时间戳。我的 time_zone 是 +12:00,我想使用基于 utc 的日期/时间来更新列,有没有办法在更新语句中包含时区,或者我应该使用 convert_tz。例如。更新table 设置modified = '2016-07-07 08:10 +00:00'
  • @bumperbox mysql 始终假定您提供时间戳列的日期与 mysql 服务器位于同一时区,因此您需要将日期从 +12:00 时区转换为您的 mysql 服务器更新的时区。这就是我在 mysql 服务器上使用 UTC 并在存储之前将任何日期转换为 UTC 的原因。
  • 这是我多年来使用 SO 时遇到的最好的、信息量最大的答案之一。谢谢。
  • @Flimm 完全正确,我前段时间忘记修复了。
  • @hafan96 是的,在 mysql 中以 utc 格式存储是个好习惯
【解决方案2】:

PHP 和 MySQL 有自己的默认时区配置。您应该在数据库和 Web 应用程序之间同步时间,否则可能会出现一些问题。

阅读本教程:How To Synchronize Your PHP and MySQL Timezones

【讨论】:

  • 基本上就是两行代码:date_default_timezone_set("America/Los_Angeles");mysql_query("SET time_zone='" . date('P', time()) . "'"); 工作得非常优雅!
  • @Noumenon 小心!今天早上我一直在摸不着头脑,因为这正是我正在做的事情,而且我的一些时间现在已经过去了一个小时。我怀疑在涉及 DST 时使用命名时区更准确。如果你使用 America/New_York,MySQL 知道 DST 并且会适当地存储日期。如果您像这里一样将其设置为 -04:00,则不会考虑 DST 计算。
  • 请检查 mysql 是否正确了解 DST,DST 规则会定期更新,相关的 mysql 表也需要更新(请参阅上面我的答案底部)
【解决方案3】:

这是一个工作示例:

jdbc:mysql://localhost:3306/database?useUnicode=yes&characterEncoding=UTF-8&serverTimezone=Europe/Moscow

【讨论】:

    【解决方案4】:

    优点和缺点几乎相同。这取决于你是否想要。

    请注意,如果 MySQL 时区与您的系统时间不同(例如 PHP),比较时间或打印给用户会涉及一些修改。

    【讨论】:

      【解决方案5】:

      如何让您的应用与服务器的时区无关?

      由于这些可能的情况:

      • 您可能无法控制网络/数据库服务器的时区设置
      • 您可能会搞砸并错误地设置设置
      • 其他答案中描述的设置太多,需要跟踪的事情太多,您可能会错过一些东西
      • 服务器上的更新、软件重置或其他管理员可能会在不知情的情况下将服务器的时区重置为默认值 - 从而破坏您的应用程序

      上述所有情况都会导致应用程序的时间计算中断。因此,似乎更好的方法是让您的应用程序独立于服务器的时区工作。

      这个想法只是在将日期存储到数据库之前始终以 UTC 格式创建日期,并始终从以 UTC 格式存储的值重新创建它们。这样,时间计算永远不会不正确,因为它们始终采用 UTC。这可以通过在创建 PHP DateTime 对象时显式声明 DateTimeZone 参数来实现。

      另一方面,客户端功能可以配置为将从服务器接收到的所有日期/时间转换为客户端的时区。像 moment.js 这样的库让这变得超级容易。

      例如,在数据库中存储日期时,不使用 MySQL 的 NOW() 函数,而是创建 UTC 格式的时间戳字符串,如下所示:

      // Storing dates
      $date = new DateTime('now', new DateTimeZone('UTC'));
      $sql = 'insert into table_name (date_column) values ("' . $date . '")';
      
      // Retreiving dates
      $sql = 'select date_column from table_name where condition';
      $dateInUTC = new DateTime($date_string_from_db, new DateTimeZone('UTC'));
      

      您可以在 PHP 中为创建的所有日期设置默认时区,从而无需在每次要创建日期时初始化 DateTimeZone 类。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-04-16
        • 1970-01-01
        • 1970-01-01
        • 2012-05-07
        • 2020-07-08
        • 2017-10-10
        相关资源
        最近更新 更多