如何使用时区信息在 MySQL 中存储日期时间
Posted
技术标签:
【中文标题】如何使用时区信息在 MySQL 中存储日期时间【英文标题】:How to store a datetime in MySQL with timezone info 【发布时间】:2013-11-19 12:39:03 【问题描述】:我有数千张在坦桑尼亚拍摄的照片,我想将每张照片的拍摄日期和时间存储在 mysql 数据库中。但是,服务器位于美国,当我尝试存储在春季夏令时(在美国)的“无效”小时内的坦桑尼亚日期时间时遇到了问题。坦桑尼亚不做夏令时,所以时间是一个实际有效的时间。
其他复杂情况是,来自许多不同时区的合作者需要访问存储在数据库中的日期时间值。我希望他们始终以坦桑尼亚时间出现,而不是各种合作者所在的当地时间。
我不愿意设置会话时间,因为我知道如果有人有时忘记设置会话时间并弄错时间,就会出现问题。而且我无权更改服务器的任何内容。
我读过: Daylight saving time and time zone best practices 和 MySQL datetime fields and daylight savings time -- how do I reference the "extra" hour? 和 Storing datetime as UTC in php/MySQL
但它们似乎都没有解决我的特殊问题。我不是 SQL 专家。有没有办法在设置 DATETIME 时指定时区?我一个都没见过。否则,非常感谢任何有关如何解决此问题的建议。
编辑: 这是我遇到的问题的一个例子。我发送命令:
INSERT INTO Images (CaptureEvent, SequenceNum, PathFilename, TimestampJPG)
VALUES (122,1,"S2/B04/B04_R1/IMAG0148.JPG","2011-03-13 02:49:10")
我得到了错误:
Error 1292: Incorrect datetime value: '2011-03-13 02:49:10' for column 'TimestampJPG'
此日期和时间存在于坦桑尼亚,但不存在于数据库所在的美国。
【问题讨论】:
您不应该将时区信息存储在数据库中。将所有日期/时间数据存储为 UTC,并始终在应用层进行时区偏移调整。 @MarcellFülöp:我一直看到人们说“将其存储为 UTC”,但我不明白这是什么意思。如何将日期时间存储为“UTC”?据我所知,我所能做的就是插入格式为 YYYY-MM-DD HH:MM:SS 的内容。我在哪里告诉数据库它是 UTC? 你没有。默认情况下,MySQL 将在内部使用系统时区,但可以为 MySQL 服务器全局甚至每个事务定义不同的时区。插入日期时,无法在 MySQL 中定义时区和日期字符串。像“2013-11-10 00:00”这样的日期是指自纪元以来的时间点。您存储它并且您知道您的服务器所在的时区。然后当您检索它时,您可以进行必要的调整以将该日期从服务器的时区转换为客户端的时区。 @marekful 不,这取决于。我想过滤掉黎明时分拍摄的照片,在这种情况下,UTC 将无济于事。我想同时保存 UTC 时间戳和时区偏移量。 IMO 和许多经验丰富的工程师遵循的最佳实践仍然是将所有日期/时间值存储为 UTC(又名 Zulu)。无论物理位置如何,都将数据库服务器配置为 UTC。然后,在这种特殊情况下,您想要做的是知道您想要找到与坦桑尼亚的黎明相对应的一天中的时间。因此,您在程序代码中添加逻辑。使用坦桑尼亚的时区创建一个时区实例,并过滤通过它从数据库中获取的所有日期/时间值。它将完成其余的工作并代表坦桑尼亚时间的所有价值观。它还将处理 DST。 【参考方案1】:你说:
我希望它们始终以坦桑尼亚时间出现,而不是各种合作者所在的当地时间。
如果是这种情况,那么您应该不使用 UTC。您需要做的就是在 MySQL 中使用 DATETIME
类型而不是 TIMESTAMP
类型。
From the MySQL documentation:
MySQL 将
TIMESTAMP
值从当前时区转换为 UTC 进行存储,然后从 UTC 转换回当前时区进行检索。 (DATETIME
等其他类型不会出现这种情况。)
如果您已经在使用DATETIME
类型,那么您一定没有在本地时间开始设置它。您将需要较少地关注数据库,而更多地关注您的应用程序代码 - 您没有在这里展示。问题和解决方案会因语言而异,因此请务必使用应用程序代码的适当语言标记问题。
【讨论】:
我使用的是 DATETIME。但是,如果我尝试设置在美国不存在的时间(但在坦桑尼亚存在),数据库会抛出错误。我已经编辑了我的问题,以举例说明我遇到的麻烦。我的应用程序代码恰好在 Python 中,但它现在实际上只是 SQL 代码的包装器。我很高兴在 Python 中进行转换,但不确定它们应该是什么。 能否请您展示您的 python 代码,以便我们了解您是如何构建 SQL 查询的?您可能只是在处理 python 中的天真与有意识的日期时间。您是否肯定您的TimestampJPG
列是mysql 数据库架构中的DATETIME
类型而不是TIMESTAMP
类型?
精氨酸。你说得对。我将数据库中的所有其他 TIMESTAMP 更改为 DATETIME,但不知何故错过了这个。这解决了这个问题。谢谢。
这是另一个让 MySQL 让您后悔没有使用 Postgresql 的地方。 ;)
@poige 为什么说 Postgresql 在这里会更好?它还在存储之前将时间戳转换为 UTC。【参考方案2】:
您描述的所有症状表明您从未告诉 MySQL 使用哪个时区,因此它默认为系统的区域。想一想:如果只有'2011-03-13 02:49:10'
,它怎么能猜到是坦桑尼亚当地的约会对象?
据我所知,MySQL 没有提供任何语法来指定日期中的时区信息。您必须将其更改为per-connection basis;类似:
SET time_zone = 'EAT';
如果这不起作用(要使用命名区域,您需要 the server has been configured 这样做,但通常情况并非如此)您可以使用 UTC 偏移量,因为在撰写本文时 Tanzania does not observe daylight saving time 但当然不是最好的选择:
SET time_zone = '+03:00';
【讨论】:
您不能只使用 UTC 偏移量,因为它忽略了夏令时。你的时间很容易就结束了一个小时,几乎没有希望纠正它!我知道 SQL Server 中的函数会将 UTC 转换为您的时区,但它没有考虑夏令时的任何任意变化。存储在 UTC 零偏移量中并在应用程序中进行转换——这非常干净地解决了很多问题,但是您将在临时查询中正确转换日期。 现在至少可以直接在日期中指定时区偏移量,例如像这样:'2020-01-01 10:10:10+05:30'(来自dev.mysql.com/doc/refman/8.0/en/datetime.html)。因为时区偏移是针对特定日期和时间的,所以它不指定特定时区(以及它们的夏令时规则)并不重要。因为通过这种方式,您可以准确地指定如何从该日期计算 UTC,并且 MySQL 将其作为 UTC 保存在其数据库中。仅当您要计算多个日期(DST 而不是 DST)的 UTC 时,才需要像“EAT”这样的时区。【参考方案3】:MySQL 存储 DATETIME 没有时区信息。假设您将“2019-01-01 20:00:00”存储到 DATETIME 字段中,当您检索该值时,您应该知道它属于哪个时区。
因此,在您的情况下,当您将值存储到 DATETIME 字段时,请确保它是坦桑尼亚时间。然后当你把它拿出来的时候,就是坦桑尼亚的时间了。耶!
现在,棘手的问题是:当我执行 INSERT/UPDATE 时,如何确保值是坦桑尼亚时间?两种情况:
你是INSERT INTO table (dateCreated) VALUES (CURRENT_TIMESTAMP or NOW())
。
您执行INSERT INTO table (dateCreated) VALUES (?)
,并从您的应用程序代码中指定当前时间。
案例#1
MySQL 将采用当前时间,假设是 '2019-01-01 20:00:00' 坦桑尼亚时间。然后 MySQL 会将其转换为 UTC,即 '2019-01-01 17:00:00',并将 那个 值存储到字段中。
那么如何将坦桑尼亚时间(即“20:00:00”)存储到字段中?这是不可能的。从该字段读取时,您的代码需要预计 UTC 时间。
案例#2
这取决于您作为?
传递的值类型。如果您传递字符串“2019-01-01 20:00:00”,那么对您有好处,这正是将存储到数据库的内容。如果您传递某种日期对象,那么它将取决于 db 驱动程序如何解释该日期对象,以及它提供的最终 'YYYY-MM-DD HH:mm:ss' 字符串到 MySQL 进行存储。 db 驱动程序的文档应该会告诉你。
【讨论】:
案例 #1 是错误的。 MySQL 不会将 datetime 列的值转换为 UTC。它们按原样存储。【参考方案4】:我曾经也遇到过这样一个问题,我需要保存不同合作者使用的数据,我最终以 unix 时间戳形式存储时间,它表示自 1970 年 1 月以来的秒数,这是一种整数格式。
坦桑尼亚今天的日期和时间示例为 Friday, September 13, 2019 9:44:01 PM
,当存储在 unix 时间戳中时为 1568400241
现在读取数据时,只需使用 php 或任何其他语言,然后从 unix 时间戳中提取日期。使用 php 的示例将是
echo date('m/d/Y', 1568400241);
这使得与不同位置的其他协作者一起存储数据变得更加容易。他们可以简单地将日期转换为具有自己的 gmt 偏移量的 unix 时间戳,并将其存储为整数格式,并且在输出时只需使用 a
【讨论】:
从技术上讲,您不是在存储带有时区信息的日期——您是在丢弃时区信息。这基本上是 MySQL 中原生日期类型所做的事情 ;-)【参考方案5】:这里的答案都没有一针见血。
如何使用时区信息在 MySQL 中存储日期时间
使用两列:DATETIME
和一个VARCHAR
来保存时区信息,可能有多种形式:
timezone 或 位置 例如 America/New_York
是最高的数据保真度。
timezone abbreviation 如PST
是次高保真度。
像-2:00
这样的时间偏移量是这方面的最小数据量。
一些关键点:
避免使用TIMESTAMP
,因为它仅限于 2038 年,并且 MySQL 将其与服务器时区相关联,这可能是不受欢迎的。
时间偏移不应天真地存储在 INT
字段中,因为存在半小时和四分之一小时的偏移。
如果让 MySQL compare 或 sort 这些日期按时间顺序对您的用例很重要,DATETIME
有问题:
'2009-11-10 11:00:00 -0500'
在“即时”方面在'2009-11-10 10:00:00 -0700'
之前,但当插入DATETIME
时,它们会以另一种方式排序。
您可以自行转换为 UTC。在上面的示例中,您将分别拥有 '2009-11-10 16:00:00'
和 '2009-11-10 17:00:00'
,这将正确排序。检索数据时,您将使用时区信息将其恢复为原始形式。
我非常喜欢的一个recommendation 是有三个 列:
local_time DATETIME
utc_time DATETIME
time_zone VARCHAR(X)
其中 X 适用于您在其中存储的数据类型。 (我会为时区/位置选择 64 个字符。)
3 列方法的一个优点是它是明确的:使用单个 DATETIME
列,您无法一眼看出它在插入之前是否已转换为 UTC。
关于通过时区/缩写/偏移量的准确性下降:
如果您有用户的timezone/location,例如America/Juneau
,您可以准确地知道他们在过去或未来的任何时间点的挂钟时间(除非更改该位置处理夏令时的方式)。夏令时的起点/终点,以及是否使用,都取决于位置,因此这是唯一可靠的方法。
如果您有一个 timezone abbreviation,例如 MST(山地标准时间)或一个普通的偏移量,例如 -0700
,您将无法预测过去或未来的挂钟时间。例如,在美国,科罗拉多州和亚利桑那州都使用 MST,但亚利桑那州不遵守 DST。因此,如果用户在冬季月份将他的猫照片上传到14:00 -0700
,那么他是在亚利桑那州还是加利福尼亚州?如果您正好在该日期前加上六个月,那么对于用户来说是14:00
还是13:00
?
当您的应用程序将时间、日期或日程安排作为核心功能时,这些事项非常重要。
参考资料:
MySQL Date/Time Reference The Proper Way to Handle Multiple Time Zones in MySQL (披露:我没有阅读整篇文章。)【讨论】:
我没有看到存储本地时间的好处。恕我直言,存储日期时间值本身的最简洁方式是 UTC。这避免了比较日期时间时的任何混淆。如果有理由知道在特定当地时间是什么,那么记录一个 LOCATION。然后应用程序逻辑可以应用该位置来了解本地时间(例如,它是在工作时间吗?)但将 DateTime 本身保持在 UTC 中。例如。我们的瑞典设施被定义在瑞典的时区;我们已经有一个字段(在事件记录中)说“在什么设施”,所以不需要单独为每个事件记录添加时区。 如果有人在一个地点参加另一地点的活动,那么保持 UTC 日期的好处就很明显了。如果纽约的技术人员报告瑞典的事件,“当地时间”是纽约还是瑞典?通过存储 UTC 时间来避免这种混淆。技术人员的位置和设施的位置,附加到事件中,允许根据需要在任一“本地时间”查看它,而不会产生任何混淆 - 只需指定 POV。或者您可能对它发生在您的时间感兴趣,这可能是第三个“本地时间”。通过位置动态指定“本地偏移量”。 @ToolmakerSteve 如果您再次阅读我的答案,您会发现我的主要观点是您需要将time
和location
存储在不同的列中。我还谈到了存储 UTC 时间。如果您希望仅存储 UTC 时间,因为您的 location
s 是复数和/或在另一个表中,那太好了。我唯一的建议是为清楚起见将其标记为utc_time
。
如果我需要时间处理任何不是数据库元数据的数据点,我也会使用这种方法。例如,我存储了一些跨越多个时区的旅行数据。我保留了四列(start_dt、start_tz、end_dt、end_tz),但为简单起见,我还计算了duration
,这样我就不需要一直进行这些计算。如果我需要在用户的时区中显示它们,我会在应用程序层中进行。虽然有时我正在考虑更改为存储 UTC + 时区,以便我可以更轻松地将 UTC 传递给应用程序
存储utc_time
+ tz
而不是local_time
+ tz
的另一个原因是local_time
+ tz
并不总是可以明确地转换为时间点(瞬间)。例如。如果 DST 时钟在凌晨 3:00 向后移动 1 小时,则将有两个当地时间凌晨 2:01 的实例,即使您知道 TZ id,它们也将无法区分。因此,要同时存储:“时刻”和“此时本地时钟上的时间”,您需要存储 utc 时间 + tz id。或本地时间 + 偏移量 + tz id(有时更可取)。以上是关于如何使用时区信息在 MySQL 中存储日期时间的主要内容,如果未能解决你的问题,请参考以下文章