关于php:MySQL日期时间字段和夏令时 – 我如何引用“额外”时间?

MySQL datetime fields and daylight savings time — how do I reference the “extra” hour?

我正在使用美国/纽约时区。在秋天,我们"退回"一小时 - 在凌晨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)。但这似乎不应该是必要的。

非常感谢任何建议。


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

与我在之前的评论中所说的相反,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()函数,因为CONVERT_TZ将只输出一个":MM:YYYY-MM-DD HH SS"完成的MySQL从歧义问题遭受值,和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上看到了这一点


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

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

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

这里使用专有的INTERVAL语法:

1
2
3
4
5
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()也不会返回准确的时间。

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

奇怪的是,DATETIME仍将在DST开始的"丢失"时间内存储并返回(仅以字符串形式!)次数(例如2009-03-08 02:59:59)。但是在任何MySQL函数中使用这些日期都是有风险的:

1
2
3
4
5
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

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

  • 将系统时区设置为GMT +一些常量偏移量。例如。世界标准时间
  • 将日期存储为INT(如Aaron发现,TIMESTAMP甚至不可靠)

  • 假设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


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

    1
    SET SESSION time_zone = '+0:00'

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


    这个线程让我变得怪异,因为我们使用带有On UPDATE CURRENT_TIMESTAMPTIMESTAMP列(即:recordTimestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP)来跟踪已更改的记录和ETL到数据仓库。

    如果有人想知道,在这种情况下,TIMESTAMP表现正常,您可以通过将TIMESTAMP转换为unix时间戳来区分两个相似的日期:

    1
    2
    3
    4
    5
    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

    But how do I save anything to 01:30:00
    -04:00?

    你可以转换为UTC,如:

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

    更好的是,将日期保存为TIMESTAMP字段。它始终以UTC格式存储,UTC不了解夏季/冬季时间。

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

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

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


    Mysql固有地使用mysql db中的time_zone_name表解决了这个问题。
    在CRUD时使用CONVERT_TZ更新日期时间而不必担心夏令时。

    1
    2
    3
    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;


    我正在研究记录页面访问次数和在图表中显示计数(使用Flot jQuery插件)。我在表格中填写了测试数据,一切看起来都很好,但我注意到在图表的末尾,根据x轴上的标签,这些点是一天休息。经过检查,我注意到2015-10-25天的视图计数从数据库中检索了两次并传递给Flot,因此在此日期之后的每一天都被移动了一天到右边。
    在我的代码中查找了一段时间的错误后,我意识到这个日期是DST发生的时间。然后我来到这个SO页面......
    ...但建议的解决方案对我需要的东西来说太过分了,或者他们有其他缺点。我不是很担心无法区分模糊的时间戳。我只需要计算并显示每天的记录。

    首先,我检索日期范围:

    1
    2
    3
    4
    5
    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开始,以max_date结尾,一天(60*60*24)的步骤,我正在检索计数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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("Y-m-d 00:00:00", $day ) ."' AND
                    '"
    . date("Y-m-d 23:59:59", $day ) ."'
                )
       "
    ;
        //execute query and do stuff with the result
    }

    我对问题的最终和快速解决方案是:

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

    所以我不是在一天开始时盯着循环,而是两小时后。这一天仍然是相同的,我仍在检索正确的计数,因为我明确要求数据库在当天的00:00:00和23:59:59之间记录,而不管时间戳的实际时间。当时间跳了一个小时,我仍然在正确的一天。

    注意:我知道这是一个5岁的线程,我知道这不是OP问题的答案,但它可能会帮助像我这样的人找到解决我描述的问题的方法。