当数据类型是带时区和没有时区时,时间戳值在PostgreSQL中存储不同吗?这些差异可以用简单的测试用例来说明吗?


当前回答

时间戳vs时间戳

Postgres中的timestamp字段基本上只是时间戳字段,其中Postgres实际上只是存储“规范化”的UTC时间,即使输入字符串中给出的时间戳具有时区。

如果您的输入字符串是:2018-08-28T12:30:00+05:30,当此时间戳存储在数据库中时,它将存储为2018-08-28T07:00:00。

与简单的时间戳字段相比,这个字段的优点是你对数据库的输入是独立于时区的,当来自不同时区的应用程序插入时间戳时,或者当你将数据库服务器位置移动到不同的时区时,它不会不准确。

引用文件中的内容:

For timestamp with time zone, the internally stored value is always in UTC (Universal Coordinated Time, traditionally known as Greenwich Mean Time, GMT). An input value that has an explicit time zone specified is converted to UTC using the appropriate offset for that time zone. If no time zone is stated in the input string, then it is assumed to be in the time zone indicated by the system’s TimeZone parameter, and is converted to UTC using the offset for the timezone zone. To give a simple analogy, a timestamptz value represents an instant in time, the same instant for anyone viewing it. But a timestamp value just represents a particular orientation of a clock, which will represent different instances of time based on your timezone.

对于几乎所有用例,时间戳几乎总是一个更好的选择。由于timestamptz和timestamp占用相同的8字节数据,这种选择变得更加容易。

来源: https://hasura.io/blog/postgres-date-time-data-types-on-graphql-fd926e86ee87/

其他回答

这些区别在PostgreSQL官方文档中有显示。请参考文档进行深入挖掘。

简而言之,TIMESTAMP WITHOUT TIME ZONE不会保存任何与时区相关的信息,如果你给date TIME timezone信息,它只接受date & TIME,忽略timezone

例如

当我保存这个12:13,2021年6月11日IST到PostgreSQL的时间戳没有时区将拒绝时区信息,并保存日期时间12:13,2021年6月11日

但是在TIMESTAMP WITH TIME ZONE的情况下,它以UTC格式保存时区信息。

例如

当我保存这个12:13,2021年6月11日IST到PostgreSQL的时间戳带时区类型变量时,它将把这个时间解释为UTC值和 存储如下所示,UTC 2021年6月11日6:43

注意:UTC + 5.30是IST

在时间转换期间,由TIMESTAMP WITH time ZONE返回的时间将以UTC格式存储,我们可以将其转换为所需的时区,如IST或PST等。

所以PostgreSQL中推荐的时间戳类型是timestamp WITH TIME ZONE或TIMESTAMPZ

这些差异在PostgreSQL文档中有关于日期/时间类型的说明。是的,TIME或TIMESTAMP的处理在带TIME ZONE或不带TIME ZONE之间是不同的。它不会影响值的存储方式;它影响了它们的解释方式。

时区对这些数据类型的影响在文档中有详细介绍。差异来自于系统可以合理地知道的值:

将时区作为值的一部分,该值可以在客户端中作为本地时间呈现。 如果没有时区作为值的一部分,显然默认时区是UTC,因此它是按照该时区呈现的。

行为的不同取决于至少三个因素:

客户端的时区设置。 值的数据类型(即带时区或不带时区)。 是否使用特定的时区指定该值。

以下是这些因素组合的例子:

foo=> SET TIMEZONE TO 'Japan';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+09
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 06:00:00+09
(1 row)

foo=> SET TIMEZONE TO 'Australia/Melbourne';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+11
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 08:00:00+11
(1 row)

时间戳vs时间戳

Postgres中的timestamp字段基本上只是时间戳字段,其中Postgres实际上只是存储“规范化”的UTC时间,即使输入字符串中给出的时间戳具有时区。

如果您的输入字符串是:2018-08-28T12:30:00+05:30,当此时间戳存储在数据库中时,它将存储为2018-08-28T07:00:00。

与简单的时间戳字段相比,这个字段的优点是你对数据库的输入是独立于时区的,当来自不同时区的应用程序插入时间戳时,或者当你将数据库服务器位置移动到不同的时区时,它不会不准确。

引用文件中的内容:

For timestamp with time zone, the internally stored value is always in UTC (Universal Coordinated Time, traditionally known as Greenwich Mean Time, GMT). An input value that has an explicit time zone specified is converted to UTC using the appropriate offset for that time zone. If no time zone is stated in the input string, then it is assumed to be in the time zone indicated by the system’s TimeZone parameter, and is converted to UTC using the offset for the timezone zone. To give a simple analogy, a timestamptz value represents an instant in time, the same instant for anyone viewing it. But a timestamp value just represents a particular orientation of a clock, which will represent different instances of time based on your timezone.

对于几乎所有用例,时间戳几乎总是一个更好的选择。由于timestamptz和timestamp占用相同的8字节数据,这种选择变得更加容易。

来源: https://hasura.io/blog/postgres-date-time-data-types-on-graphql-fd926e86ee87/

下面是一个应该会有帮助的例子。如果您有一个带有时区的时间戳,您可以将该时间戳转换为任何其他时区。如果你没有一个基本时区,它将不会被正确转换。

SELECT now(),
   now()::timestamp,
   now() AT TIME ZONE 'CST',
   now()::timestamp AT TIME ZONE 'CST'

输出:

-[ RECORD 1 ]---------------------------
now      | 2018-09-15 17:01:36.399357+03
now      | 2018-09-15 17:01:36.399357
timezone | 2018-09-15 08:01:36.399357
timezone | 2018-09-16 02:01:36.399357+03

执行以下命令,查看pgAdmin中的diff:

create table public.testts (tz timestamp with time zone, tnz timestamp without time zone);
insert into public.testts values(now(), now());
select * from public.testts;

如果你在Angular / Typescript / Node API / PostgreSql环境中有类似的时间戳精度问题,希望我完整的回答和解决方案能帮助你。