Answers:
首先,PostgreSQL的时间处理和算法很棒,在一般情况下Option 3很好。但是,它不是时区和时区的完整视图,可以补充以下内容:
America/Los_Angeles
,不是-0700
)。-0700
)。UTC
并使用TIMESTAMP WITH TIME ZONE
列存储。UTC
为America/Los_Angeles
)。timezone
为UTC
。此选项并不总是有效,因为很难获得用户的时区,因此很难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 ZONE
到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)
底线:
America/Los_Angeles
),而不是UTC的偏移量(例如-0700
)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)”。
混乱的最后位:DATE
,TIME
,TIME WITHOUT TIME ZONE
和TIME 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)
在数据库中放置日期和时区是一件好事,但是很容易获得微妙的错误结果。 需要最少的额外努力来正确,完整地存储时间信息,但这并不意味着总是需要额外的努力。
肖恩的答案过于复杂和误导。
事实是“ WITH TIME ZONE”和“ WITHOUT TIME ZONE”都将值存储为类Unix绝对UTC时间戳。不同之处在于时间戳的显示方式。如果为“ WITH time zone”,则显示的值是转换为用户所在区域的UTC存储值。当“无时区”时,UTC存储值会发生扭曲,以便无论用户设置了哪个时区都显示相同的钟面。
“无时区”可用的唯一情况是,无论实际时区如何都适用钟面值。例如,当时间戳指示投票站何时关闭(即,无论某个人所在的时区如何,它们都在20:00关闭)。
使用选择3。除非有非常特殊的原因,否则始终使用“ WITH time zone”。
我更倾向于选项3,因为Postgres可以为您完成重新计算相对于时区的时间戳的工作,而其他两个则需要您自己进行。除非您要谈论数百万条记录,否则存储带有时区的时间戳的额外存储开销实际上可以忽略不计,在这种情况下,您可能已经非常需要存储。