我应该在PostgreSQL数据库中select哪种时间戳types?

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

我可以

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

我对选项3(带时区的时间戳)略有偏好,但希望对此有一个有教养的意见。

首先,PostgreSQL的时间处理和算术是非常棒的,在一般情况下,选项3是好的。 然而,这是对时间和时区的不完整的看法,可以补充:

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

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

就像选项3一样, WITH TIME ZONE的原因是因为事情发生的时间是绝对时间。 WITHOUT TIME ZONE产生相对时区。 永远不要混合绝对和相对的TIMESTAMPs。

从编程和一致性的angular度来看,确保所有计算都是使用UTC作为时区。 这不是PostgreSQL的要求,但它与其他编程语言或环境集成时有帮助。 在列上设置CHECK以确保写入时间戳记列的时区偏移量为0是一个防御位置,可以防止几个类的错误(例如,脚本将数据转储到文件中,并对时间数据进行sorting使用词法sorting)。 同样,PostgreSQL不需要这个来正确地进行date计算或者在时区之间进行转换(即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对时间戳和时区的处理非常棒(尽pipe这里和那里有一些“特性”)。 例如,一个这样的特征:

 -- 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'剥离时区信息,并使用您的目标的参考帧( UTC )创build相对的TIMESTAMP WITHOUT TIME ZONE

从不完整的TIMESTAMP WITHOUT TIME ZONE转换为TIMESTAMP WITH TIME ZONE ,从您的连接inheritance缺less的时区:

 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 (ie 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时间视为input错误
  • 从不混合和匹配相对和绝对时间戳
  • 如果可能的话,也使用UTC作为数据库中的timezone

随机编程语言注意:Python的datetime数据types非常适合于保持绝对时间与相对时间的区别(尽pipe首先令人沮丧,直到用PyTZ这样的库来补充它)。


编辑

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

绝对时间用于logging事件。 示例:“用户123login”或“gradle典礼开始于2011-05-28下午2点PST”。 无论您当地的时区如何,如果您可以传送到事件发生的地方,您可以目睹事件的发生。 大多数时间数据库中的数据是绝对的(因此应该是TIMESTAMP WITH TIME ZONE ,理想情况下+0偏移量和文本标签代表特定时区的规则 – 而不是偏移量)。

相对事件是从一个尚未确定的时区的angular度来logging或安排某个事物的时间。 例如:“我们的商业大门早上8点开门,晚上9点关门”,“每星期一早上七点开会,每周早餐会”,或者“每晚八点的万圣节”。 一般来说,相对时间用于事件的模板或工厂,绝对时间用于几乎所有的事情。 有一个罕见的例外,值得指出哪些应该说明相对时代的价值。 对于未来可能发生事件的绝对时间可能不确定的未来事件,请使用相对时间戳。 这是一个真实世界的例子:

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

其他/最后一种相对时间是INTERVAL 。 例如:“会话在用户login20分钟后超时”。 INTERVAL可以与绝对时间戳( TIMESTAMP WITH TIME ZONE )或相对时间戳( TIMESTAMP WITHOUT TIME ZONE )正确使用。 “用户会话在成功login20分钟后(login_utc + session_duration)”或“我们的早餐早餐会议只能持续60分钟(recurring_start_time + meeting_length)”同样正确。

最后一点混乱: DATETIMETIME WITH TIME ZONETIME WITH TIME ZONE都是相对数据types。 例如: '2011-05-28'::DATE表示相对date,因为您没有可用于识别午夜的时区信息。 类似地, '23:23:59'::TIME是相对的,因为你不知道时间或时间表示的DATE 。 即使是'23:59:59-07'::TIME WITH TIME ZONE ,你也不知道DATE是什么。 最后,具有时区的DATE实际上不是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) 

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

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

事实上,“WITH TIME ZONE”和“WITHOUT TIME ZONE”都将该值存储为类似unix的绝对UTC时间戳。 不同之处在于时间戳的显示方式。 当“WITH时区”时,显示的值是转换到用户区域的UTC存储值。 当“无时区”时,UTC存储值被扭曲以显示相同的时钟面,而不pipe用户设置了哪个区域“。

“无时区”可用的唯一情况是无论实际区域如何,时钟面值都适用。 例如,当时间戳指示投票间何时可能closures(即,不pipe个人的时区,他们在20:00closures)。

使用select3.总是使用“WITH时区”,除非有一个非常具体的原因不。

我的意向是选项3,因为Postgres可以为您重新计算相对于时区的时间戳,而另外两个则需要您自己去做。 用时区存储时间戳的额外存储开销实际上是微不足道的,除非您正在谈论数百万条logging,在这种情况下,您可能已经拥有相当丰富的存储需求了。