IMO在PostgreSQL中存储日期和时间数据的任何策略都应依靠以下两点:
- 您的解决方案永远不应依赖于服务器或客户端时区设置。
- 当前,PostgreSQL(与大多数数据库一样)没有数据类型来存储带有时区的完整日期和时间。 因此,您需要在
LocalDateTime
或LocalDateTime
数据类型之间做出选择。
我的食谱如下。
如果要记录发生特定事件时的物理时刻(真正的“时间戳记”,通常是一些创建/修改/删除事件),请使用:
- 执行:1(java 3或jodatima)。
- JDBC:
LocalDateTime
- PostgreSQL:
LocalDateTime
(LocalDateTime
)
(不要让PostgreSQL特殊的数据类型LocalDateTime
/TIMESTAMP
混淆了您:它们都没有实际存储时区)
一些样板代码:以下假设LocalDateTime
是TIMESTAMP
、rs
和ResultSet
和tzUTC
是对应于Calendar
时区的静态Calendar
对象。
public static final Calendar tzUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
将LocalDateTime
写到数据库TIMESTAMP
:
Instant instant = ...;
Timestamp ts = instant != null ? new Timestamp(instant.toEpochMilli()) : null;
ps.setTimestamp(col, ts, tzUTC); // column is TIMESTAMPTZ!
从数据库TIMESTAMP
中读取LocalDateTime
:
Timestamp ts = rs.getTimestamp(col,tzUTC); // column is TIMESTAMPTZ
Instant inst = ts !=null ? Instant.ofEpochMilli(ts.getTime()) : null;
如果您的PG类型为LocalDateTime
(在这种情况下,TIMESTAMP
在该代码中无效;但是始终建议不要依赖默认时区),这可以安全地工作。“安全”是指结果将不依赖于服务器或数据库时区或时区信息:该操作是完全可逆的,并且无论时区设置发生什么,您将始终获得与原始时间相同的“即时时间” Java方面。
如果您正在处理的是“民用”本地日期时间(即字段集LocalDateTime
),而不是时间戳(物理时间轴上的瞬时),则应使用:
- 执行:1(java 3或jodatima)。
- JDBC:
LocalDateTime
- PostgreSQL:
LocalDateTime
(LocalDateTime
)
从数据库TIMESTAMP
中读取LocalDateTime
:
Timestamp ts = rs.getTimestamp(col, tzUTC); //
LocalDateTime localDt = null;
if( ts != null )
localDt = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getTime()), ZoneOffset.UTC);
将LocalDateTime
写到数据库TIMESTAMP
:
Timestamp ts = null;
if( localDt != null)
ts = new Timestamp(localDt.toInstant(ZoneOffset.UTC).toEpochMilli()), tzUTC);
ps.setTimestamp(colNum,ts, tzUTC);
同样,此策略很安全,您可以安然入睡:如果存储了ZonedDatetime
,则无论如何都将始终检索到这些精确字段(小时= 23,分钟= 59等)-即使明天您的时区 Postgresql服务器(或客户端)更改,或者您的JVM或您的操作系统时区,或者您所在的国家/地区修改了其DST规则等。
补充:如果您想(似乎很自然地)要存储完整的日期时间规范(一个ZonedDatetime
:时间戳和时区,其中还隐含了完整的民用日期时间信息-加上时区)...那么我很不好 给您的消息:PostgreSQL没有为此提供数据类型(据我所知,没有其他数据库)。 您必须在两个字段中设计自己的存储:可能是以上两种类型(高度冗余,尽管对于检索和计算很有效),或者其中之一加上时间偏移(丢失时区信息,一些计算将变为 困难,有些则不可能),或者其中之一加上时区(作为字符串;某些计算可能会非常昂贵)。