Postgres 时间戳

Posted

技术标签:

【中文标题】Postgres 时间戳【英文标题】:Postgres Timestamp 【发布时间】:2013-01-14 22:24:50 【问题描述】:

我们正在讨论在 postgres 中存储时间戳的最佳方式。目前,所有时间戳都存储为 +00,并且我们有一个与每个客户端关联的时区。我们查找时区并转换发生某事的时间,这会增加复杂性,因为我们需要进行更多的连接和更复杂的查询。

另一种方法是连接到 Postgres 并设置连接的时区,它会一直更改为该时区。

我的问题是在 ANZ 有 4-5 个时区。当我们尝试开具发票时,我们需要知道某些交易发生在哪一天,并且跨三个时区没有完美的解决方案。

我正在考虑在时间戳中包含时区以使其更容易 - TIMESTAMP '1999-01-15 8:00:00 -8:00'

我的印象是这是最佳做法,但有人说这是个坏主意。我们将在 ANZ 有客户,我们需要为其制作准确的发票,什么是最好的解决方案和最优雅的解决方案?

干杯 斯科特

【问题讨论】:

PostgreSQL docs: "我们不建议使用带时区的时间类型(尽管 PostgreSQL 支持旧应用程序并符合 SQL 标准)。" time with timezone 不推荐,因为它不使用日期来限定保存它的时间段。没有这个,夏令时和其他因素就无法计算 - 因此它为什么不是推荐的。然而timestamp with timezone 是一种不同的数据类型,由于日期的存在,它可以保存其相对于 UTC 的所有条目,并根据用户的偏好调整时差。确保您的服务器时间是正确的,并且所有条目都设置为正确的标准,并且您的时间也是正确的。 如果您需要存储时区,您需要添加一列来保存它。 timestamptz 不存储时区信息。 ANZ 是什么/在哪里?也许Australia/New Zealand? 【参考方案1】:

为您的输入字段使用timestamptz(或timestamp with time zone 用于标准SQL 语法),然后您可以使用适合您偏好的时区或时间偏移为每次插入设置自定义时间偏移。

例子……

CREATE TABLE "timetest"
(
"timestamp" timestamptz
);

INSERT INTO "timetest" ("timestamp") VALUES ('2013-01-01 08:45:00 PST');
INSERT INTO "timetest" ("timestamp") VALUES ('2013-01-01 08:45:00 Europe/Madrid');
INSERT INTO "timetest" ("timestamp") VALUES ('2013-01-01 08:45:00 Europe/Athens');
INSERT INTO "timetest" ("timestamp") VALUES ('2013-01-01 08:45:00 GMT+11');
INSERT INTO "timetest" ("timestamp") VALUES ('2013-01-01 08:45:00 GMT-11');
INSERT INTO "timetest" ("timestamp") VALUES ('2013-01-01 08:45:00 UTC');

...你的时间会相应调整

SELECT * FROM "timetest"; -- note this may default to your timezone
------------------------
[timestamp]
------------------------
2013-01-01 16:45:00+00
2013-01-01 07:45:00+00
2013-01-01 06:45:00+00
2012-12-31 21:45:00+00
2013-01-01 19:45:00+00
2013-01-01 08:45:00+00
2013-01-01 08:45:00+00

或者更好的是,尝试以下...

SELECT 
"timestamp" AT TIME ZONE 'Australia/Sydney' AS "Sydney", 
"timestamp" AT TIME ZONE 'Australia/Perth' AS "Perth" 
FROM "timetest";
--------------------------------------------
[Sydney]..............[Perth]
--------------------------------------------
2013-01-02 03:45:00 - 2013-01-02 00:45:00
2013-01-01 18:45:00 - 2013-01-01 15:45:00
2013-01-01 17:45:00 - 2013-01-01 14:45:00
2013-01-01 08:45:00 - 2013-01-01 05:45:00
2013-01-02 06:45:00 - 2013-01-02 03:45:00
2013-01-01 19:45:00 - 2013-01-01 16:45:00

最后,要了解数据库可用的时区列表,请尝试:

SELECT * FROM pg_timezone_names ORDER BY utc_offset DESC; 

【讨论】:

【参考方案2】:

这里没有防弹的解决方案。

我的第一个建议:永远不要依赖服务器的默认时区。

我的第二个建议:根据数据的(主要)语义在timestamp-timestamptz 之间进行选择。

更详细: PostgresSQL 有两个时间戳变体,混淆地命名为 TIMESTAMP WITHOUT TIMEZONE (timestamp)TIMESTAMP WITH TIMEZONE (timestamptz)。实际上,既不存储时区,也不存储偏移量。两种数据类型占用相同的宽度(4 个字节),它们的区别很微妙——更糟糕的是,如果你不完全理解它们并且你的服务器更改了时区,它们可能会咬你。我的理智规则集是:

使用TIMESTAMP WITH TIMEZONE (timestamptz)存储主要与“物理”时间相关的事件,您主要有兴趣查询event 1event 2 之前(不考虑时区),或计算时间间隔(以“物理单位”,例如秒;而不是“民用”单位,如天-月等)。典型的例子是记录创建/修改时间——通常是“时间戳”这个词的意思。

使用TIMESTAMP WITHOUT TIMEZONE (timestamp)存储相关信息为“公民时间”的事件(即字段year-month-day hour-min-sec作为一个整体),并且查询涉及日历计算。在这种情况下,您将只在此处存储“本地时间”,即相对于某些未指定(不相关、隐含或存储在其他地方)时区的日期时间。

第二个选项使您更容易查询,例如“在 '2013-01-20' 日发生的所有事件”(在每个相应的地区/国家/时区) - 但更难查询“在参考事件之前(物理上)发生的所有事件”(除非我们知道它们位于同一时区)。你选择。

如果您需要完整的东西,两者都不够,您需要将时区或偏移量存储在附加字段中。另一种浪费几个字节但查询效率更高的选择是存储这两个字段。

另见this answer。

【讨论】:

感谢您指出字段名称中的混淆,并且没有人存储偏移量!事实上,在将日期保存到timestamp with time zone 列时,它在 Postgres TODO wiki 上实际存储时区偏移量。 wiki.postgresql.org/wiki/Todo#Dates_and_Times 同时感谢您指出困惑! @DeanRadcliffe TODO 已经很老了(我敢打赌超过 5 年),所以我不会屏住呼吸......公平地说,我不知道是否有一些关系 SQL服务器已经实现了。 澄清区别:TIMESTAMP WITH TIME ZONE 使用任何传递的偏移量从 UTC 或时区信息调整到 UTC。相比之下,TIMESTAMP WITHOUT TIME ZONE 忽略任何偏移量或时区,无论是否包含。如果您想要大致了解日期时间,例如“圣诞节从 2015 年 12 月 25 日午夜开始”,请仅使用 WITHOUT .请参阅此专家的帖子Always Use TIMESTAMP WITH TIME ZONE。

以上是关于Postgres 时间戳的主要内容,如果未能解决你的问题,请参考以下文章

postgres时间戳从bigint数据类型中提取日期

javascript + postgres:时区和时间戳的使用

Postgres 通过时区转换防止时间戳

如何从 postgres 获取时间戳作为 Unix 纪元?

Postgres:获取最大值和最小值,以及它们出现的时间戳

在 Postgres 中将时间戳截断为 5 分钟的最快方法是啥?