使用Java在PostgreSQL中存储时间的最佳方法是什么?

Ren*_*ani 48 java postgresql date jdbc

我在PostgreSQL数据库中存储了两个日期.首先,是网页的访问数据,第二个日期是网页的最后修改日期(这是长的).

我怀疑存储这些值的最佳策略是什么.

我只需要日/月/年和小时:秒,这只适用于统计建议.

所以有些疑惑:

  • 是最好的存储,并转换恢复信息或以上面的数据格式存储?
  • 最好设置访问日期的软件或插入数据库?
  • 在Java中,处理日期的最佳类是怎样的?

leo*_*loy 82

在Postgresql中存储日期和时间数据的任何策略都应该依赖于这两点:

  • 您的解决方案永远不应该依赖于服务器或客户端时区设置.
  • 目前,Postgresql(与大多数数据库一样)没有数据类型来存储带有时区的完整日期和时间.因此,您需要(概念上)在一个Instant或一个LocalDateTime数据类型之间做出决定.

我对每个场景的配方:


如果要记录特定事件(通常是某些创建/修改/删除)的物理瞬间,(真正的" 时间戳 "),则使用:

(不要让Postgresql特有的数据类型WITH TIMEZONE/ WITHOUT TIMEZONE混淆你:它们中没有一个实际存储时区)

一些样板代码:以下假设ps是a PreparedStatement,rsa ResultSet并且tzUTCCalendarUTC时区对应的静态对象.

public static final Calendar tzUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));  
Run Code Online (Sandbox Code Playgroud)

写入Instant数据库TIMESTAMPTZ:

Instant instant = ...;
Timestamp ts = instant != null ? new Timestamp(instant.toEpochMilli()) : null;
ps.setTimestamp(col, ts, tzUTC);   // column is TIMESTAMPTZ!
Run Code Online (Sandbox Code Playgroud)

Instant从数据库中读取TIMESTAMPTZ:

Timestamp ts = rs.getTimestamp(col,tzUTC); // column is TIMESTAMPTZ
Instant inst = ts !=null ? Instant.ofEpochMilli(ts.getTime()) : null;
Run Code Online (Sandbox Code Playgroud)

如果您的PG类型是安全的,那么这种方法是安全的TIMESTAMPTZ(在这种情况下,该calendarUTC代码没有任何影响;但总是建议不依赖于默认时区)."安全"意味着结果将不依赖于服务器或数据库时区或时区信息:操作是完全可逆的,无论时区设置发生什么,您将始终获得与原始时间相同的"时刻". Java方面.


如果您有一个"民用"本地日期时间(即字段集{year-month-day hour:min:sec(:msecs)}),而不是时间戳(物理时间轴上的瞬间),则应使用

LocalDateTime从数据库中读取TIMESTAMP:

Timestamp ts = rs.getTimestamp(col, tzUTC); //
LocalDateTime localDt = null;
if( ts != null ) 
    localDt =  LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getTime()), ZoneOffset.UTC);
Run Code Online (Sandbox Code Playgroud)

写入LocalDateTime数据库TIMESTAMP:

  Timestamp ts = null;
  if( localDt != null)    
      ts = new Timestamp(localDt.toInstant(ZoneOffset.UTC).toEpochMilli()), tzUTC);
  ps.setTimestamp(colNum,ts, tzUTC); 
Run Code Online (Sandbox Code Playgroud)

再次,这个策略是安全的,你可以安静地睡觉:如果你存储2011-10-30 23:59:30,你将总是检索那些精确的字段(小时= 23,分钟= 59 ......等),无论如何 - 即使明天你的Postgresql的时区服务器(或客户端)更改,或您的JVM或操作系统时区,或者您的国家/地区修改其DST规则等.


补充:如果你想(似乎是一个自然要求)存储完整的日期时间规范(a ZonedDatetime:时间戳和时区,隐含地还包括完整的民用日期时间信息 - 加上时区),那么你运气不好: Postgresql没有这方面的数据类型(据我所知,这两个数据库都没有).您必须设计自己的存储,可能是在一对字段中:可能是上述两种类型(高度冗余,虽然检索和计算有效),或者其中一种加上时间偏移(您丢失了时区信息,一些计算成为困难,有些不可能),或其中一个加上时区(作为字符串;一些计算可能非常昂贵).

  • @BasilBourque,我从未使用过 JodaTime,我刚开始使用 Java 8。对我来说,这个答案几乎是一对一的 Java 8 API。 (3认同)
  • @mlorber 我意识到您的评论很旧,但它不正确,您绝对应该在几乎所有情况下都使用带 tz 的时间戳。我建议阅读 /sf/ask/463910261/。注意:您确实需要将 JVM 设置为 UTC,以便 postgres 正确解释传入的字符串。 (3认同)

Pab*_*ruz 5

时间

这并不漂亮,但这是在 Java 8 及更高版本(教程)中ZonedDateTime使用新的java.time框架的实例对我有用的方法

ZonedDateTime receivedTimestamp = some_ts_value;
Timestamp ts = new Timestamp(receivedTimestamp.toInstant().toEpochMilli());
ps.setTimestamp(
   1, 
   ts, 
   Calendar.getInstance(TimeZone.getTimeZone(receivedTimestamp.getZone()))
); 
Run Code Online (Sandbox Code Playgroud)

  • (a) 这个答案并不完全符合问题的意图。(b) 此代码有更直接的方法。旧的类,如`java.sql.Timestamp` 在 Java 8 和更高版本中进行了改进,提供了用于转换到新 java.time 类型/从新 java.time 类型转换的便捷方法。所以:`java.sql.Timestamp ts = java.sql.Timestamp.fromInstant(myZonedDateTime.toInstant());` (4认同)