Answer by Meno Hochschild 是正确的。我将添加更多的 cmets 和一些特定于 SQL 的代码。
避免使用旧的日期时间类
旧的日期时间类 java.util.Date/.Calendar 和 java.text.SimpleDateFormat 设计不佳、混乱且麻烦。避开他们。
旧的类已被 Java 8 及更高版本中内置的 java.time 框架所取代。
要在 Java 8 之前使用,请查看 ThreeTen-Backport 项目。
纳秒
java.time 类具有纳秒级分辨率。因此,您不会遇到由于旧类使用毫秒分辨率而导致2016-03-10T11:24:59.7862749+04:00 被截断为2016-03-10 11:24:59.786 的数据丢失问题。
由于遗留问题,在 Java 8 中获取当前时刻被限制为毫秒,即秒的小数部分的三位数字。 Java 9 将以纳秒为单位获取当前时刻,最多九位小数(假设您的计算机的硬件时钟可以提供如此精细的分辨率)。
ISO 8601
ISO 8601 标准为日期时间值定义了合理的文本格式。例如,2016-03-09T23:24:33Z 或 2016-03-09T22:24:33-01:00。 java.time 类默认使用这些,因此无需定义解析模式。
即时
Instant 是 UTC 时间线上的一个时刻。
Instant instant = Instant.now();
调用Instant::toString 以生成标准格式的字符串。
String output = instant.toString();
2016-03-09T23:24:33.123Z
OffsetDateTime
申请ZoneOffset 以获得OffsetDateTime。
ZoneOffset zoneOffset = ZoneOffset.ofHoursMinutes( -5 , 30 );
OffsetDateTime odt = OffsetDateTime.ofInstant( instant , zoneOffset );
ZonedDateTime
如果您知道完整时区而不仅仅是offset-from-UTC,请应用ZoneId 以获得ZonedDateTime。
使用proper time zone names。
ZoneId zoneId = ZoneId.of( "Asia/Kolkata" ); // "Europe/Paris", "America/Montreal", etc.
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
java.sql.Timestamp
希望 JDBC 驱动程序将更新为直接使用 java.time 类型。在此之前,我们将转换为 java.sql 类型以将数据传入/传出数据库。
如上所述,java.time 可以处理纳秒。 java.sql.Timestamp 也是如此。但是您的数据库可能不会。某些数据库仅限于整秒、毫秒或微秒。当数据通过 JDBC 传递到数据库时,数据库可能会被截断。
java.sql.Timestamp ts = java.sql.Timestamp.from( instant );
……然后朝着另一个方向……
Instant instant = ts.toInstant();
请注意,根据定义,Instant 始终采用 UTC。所以不需要执行问题末尾尝试的那种代码。
工作流程
在处理日期时间时应尽量减少字符串的使用。最大限度地使用有用的日期时间类/对象,即 java.time 类。不要将字符串视为日期时间值——它们是日期时间值的文本表示。
不要将日期时间值作为字符串插入/检索数据库。使用 java.sql 对象,例如 java.sql.Timestamp 和 java.sql.Date。使用PreparedStatement 和“设置/获取”方法,例如setTimestamp/getTimestamp。并且在数据库中几乎 always define your columns 为 TIMESTAMP WITH TIME ZONE 而不是“没有时区”。
从数据库获取数据时,使用 java.sql 类型。但尽快转换为 java.time 类型。 java.sql 类型很乱,很脏,应该只用于数据传输而不是业务逻辑。
一般最好在您的业务逻辑、数据存储、数据交换、API 调用等中使用UTC。仅在用户期望或数据接收器需要时调整到时区。