我应该在PostgreSQL数据库中选择哪种时间戳类型?


119

我想定义一个最佳实践,以便在多时区项目的上下文中将时间戳存储在Postgres数据库中。

我可以

  1. 选择TIMESTAMP WITHOUT TIME ZONE并记住此字段在插入时使用的时区
  2. 选择TIMESTAMP WITHOUT TIME ZONE并添加另一个字段,该字段将包含在插入时使用的时区的名称
  3. 选择TIMESTAMP WITH TIME ZONE并相应地插入时间戳

我对选项3(带时区的时间戳记)略有偏爱,但希望对此事有个好的见解。

Answers:


142

首先,PostgreSQL的时间处理和算法很棒,在一般情况下Option 3很好。但是,它不是时区和时区的完整视图,可以补充以下内容:

  1. 将用户所在时区的名称存储为用户首选项(例如America/Los_Angeles,不是-0700)。
  2. 将用户事件/时间数据提交到其参考框架本地(很可能是与UTC的偏差,例如-0700)。
  3. 在应用程序中,将时间转换为UTC并使用TIMESTAMP WITH TIME ZONE列存储。
  4. 返回时间请求是本地时区(即从转换UTCAmerica/Los_Angeles)。
  5. 将数据库的设置timezoneUTC

此选项并不总是有效,因为很难获得用户的时区,因此很难TIMESTAMP WITH TIME ZONE为轻量级应用程序使用对冲建议。也就是说,让我更详细地说明此选项4的一些背景方面。

像选项3一样,之所以会这样,WITH TIME ZONE是因为某件事发生的时间是绝对的时间。WITHOUT TIME ZONE产生一个相对时区。永远,永远,永远都不会混合使用绝对时间戳和相对时间戳。

从程序和一致性的角度来看,请确保所有计算均使用UTC作为时区进行。这不是PostgreSQL的要求,但在与其他编程语言或环境集成时会有所帮助。CHECK在该列上设置a 以确保对时间戳列的写入具有一个时区偏移量,这0是一个防御位置,可以防止几类错误(例如,脚本将数据转储到文件中,而其他方式则使用词汇排序)。再一次,PostgreSQL不需要这样做来正确地进行日期计算或在时区之间进行转换(即PostgreSQL非常擅长在任意两个时区之间转换时间)。为了确保进入数据库的数据以零偏移量存储:

CREATE TABLE my_tbl (
  my_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  CHECK(EXTRACT(TIMEZONE FROM my_timestamp) = '0')
);
test=> SET timezone = 'America/Los_Angeles';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
ERROR:  new row for relation "my_tbl" violates check constraint "my_tbl_my_timestamp_check"
test=> SET timezone = 'UTC';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
INSERT 0 1

它不是100%完美的,但是它提供了足够强大的反纠错措施,以确保数据已经转换为UTC。关于如何执行此操作有很多意见,但是根据我的经验,这似乎是最佳实践。

对数据库时区处理的批评在很大程度上是合理的(有很多数据库以极大的能力来处理它),但是PostgreSQL对时间戳和时区的处理非常棒(尽管在这里和那里有一些“功能”)。例如,一种这样的功能:

-- Make sure we're all working off of the same local time zone
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 15:47:58.138995-07
(1 row)

test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:02.235541
(1 row)

请注意,这会AT TIME ZONE 'UTC'去除时区信息,并TIMESTAMP WITHOUT TIME ZONE使用目标的参照系(UTC)创建一个亲戚。

当从一个不完整的转换TIMESTAMP WITHOUT TIME ZONETIMESTAMP WITH TIME ZONE,缺少的时区从您的连接继承:

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
        -7
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
        -7
(1 row)

-- Now change to UTC    
test=> SET timezone = 'UTC';
SET
-- Create an absolute time with timezone offset:
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 22:48:40.540119+00
(1 row)

-- Creates a relative time in a given frame of reference (i.e. no offset)
test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:49.444446
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
         0
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
         0
(1 row)

底线:

  • 将用户的时区存储为命名标签(例如America/Los_Angeles),而不是UTC的偏移量(例如-0700
  • 除非有迫切的理由要存储非零偏移量,否则所有内容都应使用UTC
  • 将所有非零UTC时间视为输入错误
  • 永远不要混合和匹配相对和绝对时间戳
  • 如果可能,还UTC用作timezone数据库中的

随机编程语言说明:Python的datetime数据类型非常擅长保持绝对时间与相对时间之间的区别(尽管首先令人沮丧,直到您使用PyTZ之类的库对其进行了补充)。


编辑

让我解释一下相对和绝对之间的区别。

绝对时间用于记录事件。示例:“用户123登录”或“毕业典礼开始于太平洋标准时间2011-05-28 2pm。” 无论您所在的时区如何,只要您可以将其传送到事件发生的地方,就可以见证事件的发生。数据库中的大多数时间数据都是绝对的(因此TIMESTAMP WITH TIME ZONE,理想情况下应该是+0偏移量,并且带有表示特定时区的规则的文本标签-而不是偏移量)。

一个相对的事件是从一个尚待确定的时区的角度记录或安排某个时间。例如:“我们的公司的门在早上8点开门,在晚上9点关门”,“让我们在每个星期一的早上7点见面,每周一次早餐会,”或“每个万圣节晚上8点”。通常,相对时间在模板或工厂中用于事件,而绝对时间用于几乎所有其他事件。值得指出的是一个罕见的例外,它应该说明相对时间的价值。对于将来发生的事情,如果将来发生的事情足够远,并且绝对时间不确定,请使用相对时间戳。这是一个真实的例子:

假设现在是2004年,您需要安排在2008年10月31日下午1点在美国西海岸交货(即America/Los_Angeles/ PST8PDT)。如果您使用绝对时间来存储时间’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE,则交货时间将显示为下午2点,因为美国政府通过了2005年能源政策法案》,法案更改了控制夏令时的规则。在2004年安排交货时间时,日期10-31-2008应该是太平洋标准时间(+8000),但是从2005年起,时区数据库开始承认该日期10-31-2008应该是太平洋夏令时(+0700)。在时区中存储相对时间戳可能会导致正确的交付计划,因为相对时间戳可以避免国会不知情的篡改。使用相对时间和绝对时间进行事物调度之间的界限是一条模糊线,但是我的经验法则是,将来在3-6mo以外的时间进行任何事物的调度都应使用相对时间戳(已调度=绝对vs计划=相对???)。

相对时间的另一种/最后一种类型是INTERVAL。示例:“会话将在用户登录后20分钟后超时”。一个INTERVAL可以正确地与任一绝对时间戳(可以使用TIMESTAMP WITH TIME ZONE)或相对时间戳(TIMESTAMP WITHOUT TIME ZONE)。同样正确的说法是:“用户会话在成功登录后20分钟到期(login_utc + session_duration)”或“我们的早餐会只能持续60分钟(recurring_start_time + Meeting_length)”。

混乱的最后位:DATETIMETIME WITHOUT TIME ZONETIME WITH TIME ZONE都是相对的数据类型。例如:'2011-05-28'::DATE代表一个相对日期,因为您没有可用于识别午夜的时区信息。同样,'23:23:59'::TIME是相对的,因为您不知道时区或时间所DATE代表的时间。即使使用'23:59:59-07'::TIME WITH TIME ZONE,您也不知道DATE会是什么。最后,DATE时区实际上不是a DATE,而是一个TIMESTAMP WITH TIME ZONE

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 07:00:00
(1 row)

test=> SET timezone = 'UTC';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 00:00:00
(1 row)

在数据库中放置日期和时区是一件好事,但是很容易获得微妙的错误结果。 需要最少的额外努力来正确,完整地存储时间信息,但这并不意味着总是需要额外的努力。


2
如果您准确地告诉postgresql用户时间戳所在的正确时区,postgresql将在后台进行繁重的工作。自己转换它只是在麻烦。
塞斯·罗伯逊

1
@Sean-在您的检查约束下,如何插入不带时间戳记set timezone to 'UTC'?您知道所有知道时区的日期都存储在UTC内部吗?

2
检查的重点是确保存储的数据与UTC的偏移量为零。信息的分类和检索以及具有非零偏移量的时间比较容易出错。通过实施零UTC偏移,您可以从零角度以几乎零风险的方式从单个角度持续地与数据进行交互,并且在所有情况下都可以预期地表现出来。如果时间戳支持时区的文本表示是可行的,那么我对这个问题的看法将有所不同。:〜]
肖恩

6
@Sean:但是,正如杰克所指出的那样,所有时区感知时间戳基本上都存储在UTC内部,并在使用时转换为本地时区;有效地,提取(时区从...)将始终返回连接的本地时区:与时间戳的“存储”方式无关。换句话说,时区根本不是该类型的一部分,并且不能存储:“带时区”只是与其他类型进行交互时如何转换数据的属性。因此,该数据根本不表示时区,无论是文本形式还是其他形式。
杰伊·弗里曼-saurik- 2012年

@ JayFreeman-saurik-:你是绝对正确的。CHECK()是一种防脚踩措施,可防止可能出现的狡猾代码。确保数据在写入时是UTC,可以适当保证代码经过仔细考虑或正确设置了执行环境。
肖恩

58

肖恩的答案过于复杂和误导。

事实是“ WITH TIME ZONE”和“ WITHOUT TIME ZONE”都将值存储为类Unix绝对UTC时间戳。不同之处在于时间戳的显示方式。如果为“ WITH time zone”,则显示的值是转换为用户所在区域的UTC存储值。当“无时区”时,UTC存储值会发生扭曲,以便无论用户设置了哪个时区都显示相同的钟面。

“无时区”可用的唯一情况是,无论实际时区如何都适用钟面值。例如,当时间戳指示投票站何时关闭(即,无论某个人所在的时区如何,它们都在20:00关闭)。

使用选择3。除非有非常特殊的原因,否则始终使用“ WITH time zone”。


10
Postgres的主要专家David E. Wheeler将根据他的帖子“ 始终使用时区使用时区”同意您的评估。
罗勒·布尔克

2
如果让浏览器将UTC时间戳转换为本地时区怎么办?因此,数据库将永远不会进行转换,而仅包含UTC。是否可以接受“无时区”?
dman

5

我更倾向于选项3,因为Postgres可以为您完成重新计算相对于时区的时间戳的工作,而其他两个则需要您自己进行。除非您要谈论数百万条记录,否则存储带有时区的时间戳的额外存储开销实际上可以忽略不计,在这种情况下,您可能已经非常需要存储。


19
不正确 没有开销……Postgres不存储时区(顺便说一句,“偏移”是正确的术语,而不是时区)。该TIMESTAMP WITH TIME ZONE名称具有误导性。它的真正含义是“在插入/更新时请注意任何指定的偏移量,并使用该偏移量将日期时间调整为UTC”。该TIMESTAMP WITHOUT TIME ZONE名称的意思是“忽略插入/更新过程中可能出现的任何偏移,将日期和时间部分视为UTC,而无需进行调整”。仔细阅读文档
罗勒·布尔克

1
@BasilBourque感谢您提供此信息。非常有用。对于其他阅读此文档的人,文档中的行说:“在已确定为没有时区的时间戳的文字中,PostgreSQL将默默地忽略任何时区指示。也就是说,结果值来自于其中的日期/时间字段。输入值,并且未针对时区进行调整。“
艾丹·罗斯伍德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.