PostgreSQL中的时间、TIMETZ、Timestamp和TimestampTZ之间的区别

3,029 阅读24分钟

在Cockroach实验室,我们一直忙于使CockroachDB(CRDB)与PostgreSQL的v20.1版本更加一致。这使得你可以将Cockroach作为PostgreSQL的替代品(同时使用你最喜欢的现有ORM或驱动),但我们提供了强大的、可扩展的后端。

时间时间戳时间戳TZ是Cockroach支持的三种数据类型,但与PostgreSQL的功能并不匹配。我们还缺少TimeTZ数据类型,以及时间间隔类型的精度。更糟糕的是,对于我们支持的现有数据类型,我们的结果在某些情况下并不符合PostgreSQL。

v20.1版本中,我们花了很多时间来掌握时间数据类型,通过ORM测试和社区报告来修复损坏的部分,同时增加新的功能来弥补这一差距。我们花了大量的时间来弄清楚为什么现有的与时间相关的ORM测试在CRDB下会失败。我们在细节中发现了很多魔鬼,这些边缘案例的知识在旧的SQL邮件列表和StackOverflow中都很稀少。

在这篇博文中,我们将探讨PostgreSQL中的一些错综复杂的时间问题,并看看我们如何使用Go及其生态系统来重现这些功能。我们将分享我们对沿途使用时间的建议。

你对学习PostgreSQL世界中的时间感到兴奋吗?加入我们的TARDIS**(** SQL中的时间 相关 数据类型......博文)的同伴,跳进时间世界的冒险中。Allons-y!

当你打开与Postgres/CRDB的连接时,在你(客户端)和数据库(服务器)之间将有一个会话。这个会话将包含一个时区,你可以用SET TIME ZONE 命令为PostgreSQLCRDB设置。

当使用shell连接时,PostgreSQL shell(psql)将尝试连接并将会话设置为你的计算机设置所定义的本地时区,而CRDB将默认为UTC。我们可以通过CURRENT_TIMESTAMP 来观察这一点,它返回默认会话时区的当前时间戳。

otan=# SELECT CURRENT_TIMESTAMP;root@127.0.0.1:61879/defaultdb> SELECT CURRENT_TIMESTAMP;
psql shellCRDB shell

你可以指定你的时区为一个地点(它是知道夏令时的),或者一个UTC的偏移量。这将改变CURRENT_TIMESTAMP,使其处于你的会话设置的时区。

otan=# SET TIME ZONE 'Australia/Sydney';root@127.0.0.1:61929/defaultdb> SET TIME ZONE 'Australia/Sydney';
psql shellCRDB shell

在上面的例子中,CRDB和psql现在输出相同的数据,因为SET TIME ZONE被明确地设置为相同的东西。

注意你的ORM或驱动可能有与psql shell或CRDB shell不同的默认行为,但应该允许你设置一个默认的时区。

设置你的会话时区会以一些有趣和令人兴奋的方式影响一些时间操作(这不一定是你作为一个开发者想要的),我们将很快探索。

在上面的例子中,我们使用了SET TIME ZONE ,有一个整数偏移(-11)或一个地点(澳大利亚/悉尼)。然而,Postgres也支持SET TIME ZONE ,在前面有GMTUTC 的语法,例如:SET TIME ZONE 'UTC+3' 。让我们看看它是如何表现的。

otan=# SET TIME ZONE 'UTC+3';root@127.0.0.1:63094/defaultdb> 设置时区'UTC+3'。
psql shellCRDB shell

等一下,为什么在设置时区为UTC+3 时,它的时区是-3?

事实证明,这是因为前面有UTCGMT ,使用了POSIX对时区的定义,该定义将时区定义为GMT线以西的小时。实质上,这个符号与我们熟知的时区结构相反,正整数代表时间戳在格林尼治标准时间线以东(也被称为ISO8601标准)。注意,如果你只是在SET TIME ZONE中的偏移量上加上冒号分隔符,POSIX标准也适用。

otan=# SET TIME ZONE '+3:00';root@127.0.0.1:51387/defaultdb> 设置时区'+3:00'。
psql shellCRDB shell

这里的启示是,整数被视为ISO8601格式,位置做你所期望的,其他都是POSIX标准。

你觉得这有点奇怪吗?我们稍后会有另一个惊喜。

TIMESTAMP和TIMESTAMPTZ

TIMESTAMP(也称为TIMESTAMP WITHOUT TIME ZONE )和TIMESTAMPTZ(也称为TIMESTAMP WITH TIME ZONE )类型在CRDB中以64位整数作为自1970-01-01以来的微秒偏移量存储,在PostgreSQL中以64位整数的微秒偏移量存储(默认)。

由于这两种数据类型都只使用64位整数来存储,需要注意的是,这两种数据类型都没有存储任何时区信息。 那么,TIMESTAMPTZ从哪里获得时区?没错,就是会话时区。作为一个推论,这意味着存储TIMESTAMPTZ并从不同的会话时区获取结果,会产生不同的打印结果,其下有相同的等效UTC偏移。

让我们比较一下存储TIMESTAMP/TIMESTAMPTZ和从不同时区获取的结果。

otan=# CREATE TABLE time_comparison(t TIMESTAMP, ttz TIMESTAMPTZ)。root@127.0.0.1:56384/defaultdb> CREATE TABLE time_comparison(t TIMESTAMP, ttz TIMESTAMPTZ);
psql shellCRDB shell

在psql shell中,我们将这些条目插入的时间为2020-05-13 14:05:23.801845,偏移量为-07 。TIMESTAMP列不考虑时区,所以放弃了这个信息。当我们把时区切换到UTC-3 ,只有ttz 列会改变其结果,把时间转化为+3 的偏移量,即提前10小时,因此现在显示为2020-05-13 18:05:23.801845-03

在CRDB外壳中,事情看起来基本相同。然而,TIMESTAMP列在前面有一个多余的+00:00。这是使用支持CRDB shell的Go驱动lib/pq(与C库libpq无关)的一个用户体验怪癖--它在表示任何时间对象时总是显示一个时区,因为它对所有时间相关类型使用相同的格式字符串。我们希望在未来的版本中修复这个用户体验问题--但对它在下面表示相同的值感到安慰。

如果你仍然感到困惑,不要担心--这对我们来说也很困惑。让我们把它说清楚。

TIMESTAMP是一个绝对值的时间偏移。它不存储时区,也不根据会话的时区而改变。另一种思考方式是,它总是在UTC。

TIMESTAMPTZ也是一个绝对值的时间偏移,没有设置时区元数据,但它显示时间戳并在会话时区执行操作。你已经看到了 "显示时间戳 "的部分--我们稍后将讨论 "执行操作 "的部分。

在本节的剩余部分,我们将省略CRDB shell输出,以避免重复信息--但值得注意的是,我们在以下领域的所有方面都有错误,这就是我们如何解释它的原因。

解析

让我们用加州的一个新的psql shell来解析PostgreSQL中的悉尼奥运会的开幕式。

otan=# SELECT '2000-09-15 19:00':TIMESTAMP, '2000-09-15 19:00':TIMESTAMPTZ;
psql shell

所以解析一个没有任何时区信息的时间戳字符串,会自动默认为当前时区的TIMESTAMPTZ。

让我们在投递前把悉尼9月份的时区(+11:00)追加到字符串中。

otan=# SELECT '2000-09-15 19:00+11:00':TIMESTAMP, '2000-09-15 19:00+11:00':TIMESTAMPTZ;
psql shell

正如我们上面看到的。

  • 对于TIMESTAMPs,我们得到了19:00--与输入相同,但我们忽略了时区偏移。这对我们来说很重要,因为19:00-07与它存储的值不同--19:00+00。
  • 对于TIMESTAMPTZ,我们已经得到了01:00,这看起来很不对劲。请记住--TIMESTAMPTZ是一个UTC偏移量,它显示在会话时区。我们用+11:00来解析TIMESTAMPTZ(它被解析为ISO8601格式,不像SET TIME ZONE ),但我们在时区-07:00显示TIMESTAMPTZ,因为我们在加利福尼亚。由于有18个小时的时差,这使得开幕式在加州当地时间凌晨1点举行,你可以从输出中看到。美国读者,你们在2000年观看奥运会时一定很累。

当然,用同样的字符串明确地改变会话时区将改变TIMESTAMPTZ的输出。

otan=# SET TIME ZONE 'Asia/Tokyo';
psql shell

同样,解析TIMESTAMP时的时区是完全被忽略的,但是对于东京的时区+09:00,我们不得不将时钟从19:00倒退到17:00,以便在会话时区正确显示。

铸造

让我们看一下TIMESTAMP和TIMESTAMPTZ之间的一些投射,其中有一个最伟大的板球测试赛结束的时间。

otan=# SET TIME ZONE 'Australia/Adelaide';
psql shell

当我们在案例1中从TIMESTAMP转换到TIMESTAMPTZ时,我们必须设置时区为+10:30。然而,这实际上改变了UTC时间,因为在当前会话时区 "正确显示 "涉及从基础偏移值中减去10:30。

相反,当我们在案例2中从TIMESTAMPTZ转换到TIMESTAMP时,TIMESTAMP代表一个绝对的UTC值,但没有时区信息。因此,我们必须在基础偏移值上加上10:30,以获得正确的等值,使其等于17:00的值。

这种行为变得很棘手,特别是当数据在另一个时区的会话中被存储和获取时,同时希望投向TIMESTAMP的结果是一样的。

otan=# SET TIME ZONE 'Australia/Adelaide';
psql shell

在上面的例子中,我们把上面的时区从+10:30改为芝加哥的-06:00,所以我们有16:30的时间差。由于我们使用TIMESTAMPTZ数据类型投递到TIMESTAMP,我们在评估时按照会话时区评估,因此在美国/芝加哥投递时得到2006-12-05 00:30:00。注意,如果你想让一个结果无论在哪个时区都评估为同一个TIMESTAMP,请使用下面讨论的AT TIME ZONE语法。

对于许多这样的操作,我们需要将时间戳移动到不同的时区,但同时保持相同的时间戳。Go本身并不能做到这一点,因为time.In只改变了位置,但下面还有相同的UTC偏移。这意味着在10:00投到一个+3时区的东西,如果没有任何偏移量的变化,就会报告为13:00+3而不是10:00+3。这是我们很多时间错误的源头。

为了正确处理这个问题,我们必须做一个尴尬的动作:从Zone中读取第二个参数,以获得前后时区的秒数偏移,然后使用time.Add来减去新时间值的持续时间偏移。

示例代码。

精度

TIMESTAMP和TIMESTAMPTZ支持微秒级精度。然而,有一个选项可以对秒成分的小数位进行 "舍入"。

要做到这一点,我们可以在TIMESTAMP和TIMESTAMPTZ后面的括号中指定一个0到6之间的数字,例如:TIMESTAMP(3)用于允许3个小数位的毫秒精度的TIMESTAMP,或者TIMESTAMPTZ(0)用于没有小数位的TIMESTAMPTZ。这将得到四舍五入到指定精度的数据。

让我们看一下使用超级大盘规则不公平时的例子。

otan=# SELECT '2019-07-14 17:00:00.545454':TIMESTAMP(0); -四舍五入
psql shell

Go中的同等功能可以使用时间库中的time.Round来实现。

函数和操作符

函数(例如:extract,date_trunc )和操作符(例如:=,+,-,> )在TIMESTAMP中是相当容易理解的。然而,它们与TIMESTAMPTZ有一些细微的差别,导致CRDB内部出现各种错误。正如我们前面提到的,重要的是要记住TIMESTAMPTZ是在会话时区执行操作

让我们看看一些时间运算符,其时间戳是在电影《鳄鱼邓迪》发行前后。

otan=# SET TIME ZONE 'America/New_York';
psql shell

记住--在所有情况下,TIMESTAMP被转换为当前会话时区的TIMESTAMPTZ,即纽约的'1986-09-26 10:00-04'。看一下每个案例。

  • 情况1:在转换为TIMESTAMPTZ时,TIMESTAMP增加了-04,意味着这个结果是真的。
  • 情况2:它们内部都是相同的时间偏移,因此它们是相等的。
  • 情况3:10:00-05可以被认为是11:00-04,严格意义上来说,它更高,因此返回true。
  • 情况4:增加一个1天的间隔,使日期增加1。

对于内置程序,让我们看看extract

otan=# SET TIME ZONE 'America/New_York';
psql shell

从上面的内容来看。

  • 情况1:小时可以直接提取为 "10"。
  • 情况2:由于提供的时间戳与会话时区相同,计算时不需要任何时区转换,但底层的UTC偏移有不同的小时。值得庆幸的是,Go的time.Hour操作符(以及Minute、Second、Month等)考虑到了时间所处的位置,所以提取小时是很简单的。
  • 案例3:这里发生了什么?记住--TIMESTAMPTZ在会话时区执行操作。如果我们运行SELECT '1986-09-26 10:00-06'::TIMESTAMPTZ ,我们会看到1986-09-26 12:00-04,因为-04是会议时区。 因此,当把它从-02移到-04时,时间变成了12:00--由于我们在会话时区执行操作--因此extract将返回12。

AT TIME ZONE将把一个TIMESTAMPTZ转换成给定时区的TIMESTAMP,或者把一个TIMESTAMP转换成给定时区的TIMESTAMPTZ(它将被转置到会话时区)。值得注意的是,TIMESTAMP AT TIME ZONE和TIMESTAMPTZ AT TIME ZONE是互为倒数的。

如果你希望用户从TIMESTAMPTZ转换到TIMESTAMP,或者从不同的会话时区转换到TIMESTAMP,但你需要与纪元时间保持一致的偏移,这可能很有用(关于这可能是一个问题的例子,见 "转换")。

令人困惑吗?让我们看看真实的例子,使用歌曲星期五的发行日期。

otan=# 设置时区 "澳大利亚/悉尼"。
psql shell

从上面的情况来看。

  • 案例1:我们要从澳大利亚/悉尼时间切换到亚洲/东京时间,后者比我们晚2小时。这涉及到从上午10点转移到上午8点,但在删除UTC时区的偏移后,绝对的 "TIMESTAMP "值为上午8点。
  • 案例2:我们有一个TIMESTAMP,我们希望转换为悉尼时间。这很简单--在时间上加上UTC的偏移量,当我们在 "澳大利亚/悉尼 "的会话时区显示时,仍然是上午10点。
  • 案例3:案例3变得很有趣。我们将亚洲/东京时区的偏移量加入到基础偏移量中,但请记住我们在会话时区显示这个偏移量。由于有两个小时的时差,这意味着我们在澳大利亚/悉尼的会话时区显示这个操作时,看到的是下午12点。

POSIX标准再次出击

还记得之前在使用SET TIME ZONE时,POSIX标准被用于字符串的惊喜吗?在AT TIME ZONE中,省略UTC/GMT前缀,仅有一个光秃秃的整数偏移(例如:+3,-3,3 )也表现为POSIX时间戳(不像SET TIME ZONE,整数是特殊的,表现为ISO8601)。

otan=# SET TIME ZONE '+3';
psql shell

在使用AT TIME ZONE时,如果是ISO8601,我们会期望上述情况是2011-03-14 10:00:00+03:00 。然而,由于+3 是用于AT TIME ZONE的POSIX,它实际上意味着 "在GMT以西3小时的时区",显示为 "UTC以东3小时",因此在结果中增加了6小时。

夏令时

现在你可能想知道什么时候使用位置与什么时候使用绝对偏移。鉴于它使用的是POSIX风格的偏移量,偏移量似乎已经很棘手了。

这可能有助于你决定--地点可以推断出变化的时区信息。让我们看看一个穿越芝加哥时区的日期。

root@127.0.0.1:64900/defaultdb> SET TIME ZONE 'America/Chicago';
psql shell

随着夏令时界限的变化,我们可以看到时区的偏移量发生了变化。这很巧妙,不是吗?

你现在可能想知道--这些时区变化是如何编码的?IANA维护着一个每个时区的夏令时变化的数据库(其中一些可以追溯到很久之前)。但我们使用的是这个数据库的哪个版本?好吧。

  • CRDB将使用安装在你的电脑中的这个数据库的副本。这是Go的默认行为。
  • PostgreSQL每个版本都会推出自己的tzdata副本。

这意味着在使用CRDB时,如果你的系统上有较新版本的IANA数据库,时间和夏令时的行为会在不同的电脑上发生变化。这一点目前正在跟踪修复,这也是我们建议始终使用UTC作为会话时区的原因之一。

带有夏令时的间隔数学

让我们看看夏令时是如何影响时间间隔计算的。

otan=# 设置时区 "美国/芝加哥"。
psql shell

咦--一天中没有24小时吗?Postgres中的时间间隔被表示为 "月"、"日 "和 "秒",这在我们看到的情况中起了作用。让我们看看这如何适用于上面的案例。

  • 案例1:当向TIMESTAMPs(由非天的单位表示,即24小时仍然使用秒,直到它溢出)添加 "秒 "时,我们添加了真实世界的秒。这在Go中通过使用time.Add完成。
  • 情况2:当添加 "天 "时,我们只是将任何数学运算直接放入日期字段,即使跨越夏令时的障碍,也会保留相同的时间。这在Go中是由time.AddDate来处理的。
  • 案例3:与案例2类似,但我们添加的是月而不是天。

Y2K38问题

2038-01-19 03:14:07是否激发了任何千年虫的感觉?这是时间在unix偏移量1970-01-01之后的2147483647(max int32)秒,被称为Y2K38日期。这是在Go中处理2038年以后的tzdata时的一个问题,这个问题刚刚被解决。

由于Go 1.13不能理解tzdata的 "扩展 "格式,该格式可以处理2000年后的时区,因此在v20.1(与Go 1.13一起发布)中,我们对夏令时的处理在2000年后被破坏。我们也不能轻易解析它,因为Go的Location结构不是一个接口,而且要改变的相关变量是私有的,如果不分叉Go的时间库就无法访问。

因此,如果我们通过Y2K38,CRDB中的时区将与你当前时区的夏令时相同,因为它在Go中被评估为夏令时。如果使用有夏令时的时区,这可能是一个问题。

otan=# SET TIME ZONE 'America/Chicago';root@127.0.0.1:57882/defaultdb> SET TIME ZONE 'America/Chicago';
psql shellCRDB shell

这是 为将来的修复而跟踪的。

作为一个附带说明--如果你对tzdata感到好奇,它将是一个有趣的读物。看看一些奇怪的偏移量,比如1900年以前的美国/芝加哥,时间偏移量为-5:50:36

对时间感到自信吗?觉得你可能会变成一半是人,一半是时间领主?

让我们看看--你能解释以下行为吗。

otan=# 设置时区'-9'。
psql shell

答案就在这篇博文的最后。

我们的建议是什么?

像其他许多人一样,CRDB推荐使用TIMESTAMPTZ,因为对时区数据进行编码是很有价值的。然而,我们建议始终将会话时区设置为UTC。这使得用户不必担心在解析时不会丢失时区信息,同时消除了用户的顾虑,即如果用户决定使用会话时区,他们会执行预期的夏令时行为。

时间和TIMETZ

TIME(也称为TIME WITHOUT TIME ZONE)和TIMETZ(也称为TIME WITH TIME ZONE)都只存储TIMESTAMP的一天的时间部分。但是。

  • TIME仍然用8个字节编码,代表从午夜开始的微秒。
  • TIMETZ是用12个字节编码的,其中8个字节代表自午夜以来的微秒,4个字节用于存储UTC以西的时区偏移量(同样,与我们在谈论时区时习惯的情况相反)。与TIMESTAMPTZ不同,它不考虑当前会话的时区(除了解析),而且由于它只存储时区偏移而不是位置,所以它不对夏令时信息进行编码。

我们来看看在psql(默认为加州时间)和CRDB中使用CURRENT_TIME(相当于CURRENT_TIMESTAMP)的情况。

otan=# CREATE TABLE timetz_example (t time, ttz timetz);root@127.0.0.1:56478/defaultdb> CREATE TABLE timetz_example (t time, ttz timetz);
psql shellCRDB shell

正如你所看到的,改变时区并不影响表的结果。因为TimeTZ存储的是偏移量,它们在会话时区转移之间保持不变。

这里值得注意的是,CRDB为时间类型输出了一个额外的 "0000-01-01",以及一个不相干的 "+00:00 "的时间类型。这个数据没有任何意义,是我们CRDB shell的驱动lib/pq显示这个数据的方式。这个问题将被跟踪,以便将来修复。

当对时间进行间隔计算时,超过23:59:59.999999的时间会自动溢出到00:00:00,因为没有 "日期 "成分。

otan=# select '10:00':time + '14 hours':interval;
psql shell

解析

解析TIME和TIMETZ的行为与解析TIMESTAMP和TIMESTAMPTZ的行为基本相同。

  • 对于TIME,任何时区偏移都被忽略。
  • 对于TIMETZ,如果没有指定时区,则将当前会话时区附加到TIMETZ中(根据夏令时的变化)。 然而,如果为TIMETZ指定了一个时区偏移,它将使用该时区。时区偏移使用熟悉的ISO8601标准。
otan=# 设置时区 "澳大利亚/悉尼"。
psql shell

精度

与TIMESTAMP/TIMESTAMPTZ类似,你可以在括号中为TIME/TIMETZ类型指定精度,对于秒数部分,它将四舍五入到指定精度的小数位。

otan=# select '17:00:00.545454'::time(0), '17:00:00.545454+03'::timetz(0); - 舍入
psql shell

铸造

当铸造TIME到TIMETZ时,TIME将被提升到你当前会话的时区。然而,当把TIMETZ投给TIME时,我们将失去时区偏移。

otan=# SET TIME ZONE 'Australia/Sydney';
psql shell

丢失时区偏移并在会话时区中重新解释它可能是令人惊讶的,因为将TIME投回TIMETZ会给你一个不同的结果。换句话说,铸造逆的逆,并不能产生相同的结果。你可以看到下面的一个例子。

otan=# 设置时区 "澳大利亚/悉尼"。
psql shell

在这里,我们的TIMETZ中的+03已经被重新解释为会话时区的+11,在将其铸入TIME后,这代表了一个完全不同的结果。如果你需要反相匹配,在TIME和TIMETZ之间的AT TIME ZONE将产生预期的效果。

比较器和TIMETZ的排序

考虑比较这两个相等的时间(10:00+03和11:00+04),一个在同一时区,一个在不同时区。

otan=# SELECT '10:00+03'::timetz = '10:00+03'::timetz; - Case 1
psql shell

这很有趣--它们在现实世界中是同一时间,不是吗?但在TimeTZ的领域里不是。回顾一下,TimeTZ同时存储了微秒偏移和代表UTC以西秒数的偏移。如果微秒偏移量相同,那么我们就比较UTC以西的秒数(也就是我们上面看到的偏移量的负数)。因此。

  • 案例1表明,相同的UTC偏移量和时区偏移量是相等的,意味着结果是相等的,正如预期的那样。
  • 案例2表明,尽管有相同的UTC偏移量,但结果并不相等,因为偏移的时区不一样。
  • 案例3表明,"更西 "的时区(有更高的POSIX偏移量)有优先权。
  • 案例4表明,UTC的偏移量优先,因为11:01+04相对于UTC来说比10:00+03高一分钟。

只有TIME和TIMETZ支持的一个有趣的特性是24:00:00时间。这是一个可以解析的时间,但你不能用算术来实现这个值。加到24:00:00后,数值会溢出到00:00:00。

otan=# SELECT '24:00':时间; - 24:00的时间可以这样解析
psql shell

不幸的是,Go的time.Parse不能处理24:00 - 所以我们的TIME字符串解析器都有包装器来重新匹配和处理24:00的情况。

用Go的时间库解析和显示24:00的时间也是一个挑战,这可以在lib/pq驱动中得到证明。不幸的是,我们使用的lib/pq驱动显示的24:00与00:00的表示方法完全一样。

root@127.0.0.1:57021/defaultdb> SELECT '24:00':时间,'00:00':时间。
psql shell

我们已经提交了一个 PR 作为修复,但在那之前,lib/pq 驱动程序中的 24:00 时间是坏的,即使是针对 PostgreSQL。

我们有什么建议?

虽然TIME和TIMETZ只存储时间部分,但TIME需要同样的空间--对于TimeTZ来说甚至更多。此外,TIMETZ也没有用时间偏移量来跟踪位置。24:00时间是一个有趣的情况,它被处理了,但可能实际用途有限。

还值得注意的是,PostgreSQL建议不要使用TIMETZ数据类型。TIMETZ最初是为了遵循SQL标准而实现的。参见PostgreSQL自己的文档中第8.1节上面的说明

所以我们的建议是使用......两者都不使用!如果你需要时间,你很可能最好使用TIMESTAMPTZ,它在所有这些情况下都能照顾到这些不足之处。如果需要时区信息,使用一个单独的列来编码该信息。

会话时区被设置为-9。这意味着1947-12-13 13:00+11 将被翻译成1947-12-12 17:00:00-09

现在UTC+3是POSIX标准,意味着它实际上是指UTC以西3小时(ISO8601中的"-3")。这比"-9 "的会话时区早六个小时,因此翻译成23:00:00。由于AT TIME ZONE将TIMESTAMPTZ转换为TIMESTAMP,所以时区数据就消失了。

(如果你感到好奇,1947-12-13是臭名昭著的曼卡德事件的日期)。

总结

每种时间类型都有有趣的细微差别。

  • TIMESTAMP是一个带有日期和时间的类型,没有时区信息,是绝对的。它在解析时不考虑时区,这可能是意想不到的。另外,可以把它看成是 "总是UTC时间"。
  • TIMESTAMPTZ是一个有日期和时间的类型,它的时区行为取决于你的会话时区,并有意识地考虑到夏令时。如果你想安全起见,总是使用UTC。
  • TIME是一个只包含一天中的时间信息的类型。
  • TIMETZ是一个具有一天中的时间和固定时区偏移的类型。它不依赖于会话时区的计算,也不具有夏令时意识。

如上所述,我们的建议是始终使用TIMESTAMPTZ,将你的会话时区设置为UTC。如果你需要时区信息,使用一个单独的列来存储这个信息。在一天结束时,确保你选择的东西对你有用。

呼,这真让人困惑。也许我应该写一封措辞强烈的信。无论如何,与时间数据类型打交道确实很有趣--我们甚至还没有触及时间间隔和闰秒

你喜欢我们的冒险和对数据库的内部和细微差别的深入研究吗?像往常一样,我们正在招聘!