如何告诉postgres列中的时间戳是UTC?

时间:2014-12-04 16:37:59

标签: postgresql utc

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

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

2 个答案:

答案 0 :(得分: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之后声明它并将其余部分留给数据库。

  3. 例如,让我们连接为编写应用程序并将我们的时区声明为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)
    

    现在,让我们说一个阅读应用程序连接并且在EST时区。

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

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

答案 1 :(得分:1)

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值。尽管有很好的意图,但这种反功能使人产生了这样的幻想,即实际上只在TIMESTAMP WITH TIME ZONE中存储了UTC或在TIMESTAMP WITHOUT TIME ZONE中根本没有存储任何时区/偏移。

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

  

我需要在该时间戳上执行一些偏移(将其移至EST)

通常最好只将数据库用于存储,查询和检索数据。要像调整时区那样按摩数据,请在您的应用程序中进行此类工作。例如,在Java中,使用行业领先的 java.time 类;在.NET中,Noda Time项目( java.time 的前身的端口, Joda-Time项目)。

使用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

要通过北美中西部地区的人们使用的壁钟时间来查看OffsetDateTime在UTC中设置的值,请​​指定一个时区,例如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 [美国/丹佛]