MySQLdate时间字段和夏令时 – 我如何引用“额外”小时?

我正在使用美国/纽约时区。 在秋天,我们“倒退”一个小时 – 有效地“凌晨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 ? 该文档不显示任何指定偏移的支持,因此,当我已经尝试指定偏移它被适当的忽略。

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

非常感谢您的任何build议。

MySQL的datetypes,坦率地说,破坏,不能正确存储所有时间,除非你的系统设置为一个恒定的偏移时区,如UTC或GMT-5。 (我正在使用MySQL 5.0.45)

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

我的系统的时区是America/New_York 。 我们尝试存储1257051600(Sun,01 Nov 2009 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 

即使FROM_UNIXTIME()也不会返回准确的时间。

 SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051599)); # 1257051599 SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051600)); # 1257055200 

奇怪的是,DATETIME仍然在DST开始的时间(例如2009-03-08 02:59:59 )在“丢失”小时内存储和返回(仅以stringforms)。 但是在任何MySQL函数中使用这些date都是有风险的:

 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 

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

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

  3. 假装DATETIMEtypes有一些常量偏移时区。 例如,如果你在America/New_York ,把你的date转换成MySQL以外的 GMT-5,然后把它作为DATETIME存储(事实certificate这是非常重要的:见Aaron的答案)。 那么你必须非常小心地使用MySQL的date/时间函数,因为有些假设你的值是系统时区,其他的(特别是时间算术函数)是“时区不可知的”(它们可能performance得好像时间是UTC)。

亚伦和我怀疑自动生成TIMESTAMP列也被打破。 2009-11-01 01:30 -04002009-11-01 01:30 -0500都会被保存为2009-11-01 01:30含糊不清。

我已经明白了我的目的。 我会总结一下我所学到的东西(对不起,这些笔记是冗长的;对于我以后的转介来说,它们和其他东西一样)。

与我之前的评论中所说的相反,DATETIME和TIMESTAMP字段的行为有所不同。 TIMESTAMP字段(如文档所示)以“YYYY-MM-DD hh:mm:ss”格式发送它们,并将其从当前时区转换为UTC时间。 只要您检索数据,反向就会透明地发生。 DATETIME字段不会进行此转换。 他们把你发送给他们的任何东西直接存储起来。

DATETIME和TIMESTAMP字段types都不能准确地将数据存储在观察DST的时区中 。 如果您存储“2009-11-01 01:30:00”,则无法区分哪个版本是您想要的上午1:30,即-04:00或-05:00版本。

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

使用DATETIME,您可以将数据存储在任何您想要的时区,并确信您将返回任何您发送的数据(您不必被迫进入TIMESTAMP字段强加于您的有损往返转换)。 所以解决scheme是使用DATETIME字段,并在保存到字段之前,从您的系统时区转换到任何非DST区域,你想保存它(我认为UTC可能是最好的select)。 这允许您将转换逻辑构build到您的脚本语言中,以便您可以明确地保存“2009-11-01 01:30:00 -04:00”或“2009-11-01 01:30: 00 -05:00“。

另外需要注意的是,如果将date存储在DST TZ中,则MySQL的date/时间math函数在DST边界附近无法正常工作。 所以所有更多的理由保存在UTC。

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

从数据库中检索数据时:

为了得到一个精确的Unix时间戳,在MySQL之外明确地解释数据库中的数据为UTC。 我使用PHP的strtotime()函数或它的DateTime类。 使用MySQL的CONVERT_TZ()或UNIX_TIMESTAMP()函数不能在MySQL内部可靠地完成这个工作,因为CONVERT_TZ只会输出一个受到多义性问题困扰的'YYYY-MM-DD hh:mm:ss'值,而UNIX_TIMESTAMPinput在系统时区中,而不是数据实际存储在(UTC)中的时区。

将数据存储到数据库时:

把你的date转换成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

我认为micahwittman的链接是这些MySQL限制的最佳实际解决scheme:连接时将会话时区设置为UTC:

 SET SESSION time_zone = '+0:00' 

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

但是如何保存到01:30:00 -04:00?

你可以转换为UTC像:

 SELECT CONVERT_TZ('2009-11-29 01:30:00','-04:00','+00:00'); 

更好的是,将date保存为TIMESTAMP字段。 这总是以UTC存储的,UTC不知道夏天/冬天的时间。

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

 SELECT CONVERT_TZ(UTC_TIMESTAMP(),'+00:00','SYSTEM'); 

“+00:00”是UTC,“from”和“SYSTEM”是MySQL运行的操作系统的本地时区。

由于我们使用TIMESTAMP列的On UPDATE CURRENT_TIMESTAMP (即: recordTimestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP )跟踪已更改的logging和ETL到数据库。

如果有人想知道,在这种情况下, TIMESTAMP行为是正确的,你可以通过将TIMESTAMP转换为unix时间戳来区分两个相似的date:

 select TestFact.*, UNIX_TIMESTAMP(recordTimestamp) from TestFact; id recordTimestamp UNIX_TIMESTAMP(recordTimestamp) 1 2012-11-04 01:00:10.0 1352005210 2 2012-11-04 01:00:10.0 1352008810 

我正在logging页面访问次数和显示图表计数(使用Flot jQuery插件)。 我用testing数据填满了桌子,一切看起来都很好,但是我注意到在图表的最后,根据x轴上的标签,这些点是一天的时间。 经过检查,我注意到2015-10-25的查看次数从数据库中检索了两次并传给了Flot,所以在这个date之后的每一天都被移动了一天。
在我的代码中查找了一段时间后,我意识到这个date是DST发生的时间。 然后我来到这个SO页面…
…但是build议的解决scheme对于我所需要的或者他们还有其他缺点是过分的。 我不担心不能区分模糊的时间戳。 我只需要每天计算和显示logging。

首先,我检索date范围:

 SELECT DATE(MIN(created_timestamp)) AS min_date, DATE(MAX(created_timestamp)) AS max_date FROM page_display_log WHERE item_id = :item_id 

然后,在for循环中,以min_date开始,以min_date结尾,以一天( 60*60*24 )为单位,检索计数:

 for( $day = $min_date_timestamp; $day <= $max_date_timestamp; $day += 60 * 60 * 24 ) { $query = " SELECT COUNT(*) AS count_per_day FROM page_display_log WHERE item_id = :item_id AND ( created_timestamp BETWEEN '" . date( "Ymd 00:00:00", $day ) . "' AND '" . date( "Ymd 23:59:59", $day ) . "' ) "; //execute query and do stuff with the result } 

最后的和快速的解决 我的问题是这样的:

 $min_date_timestamp += 60 * 60 * 2; // To avoid DST problems for( $day = $min_date_timestamp; $day <= $max_da..... 

所以我在一天的开始并没有注意到这个循环,而是两个小时之后 。 这一天仍然是一样的,我仍然在检索正确的计数,因为我明确地要求数据库在当天的00:00:00和23:59:59之间logging,而不pipe时间戳的实际时间。 而当时间跳了一个小时,我仍然是在正确的一天。

注:我知道这是5岁的线程,我知道这不是一个OP问题的答案,但它可能帮助像我这样的人遇到这个页面寻找解决我所描述的问题的解决scheme。