如何使用时区信息在MySQL中存储日期时间

mko*_*ala 59 mysql timezone datetime

我有数以千计在坦桑尼亚拍摄的照片,我想在MySQL数据库中存储每张照片的日期和时间.然而,服务器位于美国,当我尝试存储坦桑尼亚日期时间时,我遇到问题,该日期时间在春季夏令时期间(在美国)处于"无效"小时内.坦桑尼亚不做DST,所以时间实际上是有效的时间.

其他复杂情况是,来自许多不同时区的合作者需要访问存储在数据库中的日期时间值.我希望他们总是作为坦桑尼亚时间出现,而不是在各个合作者所处的当地时代.

我不愿意设置会话时间因为我知道当有人忘记设置会话时间并且错过时间时会出现问题.而我无权更改服务器的任何内容.

我读过: 夏令时和时区最佳实践MySQL日期时间字段和夏令时 - 我如何引用"额外"时间?在PHP/MySQL中将日期时间存储为UTC

但它们似乎都没有解决我的特殊问题.我不是SQL专家; 有没有办法在设置DATETIME时指定时区?我还没见过一个.否则,非常感谢任何有关如何处理此问题的建议.

编辑:这是我遇到的问题的一个例子.我发送命令:

INSERT INTO Images (CaptureEvent, SequenceNum, PathFilename, TimestampJPG) 
VALUES (122,1,"S2/B04/B04_R1/IMAG0148.JPG","2011-03-13 02:49:10")
Run Code Online (Sandbox Code Playgroud)

我收到错误:

Error 1292: Incorrect datetime value: '2011-03-13 02:49:10' for column 'TimestampJPG'
Run Code Online (Sandbox Code Playgroud)

这个日期和时间存在于坦桑尼亚,但不存在于数据库所在的美国.

Mat*_*int 54

你说:

我希望他们总是作为坦桑尼亚时间出现,而不是在各个合作者所处的当地时代.

如果是这种情况,那么你应该使用UTC.您需要做的就是DATETIME在MySQL中使用类型而不是TIMESTAMP类型.

从MySQL文档:

MySQL将TIMESTAMP当前时区的值转换为UTC进行存储,然后从UTC转换回当前时区进行检索.(对于其他类型,例如,这不会发生DATETIME.)

如果您已经在使用DATETIME类型,那么您必须在当地时间之前不要设置它.您需要更少关注数据库,更多关注应用程序代码 - 您没有在此处展示.问题和解决方案将根据语言的不同而有很大差异,因此请务必使用应用程序代码的相应语言标记问题.

  • 这是MySQL让你对不使用Postgresql感到遗憾的另一个领域.;) (11认同)
  • @poige为什么说Postgresql在这里会更好?它还会在存储之前将时间戳转换为UTC. (4认同)
  • 我正在使用DATETIME.但是,如果我尝试设置美国不存在的时间(但在坦桑尼亚),数据库会抛出错误.我编辑了我的问题以举例说明我遇到的麻烦.我的应用程序代码恰好在Python中,但它实际上只是SQL代码的包装器.我很高兴在Python中进行转换,但不确定它们应该是什么. (2认同)
  • 那么请你展示你的python代码,这样我们就可以看到你是如何构建SQL查询的?你可能只是在python中处理天真vs知道日期时间.你是*肯定*你的`TimestampJPG`列是mysql数据库模式中的`DATETIME`类型而不是`TIMESTAMP`类型? (2认同)

Mat*_* Mc 33

这里的答案都没有一针见血。

如何使用时区信息在 MySQL 中存储日期时间

使用两列:DATETIME, 和 aVARCHAR来保存时区信息,可能有几种形式:

时区位置,例如America/New_York是最高的数据的保真度。

一个时区的缩写,如PST为下一个最高的保真度。

诸如此类的时间偏移-2:00是这方面的最小数据量。

一些关键点:

  • 避免,TIMESTAMP因为它仅限于 2038 年,并且 MySQL 将其与服务器时区相关联,这可能是不受欢迎的。
  • 时间偏移不应天真地存储在INT字段中,因为存在半小时和四分之一小时的偏移。

如果让 MySQL按时间顺序比较排序这些日期对您的用例很重要,则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',它们将正确排序。检索数据时,您将使用时区信息将其恢复为原始形式。

我非常喜欢的一项建议是有列:

  • local_time DATETIME
  • utc_time DATETIME
  • time_zone VARCHAR(X)其中 X 适合您在那里存储的数据类型。(我会为时区/位置选择 64 个字符。)

3 列方法的一个优点是它是明确的:对于单个DATETIME列,您无法一眼看出它在插入之前是否已转换为 UTC。


关于通过时区/缩写/偏移量的准确性下降:

  • 如果您有用户的时区/位置,例如America/Juneau,您可以准确地知道他们在过去或未来的任何时间的挂钟时间(除非改变该位置处理夏令时的方式)。DST 的起点/终点,以及是否使用它,取决于位置,因此这是唯一可靠的方法。
  • 如果您有一个时区缩写,例如 MST,(山地标准时间)或一个简单的偏移量,例如-0700,您将无法预测过去或未来的挂钟时间。例如,在美国,科罗拉多州和亚利桑那州都使用 MST,但亚利桑那州不遵守 DST。那么如果用户14:00 -0700在冬季上传他的猫照片,他是在亚利桑那州还是加利福尼亚州?如果您在该日期准确添加六个月,是为用户14:00还是13:00为用户?

当您的应用程序将时间、日期或日程安排作为核心功能时,考虑这些事情很重要。


参考:

  • @ToolmakerSteve如果您再次阅读我的答案,您会发现我的主要观点是您需要将“时间”和“位置”存储在单独的列中。我还讨论了存储 UTC 时间。如果您更喜欢*仅*存储 UTC 时间,因为您的“位置”是复数和/或在另一个表中,那就太好了。为了清楚起见,我唯一的建议是将其标记为“utc_time”。 (6认同)
  • 如果某个地点的某人正在参加另一地点的活动,则以 UTC 保存日期的好处就变得很明显。如果纽约的技术人员报告瑞典的事件,那么“当地时间”是纽约还是瑞典?通过存储 UTC 时间来避免这种混乱。技术人员的位置和设施的位置附加到事件中,允许根据需要在*任一*“当地时间”查看该事件,而不会产生任何混淆 - 只需指定 POV。或者您可能对它在您的时间发生的时间感兴趣,这可能是第三个“当地时间”。通过位置动态指定“本地偏移”。 (4认同)
  • 我没有看到存储当地时间的好处。恕我直言,存储日期时间值本身的最简洁方式是使用 UTC。这可以避免比较日期时间时出现任何混淆。如果有理由知道特定当地时间的情况,请记录一个位置。然后,应用程序逻辑可以应用该位置来了解本地时间(例如,是在工作时间吗?),但将日期时间本身保留为 UTC。例如,我们的瑞典工厂被定义为位于瑞典时区;我们已经有一个字段(在事件记录中)表示“在哪个设施”,因此不需要单独向每个事件记录添加时区。 (3认同)
  • 存储“utc_time”+“tz”而不是“local_time”+“tz”的另一个原因是“local_time”+“tz”并不总是可以明确地转换为时间点(瞬间)。例如,当 DST 时钟在凌晨 3:00 向后移动 1 小时时,将会出现两个当地时间凌晨 2:01 的实例,即使您知道 TZ id,它们也将无法区分。因此,要存储“当前时刻”和“当前本地时钟的时间”,您需要存储 utc 时间 + tz id。或者本地时间 + 偏移量 + tz id (有时更好)。 (3认同)

Hai*_*han 8

MySQL 存储没有时区信息的DATETIME 。假设您将 '2019-01-01 20:00:00' 存储到 DATETIME 字段中,当您检索该值时,您应该知道它属于哪个时区。

因此,在您的情况下,当您将值存储到 DATETIME 字段时,请确保它是坦桑尼亚时间。然后等你拿出来,就是坦桑尼亚时间了。好极了!

现在,棘手的问题是:当我执行 INSERT/UPDATE 时,如何确保该值是坦桑尼亚时间?两种情况:

  1. 你做INSERT INTO table (dateCreated) VALUES (CURRENT_TIMESTAMP or NOW())

  2. 您这样做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',那么对您有好处,这正是将存储到数据库的内容。如果您传递某种类型的 Date 对象,那么它将取决于数据库驱动程序如何解释该 Date 对象,以及它最终提供给 MySQL 用于存储的 'YYYY-MM-DD HH:mm:ss' 字符串。数据库驱动程序的文档应该告诉你。

  • 案例#1 是错误的。MySQL 不会将日期时间列的值转换为 UTC。它们按原样存储。 (6认同)

Imt*_*que 6

MySQL 8.0.19开始,您可以在向表中插入值TIMESTAMP时指定时区偏移量。DATETIME

偏移量附加到日期时间文字的时间部分,中间没有空格,并使用与设置time_zone系统变量相同的格式,但有以下例外:

  • 对于小于 10 的小时值,需要有前导零。
  • 值“-00:00”被拒绝。
  • 不能使用“ EET”和“ ”等时区名称;Asia/Shanghai' SYSTEM' 也不能在这种情况下使用。

插入的值的月份部分、日期部分或两者都不能为零。从 MySQL 8.0.22 开始强制执行此操作,无论服务器 SQL 模式设置如何。


例子:

此示例说明使用不同的 time_zone 设置将带有时区偏移的日期时间值插入到TIMESTAMPDATETIME列中,然后检索它们:

mysql> CREATE TABLE ts (
    ->     id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
    ->     col TIMESTAMP NOT NULL
    -> ) AUTO_INCREMENT = 1;

mysql> CREATE TABLE dt (
    ->     id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    ->     col DATETIME NOT NULL
    -> ) AUTO_INCREMENT = 1;

mysql> SET @@time_zone = 'SYSTEM';

mysql> INSERT INTO ts (col) VALUES ('2020-01-01 10:10:10'),
    ->     ('2020-01-01 10:10:10+05:30'), ('2020-01-01 10:10:10-08:00');

mysql> SET @@time_zone = '+00:00';

mysql> INSERT INTO ts (col) VALUES ('2020-01-01 10:10:10'),
    ->     ('2020-01-01 10:10:10+05:30'), ('2020-01-01 10:10:10-08:00');

mysql> SET @@time_zone = 'SYSTEM';

mysql> INSERT INTO dt (col) VALUES ('2020-01-01 10:10:10'),
    ->     ('2020-01-01 10:10:10+05:30'), ('2020-01-01 10:10:10-08:00');

mysql> SET @@time_zone = '+00:00';

mysql> INSERT INTO dt (col) VALUES ('2020-01-01 10:10:10'),
    ->     ('2020-01-01 10:10:10+05:30'), ('2020-01-01 10:10:10-08:00');

mysql> SET @@time_zone = 'SYSTEM';

mysql> SELECT @@system_time_zone;
+--------------------+
| @@system_time_zone |
+--------------------+
| EST                |
+--------------------+

mysql> SELECT col, UNIX_TIMESTAMP(col) FROM dt ORDER BY id;
+---------------------+---------------------+
| col                 | UNIX_TIMESTAMP(col) |
+---------------------+---------------------+
| 2020-01-01 10:10:10 |          1577891410 |
| 2019-12-31 23:40:10 |          1577853610 |
| 2020-01-01 13:10:10 |          1577902210 |
| 2020-01-01 10:10:10 |          1577891410 |
| 2020-01-01 04:40:10 |          1577871610 |
| 2020-01-01 18:10:10 |          1577920210 |
+---------------------+---------------------+

mysql> SELECT col, UNIX_TIMESTAMP(col) FROM ts ORDER BY id;
+---------------------+---------------------+
| col                 | UNIX_TIMESTAMP(col) |
+---------------------+---------------------+
| 2020-01-01 10:10:10 |          1577891410 |
| 2019-12-31 23:40:10 |          1577853610 |
| 2020-01-01 13:10:10 |          1577902210 |
| 2020-01-01 05:10:10 |          1577873410 |
| 2019-12-31 23:40:10 |          1577853610 |
| 2020-01-01 13:10:10 |          1577902210 |
+---------------------+---------------------+
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 遗憾的是,选择日期时间值时不会显示偏移量,即使在插入日期时间值时使用了偏移量。
  • 支持的偏移值范围为 -13:59 至 +14:00(含)。
  • 准备好的语句接受包含时区偏移量的日期时间文字作为参数值。
  • MySQL 将TIMESTAMP值从当前时区转换为 UTC 进行存储,并从 UTC 转换回当前时区进行检索。(对于其他类型,例如 ,不会发生这种情况DATETIME。)


Álv*_*lez 5

您描述的所有症状都表明您永远不会告诉MySQL使用什么时区,因此它默认为系统区域.想一想:如果它只是'2011-03-13 02:49:10',它怎么能猜到它是坦桑尼亚当地的约会呢?

据我所知,MySQL没有提供任何语法来指定日期中的时区信息.您必须根据每个连接更改它; 就像是:

SET time_zone = 'EAT';
Run Code Online (Sandbox Code Playgroud)

如果这不起作用(要使用命名区域,您需要将服务器配置为这样做而且通常不是这种情况),您可以简单地使用UTC偏移:

SET time_zone = '+03:00';
Run Code Online (Sandbox Code Playgroud)

  • 你不能只是拍打UTC偏移,因为它忽略了夏令时.你的时间很容易就会结束一小时的休息,没有希望纠正它!我知道SQL Server中的函数会将UTC转换为您的时区,但它没有考虑夏令时的任意转换.以UTC零偏移存储并在应用程序中进行转换 - 这非常干净地解决了很多问题,但是你会在ad-hoc查询中正确地转换日期. (14认同)