如果要记录特定事件(通常是某些创建/修改/删除)发生的物理瞬间(一个真正的“时间戳”),我建议:
> Java:java.util.Date类 – 或者,如果使用,Jodatime(强烈推荐),即时。
> JDBC:java.sql.Timestamp
> Postgresql:TIMESTAMP WITH TIMEZONE(TIMESTAMPTZ)
(不要被Postgresql数据类型名称所迷惑:WITH TIMEZONE / WITHOUT TIMEZONE有非常特殊的含义,其中没有一个实际存储时区)
这种情况的一些样板代码:以下假设ps是一个PreparedStatement,rs一个ResultSet和calendarUTC是一个对应于UTC的静态Calendar对象。
写入数据库:
long milis = date.getTime() ; // or instant.getMillis() or whatever
ps.setTimestamp(colNum,new Timestamp(milis), calendarUTC); // column is TIMESTAMPTZ!
从数据库读取:
public static final Calendar tzUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
...
Timestamp ts = rs.getTimestamp(colnum,tzUTC); // column is TIMESTAMPTZ
return ts !=null ? new Date(ts.getTime) : null;
如果你的PG类型是TIMESTAMPTZ(在这种情况下,calendarUTC对该代码没有影响,但是总是建议不依赖于默认的时区),这样做是安全的。
“安全”意味着结果不依赖于服务器或数据库时区或时区信息:操作是完全可逆的,无论时区设置如何,您将始终获得相同的“即时时间”。
如果您想要安全地记录“民用”本地日期时间(即,整个月份 – 日 – 小时 – 分钟)字段,而不是时间戳(物理时间线上的即时时间)您应该使用TIMESTAMP WITHOUTZONE并使用稍微不同的逻辑。为了完整起见(这里我使用Joda的LocalDateTime; Java的Date API是痛苦的)。
从数据库读取:
public static final DateTimeZone jodaTzUTC = DateTimeZone.forID("UTC");
...
Timestamp ts = getter.getTimestamp(tzUTC);
return ts != null ? new LocalDateTime(ts.getTime(), jodaTzUTC ) : null;
写入数据库:
Timestamp ts = new Timestamp(localdatime.toDateTime(jodaTzUTC).getMillis())
ps.setTimestamp(colNum,ts, tzUTC); // column is TIMESTAMP !
再次,这个策略是安全的,你可以安心地睡觉:如果你们存储“2011-10-30 23:59:30”,你将总是检索这些精确的字段(年= 2011,月= 10 …等等) ,无论什么,即使明天有人更改Postgresql,JVM或OS时区的时区,或者您的国家更新其DST规则等。
添加:如果您希望(似乎是一个自然需求)来存储完整的datetime规范(Jodatime中的Datetime:时间戳与时区,这也隐含地包括完整的民事日期时间信息加上时区),您已经不在运气:Postgresql没有一个数据类型(对我而言,其他数据库都不知道)。您必须设计自己的存储空间,也许在一对领域中:可以是上述两种类型(高度冗余,尽管有效的检索和计算),或者其中一个加上时间偏移(您丢失时区信息,一些计算成为困难和一些不可能),或其中一个加上时区(作为字符串;一些计算可能是非常昂贵的)。