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

Jer*_*NER 117 postgresql timezone timestamp

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

我可以

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

我略微偏好选项3(带时区的时间戳),但希望对此事有一个受过教育的意见.

Sea*_*ean 141

首先,PostgreSQL的时间处理和算术非常棒,一般情况下选项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产生一个相对时区.永远不要混用绝对和相对的TIMESTAMP.

从编程和一致性的角度来看,确保使用UTC作为时区进行所有计算.这不是PostgreSQL的要求,但在与其他编程语言或环境集成时有帮助.CHECK在列上设置一个以确保写入时间戳列的时区偏移量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
Run Code Online (Sandbox Code Playgroud)

它不是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)
Run Code Online (Sandbox Code Playgroud)

请注意,AT TIME ZONE 'UTC'剥离时区信息并TIMESTAMP WITHOUT TIME ZONE使用目标的参照系(UTC)创建亲戚.

从不完整转换TIMESTAMP WITHOUT TIME ZONE为a时TIMESTAMP 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)
Run Code Online (Sandbox Code Playgroud)

底线:

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

随机编程语言注释:Python的datetime数据类型非常擅长于保持绝对时间与相对时间之间的区别(虽然在你用像PyTZ这样的库补充它之前一开始很令人沮丧).


编辑

让我来解释相对与绝对之间的差异.

绝对时间用于记录事件.示例:"用户123登录"或"毕业典礼从2011-05-28下午2点开始." 无论您当地的时区如何,如果您能够传送到事件发生的地方,您都可以目睹事件的发生.数据库中的大多数时间数据都是绝对的(因此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以外的任何事情的调度都应该使用相对时间戳(schedule = absolute vs planned =亲戚???).

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

混乱的最后位:DATE,TIME,TIME 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时区实际上不是一个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)
Run Code Online (Sandbox Code Playgroud)

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

  • @Sean:但是,正如Jack所说,所有时区感知的时间戳基本上都存储在UTC内部,并在使用时转换为您的本地时区; 实际上,提取(来自...的时区)将始终返回连接的本地时区:它与时间戳的"存储"方式无关.换句话说,时区根本不是类型的一部分,并且不能存储:"with time zone"只是在与其他类型交互时如何转换数据的属性.因此,数据根本没有表示时区,文本或其他. (6认同)
  • 如果你准确地告诉postgresql用户时间戳的正确时区,postgresql将在幕后进行繁重的工作.自己转换只是在借钱. (2认同)
  • 检查的目的是确保数据的存储与UTC的零偏移.信息的排序和检索以及具有非零偏移的时间比较是容易出错的.通过强制实施零UTC偏移,您可以以几乎零风险的方式从单一角度始终与数据进行交互,这种方式在所有情况下均可预测.如果时间戳支持时区的文本表示是实用的,那么我对这个主题的看法会有所不同.:〜] (2认同)

Jay*_*Jay 56

肖恩的回答过于复杂和误导.

事实是"WITH TIME ZONE"和"WITHOUT TIME ZONE"都将该值存储为类似unix的绝对UTC时间戳.区别在于时间戳的显示方式.当"WITH time zone"时,显示的值是转换为用户区域的UTC存储值.当"没有时区"时,UTC存储的值被扭曲,以便显示相同的钟面,无论用户设置了什么区域".

"没有时区"可用的唯一情况是,无论实际区域如何,时钟面值都适用.例如,当时间戳指示投票亭何时可能关闭时(即,它们在20:00关闭,而不管人的时区如何).

使用选择3.除非有特殊原因,否则请始终使用"带时区".

  • 主要的Postgres专家David E. Wheeler根据他的帖子[始终使用TIMESTAMP WITH TIME ZONE](http://justatheory.com/computers/databases/postgresql/use-timestamptz.html)同意您的评估. (10认同)
  • 如果您将浏览器将UTC时间戳转换为本地时区怎么办?因此,db将永远不会进行转换,只包含UTC."没有时区"是否可以接受? (2认同)

Gor*_*onM 5

我倾向于选项3,因为Postgres可以为你重新计算相对于时区的时间戳,而另外两个你必须自己做.使用时区存储时间戳的额外存储开销实际上可以忽略不计,除非您正在谈论数百万条记录,在这种情况下,您可能已经拥有相当丰富的存储要求.

  • 不正确.没有开销... Postgres确实**不存储时区**('offset'是正确的术语,顺便说一下,不是时区)."TIMESTAMP WITH TIME ZONE"这个名字具有误导性.它实际上意味着"在插入/更新时注意任何指定的偏移量,并使用该偏移量将日期时间调整为UTC"."TIMESTAMP WITHOUT TIME ZONE"名称表示"忽略插入/更新期间可能存在的任何偏移,将日期和时间部分视为UTC,无需调整".仔细阅读[doc](http://www.postgresql.org/docs/current/static/datatype-datetime.html). (17认同)