MySQL日期时间字段和夏令时 - 我如何引用"额外"时间?

Aar*_*ron 83 php mysql datetime timestamp dst

我正在使用美国/纽约时区.在秋天,我们"退回"一小时 - 在凌晨2点有效地"获得"一小时.在转换点,会发生以下情况:

它是01:59:00 -04:00
然后1分钟后变成:01 :00:
00-05 :00

因此,如果您只是简单地说"凌晨1点30分",那么您是指第一次1:30滚动还是第二次推测是不明确的.我正在尝试将调度数据保存到MySQL数据库,无法确定如何正确保存时间.

这是问题:
"2009-11-01 00:30:00"在内部存储为2009-11-01 00:30:00 -04:00
"2009-11-01 01:30:00"在内部存储为2009-11-01 01:30:00 -05:00

这很好,也很合理.但是如何保存到01:30:00 -04:00呢?该文件没有显示指定偏移,因此,当我试图指定偏移它已经正式忽略任何支持.

我想到的唯一解决方案是将服务器设置为不使用夏令时的时区,并在我的脚本中进行必要的转换(我正在使用PHP).但这似乎不应该是必要的.

非常感谢任何建议.

Aar*_*ron 67

我已经弄清楚了我的目的.我会总结一下我学到的东西(对不起,这些笔记很冗长;它们和我未来的推荐一样多).

相反的是我在我以前的意见一说,DATETIME和TIMESTAMP领域表现不同.TIMESTAMP字段(如文档所示)采用"YYYY-MM-DD hh:mm:ss"格式发送的任何内容,并将其从当前时区转换为UTC时间.每当您检索数据时,反向都会透明地发生.DATETIME字段不进行此转换.他们接受你发送的任何东西,直接存储它.

DATETIME和TIMESTAMP字段类型都不能在观察DST的时区中准确地存储数据.如果您存储"2009-11-01 01:30:00",则字段无法区分您想要的1:30 am版本 - -04:00或-05:00版本.

好的,所以我们必须将数据存储在非DST时区(例如UTC)中.由于我将解释的原因,TIMESTAMP字段无法准确处理此数据:如果您的系统设置为DST时区,那么您在TIMESTAMP中输入的内容可能不是您返回的内容.即使您发送了已经转换为UTC的数据,它仍然会假设您的本地时区中的数据并再次转换为UTC.当您的本地时区观察到DST(因为"2009-11-01 01:30:00"映射到2个不同的可能时间)时,TIMESTAMP强制执行的本地到UTC回本地往返是有损的.

使用DATETIME,您可以将数据存储在您想要的任何时区,并确信您将收到任何您发送的数据(您不会被迫进入TIMESTAMP字段强加给您的有损往返转换).所以解决方案是使用DATETIME字段并在保存到字段之前将系统时区转换为您要保存的任何非DST区域(我认为UTC可能是最佳选择).这允许您将转换逻辑构建到脚本语言中,以便您可以显式保存UTC等效于"2009-11-01 01:30:00 -04:00"或""2009-11-01 01:30: 00:05:00".

另一个需要注意的重要事项是,如果您将日期存储在DST TZ中,则MySQL的日期/时间数学函数无法在DST边界周围正常工作.所以更有理由以UTC保存.

简而言之,我现在这样做:

从数据库中检索数据时:

在MySQL之外明确地将数据库中的数据解释为UTC,以获得准确的Unix时间戳.我使用PHP的strtotime()函数或其DateTime类.使用MySQL的CONVERT_TZ()或UNIX_TIMESTAMP()函数无法在MySQL内部可靠地完成,因为CONVERT_TZ只会输出一个"YYYY-MM-DD hh:mm:ss"值,它会出现歧义问题,并且UNIX_TIMESTAMP()假定它输入在系统时区,而不是数据实际存储在(UTC)中的时区.

将数据存储到数据库时:

将您的日期转换为您在MySQL之外所需的精确UTC时间.例如:使用PHP的DateTime类,您可以从"2009-11-01 1:30:00 EDT"明确指定"2009-11-01 1:30:00 EST",然后将其转换为UTC并保存正确的UTC时间到你的DATETIME字段.

唷.非常感谢大家的投入和帮助.希望这可以为其他人带来一些令人头疼的问题.

顺便说一下,我在MySQL 5.0.22和5.0.27上看到了这一点


Ste*_*lay 43

坦率地说,MySQL的日期类型已被破坏,并且无法正确存储所有时间,除非您的系统设置为常量偏移时区,如UTC或GMT-5.(我使用的是MySQL 5.0.45)

这是因为您无法在夏令时结束前的一小时内存储任何时间.无论您如何输入日期,每个日期函数都会将这些时间视为切换后的一小时内.

我的系统的时区是America/New_York.让我们试试存储1257051600(Sun,2009年11月1日06:00:00 +0100).

这里使用专有的INTERVAL语法:

SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3599 SECOND); # 1257051599
SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3600 SECOND); # 1257055200

SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 1 SECOND); # 1257051599
SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 0 SECOND); # 1257055200
Run Code Online (Sandbox Code Playgroud)

甚至FROM_UNIXTIME()不会返回准确的时间.

SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051599)); # 1257051599
SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051600)); # 1257055200
Run Code Online (Sandbox Code Playgroud)

奇怪的是,DATETIME 仍然是"迷失"一小时内存储和返回(仅字符串形式!)倍DST开始时(例如2009-03-08 02:59:59).但是在任何MySQL函数中使用这些日期都是有风险的:

SELECT UNIX_TIMESTAMP('2009-03-08 01:59:59'); # 1236495599
SELECT UNIX_TIMESTAMP('2009-03-08 02:00:00'); # 1236495600
# ...
SELECT UNIX_TIMESTAMP('2009-03-08 02:59:59'); # 1236495600
SELECT UNIX_TIMESTAMP('2009-03-08 03:00:00'); # 1236495600
Run Code Online (Sandbox Code Playgroud)

要点:如果你需要在一年中的一次存储和检索,你有一些不受欢迎的选择:

  1. 将系统时区设置为GMT +一些常量偏移量.例如UTC
  2. 将日期存储为INT(如Aaron发现,TIMESTAMP甚至不可靠)

  3. 假设DATETIME类型具有一些常量偏移时区.例如,如果您在America/New_York,请将您的日期转换为MySQL之外的 GMT-5 ,然后存储为DATETIME(这看起来很重要:请参阅Aaron的回答).然后你必须非常小心使用MySQL的日期/时间函数,因为有些人假设你的值是系统时区,其他(特别是时间算术函数)是"时区不可知的"(它们可能表现得好像时间是UTC).

Aaron和我怀疑自动生成的TIMESTAMP列也被破坏了.双方2009-11-01 01:30 -04002009-11-01 01:30 -0500会被存储为暧昧2009-11-01 01:30.

  • -1:您犯了一个错误,MySQL 日期/时间函数在 DATETIME 类型上运行,该类型与时区无关。因此,您传递给 UNIX_TIMSTAMP 的参数是“select '2009-11-01 00:00:00' + INTERVAL 3600 SECOND;”,即“2009-11-01 01:00:00”。然后,UNIX_TIMESTAMP 只是尝试在会话时区的上下文中将其转换为 UTC - 它不会尝试在该时区的 DST 规则的上下文中执行添加。 (2认同)

Ste*_*lay 13

我认为micahwittman的链接具有解决这些MySQL限制的最佳实用解决方案:连接时将会话时区设置为UTC:

SET SESSION time_zone = '+0:00'
Run Code Online (Sandbox Code Playgroud)

然后你只需发送它的Unix时间戳,一切都应该没问题.


And*_*mar 5

但是如何将任何内容保存到 01:30:00 -04:00?

您可以转换为 UTC,如:

SELECT CONVERT_TZ('2009-11-29 01:30:00','-04:00','+00:00');
Run Code Online (Sandbox Code Playgroud)


更好的是,将日期保存为TIMESTAMP字段。它总是以 UTC 存储,而 UTC 不知道夏/冬时间。

您可以使用CONVERT_TZ从 UTC 转换为本地时间

SELECT CONVERT_TZ(UTC_TIMESTAMP(),'+00:00','SYSTEM');
Run Code Online (Sandbox Code Playgroud)

其中 '+00:00' 是 UTC,来自时区,'SYSTEM' 是运行 MySQL 的操作系统的本地时区。


小智 5

Mysql 使用 mysql 数据库中的 time_zone_name 表本质上解决了这个问题。在 CRUD 时使用 CONVERT_TZ 来更新日期时间,而无需担心夏令时。

SELECT
  CONVERT_TZ('2019-04-01 00:00:00','Europe/London','UTC') AS time1,
  CONVERT_TZ('2019-03-01 00:00:00','Europe/London','UTC') AS time2;
Run Code Online (Sandbox Code Playgroud)