Postgres 根据 SQL 标准提供两种日期时间类型。不幸的是,该标准几乎没有涉及该主题,因此此处描述的行为特定于 Postgres。其他数据库的行为可能不同。
-
TIMESTAMP WITHOUT TIME ZONE
仅存储日期和时间。忽略传递的任何时区或与 UTC 的偏移量。
-
TIMESTAMP WITH TIME ZONE
首先使用其传递的区域/偏移量调整传递的日期+时间,以获取 UTC 值。然后在进行调整后丢弃通过的区域/偏移量;如果需要,您必须自己将原始区域/偏移信息存储在单独的列中。
注意TIMESTAMP WITHOUT TIME ZONE 不代表一个实际的时刻,不在时间轴上存储一个点。如果没有区域或偏移的上下文,它就没有真正的意义。它代表了大约 26-27 小时跨度内的一系列可能时刻。适用于诸如将约会存储在将来足够远以致时区规则可能在其到达之前更改的问题。也适用于诸如“圣诞节在今年 12 月 25 日午夜之后开始”之类的问题,您的意思是每个区域的时间不同,每个区域向西的时间越来越晚。
在记录实际时刻、时间轴上的特定点时,请使用TIMESTAMP WITH TIME ZONE。
Java 中的现代方法使用 java.time 类,而不是 Joda-Time 库或与 Java 的最早版本捆绑在一起的麻烦的旧旧日期时间类。
TIMESTAMP WITHOUT TIME ZONE
对于TIMESTAMP WITHOUT TIME ZONE,java.time 中的等效类是LocalDateTime,用于没有任何偏移或区域的日期和时间。
正如其他人指出的那样,一些工具可能会以一种误导和混淆的方式动态地将时区应用于检索到的值,尽管这是出于善意的反功能。以下 Java 代码将检索您的真实日期时间值 sans 区域/偏移量。
需要符合 JDBC 4.2 或更高版本的 JDBC 驱动程序才能直接使用 java.time 类型。
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ; // Retrieving a `TIMESTAMP WITHOUT TIME ZONE` value.
插入/更新数据库:
myPreparedStatement.setObject( … , ldt ) ; // Inserting/updating a `TIMESTAMP WITHOUT TIME ZONE` column.
TIMESTAMP WITH TIME ZONE
您对时区的讨论表明您关注时间线上的实际时刻。所以你绝对应该使用TIMESTAMP WITH TIME ZONE 而不是TIMESTAMP WITHOUT TIME ZONE。你不应该搞乱夏令时 (DST) 间隔等。让 java.time 和 Postgres 为您完成这项工作,已经编写和测试了更好的代码。
检索:
Instant instant = myResultSet.getObject( … , Instant.class ) ; // Retrieving a `TIMESTAMP WITH TIME ZONE` value in UTC.
ZonedDateTime zdt = instant.atZone( ZoneId.of( "Africa/Tunis" ) ) ; // Adjusting from a UTC value to a specific time zone.
插入/更新数据库:
myPreparedStatement.setObject( … , zdt ) ; // Inserting/updating a `TIMESTAMP WITH TIME ZONE` column.
从数据库中检索:
Instant instant = myResultSet.getObject( … , Instant.class ) ;
例如当我从欧洲/伦敦时区存储 LocalDateTime 2015-03-29 02:30:00 时
不,不,不。不要以这种方式工作。您滥用了 Java 和 Postgres 的类型。
如果用户输入的2015-03-29 02:30:00 旨在表示Europe/London 时区中的时刻,则解析为LocalDateTime 并立即应用ZoneId 以获取ZonedDateTime。
要解析,请将中间的 SPACE 替换为 T,以符合 java.time 类中默认使用的 ISO 8601 标准格式。
String input = "2015-03-29 02:30:00".replace( " " , "T" ) ;
LocalDateTime ldt = LocalDateTime.parse( input ) ;
ZoneId z = ZoneId.of( "Europe/London" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;
要在 UTC 中查看同一时刻,请提取 Instant。 Instant 类代表UTC 时间线上的时刻,分辨率为nanoseconds(最多九 (9) 位小数)。
Instant instant = zdt.toInstant() ;
通过 JDBC 传递即时信息以存储在数据库中的 TIMESTAMP WITH TIME ZONE 中。
myPreparedStatement.setObject( … , instant ) ;
使用对象,而不是字符串
请注意,我在这里的所有代码都使用 java.time 对象与数据库交换数据。始终使用这些对象而不是单纯的字符串来交换日期时间值。
关于java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date、Calendar 和 SimpleDateFormat。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
从哪里获得 java.time 类?
ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。