【问题标题】:How do I tell postgres a timestamp within a column is UTC?如何告诉 postgres 列中的时间戳是 UTC?
【发布时间】:2015-02-02 15:21:41
【问题描述】:

我们有一个从源获取数据的应用程序,并且该源以 UTC 时间戳显示数据。当我们的应用程序将该数据保存到 Postgres 时,它将该时间戳存储在没有时区的时间戳列中。我们商店中 postgres 的默认设置为我们的当地时间,山区时间。所以这意味着,我认为,postgres 假设时间戳是山区时间。如何查询该列,以便我的结果集认为它是 UTC 而不是本地时区?

更清楚地说,我需要对该时间戳执行一些偏移(将其移动到,比如说 EST),所以如果结果集认为它是 UTC 而不是我的本地时间,那么这样做的数学是不同的

【问题讨论】:

    标签: postgresql utc


    【解决方案1】:

    PostgreSQL 中有两个data types handling timestamps - timestamptimestamptz (带有时区的时间戳)。后者将时区与时间戳本身一起存储。

    如果您只使用没有时区的timestamp,则结果集无法考虑时间戳是否为UTC。这只是一个时间戳。由客户端应用程序来解释它并赋予它一些时区含义。

    相反,如果你使用timestamptz,那么PostgreSQL知道那个时间戳的时区,然后它可以为你正确计算时区偏移量。

    db=# select now();
                  now              
    -------------------------------
     2014-12-04 19:27:06.044703+02
    (1 row)
    
    db=# select timezone('est', now());
              timezone          
    ----------------------------
     2014-12-04 12:27:06.044703
    (1 row)
    

    所以,回到提出的问题。您需要确保首先正确导入数据,然后在需要时将其返回并正确显示给最终用户。你有两个选择:

    1. 继续使用timestamp

      在这种情况下,写入应用程序和读取应用程序都需要知道数据库中的所有时间戳都是 UTC 并相应地计算偏移量。

    2. 切换到timestamptz

      那么应用程序唯一需要知道的是他们自己的时区,他们只需在连接到 PostgreSQL 后声明它,并将其余的留给数据库。

    例如,让我们作为一个写作应用程序连接并将我们的时区声明为 UTC。

    db=# create table x (data timestamptz);
    CREATE TABLE
    
    db=# set timezone='utc';
    SET
    
    db=# insert into x values (now());
    INSERT 0 1
    
    db=# select * from x;
                data              
    -------------------------------
    2014-12-04 20:02:08.692329+00
    (1 row)
    

    现在,假设一个阅读应用程序连接并位于美国东部标准时间时区。

    db=# set timezone='est';
    SET
    
    db=# select * from x;
                data              
    -------------------------------
    2014-12-04 15:02:08.692329-05
    (1 row)
    

    更改客户端时区设置会更改所有时间戳的返回方式,但仅当您使用 timestamptz - timestamp with 时区时才会出现这种情况。如果您无法切换到这种数据类型,那么应用程序将不得不处理所有这些魔法

    【讨论】:

    • 不太精确。 Postgres 中的 TIMESTAMP WITH TIME ZONE 类型总是以 UTC 格式存储时刻。输入中包含的任何时区或与 UTC 的偏移量信息用于将输入调整为 UTC,然后丢弃。与您的第二句话相反,时区存储。如果您关心原始时区或偏移量,程序员必须将该值单独存储在另一列中。
    【解决方案2】:

    Answer by Kouber Saparev 大部分是正确的,但在存储时区方面不正确。

    Postgres 中的数据类型错误

    UTC 时间戳。当我们的应用程序将该数据保存到 Postgres 时,它会将时间戳存储在没有时区的时间戳列中。

    正如他在回答中指出的那样,您在 Postgres 数据库中使用了错误的数据类型。跟踪时刻时,您必须使用TIMESTAMP WITH TIME ZONE 类型的列。在插入或更新期间提供输入时,任何有关时区或与 UTC 的偏移量的随附信息都用于调整为 UTC。然后丢弃伴随的区域/偏移量。如果您需要记住原始区域/偏移量,则需要定义第二列并将该信息自己存储在那里。

    Postgres 和 SQL 标准中的另一种类型是 TIMESTAMP WITHOUT TIME ZONE。这种类型故意缺少任何时区或与 UTC 偏移的概念。所以这种类型不能代表时刻,不能在时间轴上存储点。它存储代表大约 26-27 小时范围内的潜在时刻的值,即全球各个时区的范围。仅当您的意思是在任何地方或任何地方都有时间的日期时才使用此类型,但不是专门在某个地方。也用于您的意思是未来的约会足够远,以至于我们冒着政治家改变我们关心的任何时区使用的偏移量的风险。

    始终指定时区

    我们商店的 postgres 默认设置为我们的当地时间,山区时间

    永远不要依赖主机操作系统、数据库服务器或 Java 虚拟机等工具的当前默认时区。始终在代码中指定所需/预期的时区。

    提示:通常最好在 UTC 中工作以进行数据存储、数据交换和大部分业务逻辑。从 UTC 调整为仅用于向用户展示或业务规则需要的时区。

    如上所述,Postgres 始终以 UTC 或根本没有区域/偏移量存储日期时间值。注意:您和 Postgres 之间使用的工具可能会对从数据库检索到的 UTC 值应用时区。虽然出于善意,但此反功能会产生一种错觉,即时区已存储,而实际上只有 UTC 存储在 TIMESTAMP WITH TIME ZONE 中,或者根本没有时区/偏移量存储在 TIMESTAMP WITHOUT TIME ZONE 中。

    请注意,任何伴随输入到 TIMESTAMP WITHOUT TIME ZONE 列的区域信息都将被忽略,日期和时间按原样存储。

    我需要对该时间戳执行一些偏移(将其移动到,例如 EST)

    通常最好将您的数据库仅用于存储、查询和检索数据。要调整时区等数据,请在您的应用程序中执行此类工作。例如,在 Java 中使用行业领先的 java.time 类,在 .NET 中 Noda Time 项目(java.time 的前身的一个端口,@ 987654323@项目)。

    使用 JDBC 4.2 或更高版本的 Java 示例代码。

    LocalDateTime

    对于TIMESTAMP WITHOUT TIME ZONE 列中的值,我们使用Java 中的相应类型LocalDateTime,缺少任何时区或与UTC 偏移的概念。

    LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;  // Retrieve value from database.
    String output = ldt.toString() ;  // Generate text representing this date-with-time value in standard ISO 8601 format.
    

    2018-01-23T01:23:45.123

    如果您确定此日期和时间是针对 UTC 的,但在没有任何区域/偏移信息的情况下被错误地存储,您可以应用区域或偏移来修复损坏。

    OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC );  // Apply an offset-from-UTC to a `LocalDateTime` lacking such information. Determines a moment.
    

    OffsetDateTime

    对于TIMESTAMP WITH TIME ZONE 列中的值,我们使用Java 中的相应类型OffsetDateTime(或Instant),表示UTC 中的时刻。

    OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;  // Retrieve value from database.
    String output = odt.toString() ;  // Generate text representing this date-with-time value in standard ISO 8601 format. A `Z` on the end indicates UTC, pronounced “Zulu”. 
    

    2018-01-23T01:23:45.123Z

    ZonedDateTime

    要通过北美中西部地区的人们使用的挂钟时间的镜头查看设置为 UTC 的 OffsetDateTime 值,请指定一个时区,例如 America/EdmontonAmerica/Denver .

    continent/region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 2-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。

    ZoneId z = ZoneId.of( "America/Denver" ) ;  
    ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
    

    看到这个code run live at IdeOne.com。我们看到的是同一时刻,但挂钟时间不同。

    2018-01-22T18:23:45.123-07:00[美国/丹佛]

    小心注入时区的工具和中间件

    不幸的是,许多工具和中间件会自愿将一些默认时区应用于从数据库中检索到的时刻。虽然出于善意,但这会造成该区域已成为存储数据的一部分的错觉,而实际上时区是在检索时存储后添加的。这种反特征造成了很多混乱。我希望通过以 UTC 格式报告存储的时刻,所有工具都清晰真实。

    如果您使用Java,与JDBC 4.2 及更高版本,您可以与数据库交换java.time (JSR 310) (tutorial) 对象,并避免此时区注入。

    【讨论】:

    • 如果 在插入或更新期间提供输入时,任何有关时区或与 UTC 的偏移量的随附信息都用于调整为 UTC。然后丢弃附带的区域/偏移量。 并且 Postgres 始终以 UTC 或根本没有区域/偏移量存储日期时间值。很抱歉,您能否向我解释一下或指向我可以理解的资源,如果我这样做update tablename set test='2000/01/01' where id=1;,字段test(类型为date)以 2000-01-01 00:00:00-08 结尾。如果不是偏移量,末尾的 -08 是什么?非常感谢
    • @Scaramouche 您应该发布一个包含所有详细信息的新问题(MCVE)。您的经验中还有其他情况,因为 Postgres 中的 DATE 类型既没有时间,也没有与 UTC 的偏移量。
    • 对不起,我的错,它实际上是timestamp with time zone。我的 pg 的时区设置为 US/Pacific,因此是 -08。我仍然不知道为什么该偏移量与日期和时间一起存储在同一列中(我现在正在 pgAdmin 的表和使用 pgAdmin 的查询工具的查询结果中查看它)。奇怪的是,当我从我的服务器代码 (node.js) 中查询它时,它实际上显示为您所描述的:日期时间值被调整为 UTC,并且偏移量被丢弃。还是谢谢
    • 该偏移量没有被存储。当 Postgres 向您显示日期时,它是根据 pg 的时区计算的。
    • @Scaramouche 最后我添加了一个部分,提到了由工具和中间件(例如 PgAdmin)动态应用时区来检索到的时刻所引起的问题。您在 UTC 后 8 小时的偏移量与您的数据一起存储;偏移量在应用检索之前到达您,换句话说,在数据库之间途中并且您的屏幕注入了一个时区。您的检索工具的反功能。研究检索工具的文档,寻找会话默认时区之类的内容。
    猜你喜欢
    • 1970-01-01
    • 2015-04-14
    • 2020-11-14
    • 1970-01-01
    • 1970-01-01
    • 2014-01-09
    • 1970-01-01
    • 2015-12-21
    • 1970-01-01
    相关资源
    最近更新 更多