10

等等,不要急于回答“java.util.Date”,考虑以下场景。

Person 对象有 2 个字段:“birthday”和“nextMeeting”都是 java.util.Date。现在,生日作为日期类型列(无时间)存储在数据库中,例如。01-10-1979 和 nextMeeting 作为日期时间类型,例如。2010 年 1 月 10 日 20:00:00。

您从数据库中提取它,“生日”将由 JDBC 自动设置为午夜。现在您需要使用 RMI 或任何技术将此对象发送到其他 JVM。

另一方面,JVM 具有来自原始 JVM 的时区 -1h。这就是问题开始的地方。nextMeeting 变成 01-10-2010 19:00:00 从用户的角度来看,这绝对是好的和正确的等等......

但是生日变成了 30-09-1979 23:00:00,它将向用户表示为 9 月 30 日,这真的不是我们想要的,因为显然生日是静态的,不依赖于时区。

因此,正确选择了 db 中的列类型(日期)。这种类型的列通常表示为 java.util.Date。但在我们的例子中,使用的 java 类型是错误的。

那么你会如何代表生日呢?考虑到您需要在 UI 上操作此对象,例如在 datepicker 组件等中...

4

6 回答 6

15

其他答案使用过时的类。

java.time

Joda-Time 和旧的 java.util.Date/.Calendar 类都已被Java 8 及更高版本中内置的java.time框架所取代。JSR 310定义。由ThreeTen-Extra项目扩展。ThreeTen-BackPort项目向后移植到 Java 6 和 7,该项目由ThreeTenABP项目为 Android 包装。

LocalDate

没有时间和时区的仅日期值可以由LocalDate类表示。与 Java 的最早版本捆绑在一起的旧日期时间类中缺少这样的类。旧java.sql.Date类假装是仅日期的,但实际上有一个从java.util.Date(日期时间值)继承的时间。

LocalDate dateOfBirth = LocalDate.of( 1979 , 1 , 10 );

如果没有一天中的时间或时区,出生日期对于确定一个人的年龄来说本质上是不准确的。但在几乎所有用例中,我们都不在乎;一天中的给予或接受的一部分已经足够接近了。

ZonedDateTime

对于一次会议,我们不能如此松散。我们需要日期、时间和时区。在 java.time 中,这意味着ZonedDateTime类。时区是问题场景中缺少的关键元素。添加时区,一切都很好。

ZoneId zoneIdMontreal = ZoneId.of( "America/Montreal" );
ZonedDateTime zdtMontreal = ZonedDateTime.of( 2010 , 1 , 10 , 20 , 0 , 0 , zoneIdMontreal );

现在将对象传送到另一台机器。两者都保持不变,出生日期 (1979-01-10) 的仅日期值和会议的蒙特利尔日期时间相同。

调整时区

然后,您可能希望将该会议调整到使用这台机器的人所期望的另一个时区。

ZoneId zoneIdParis = ZoneId.of( "Europe/Paris" );
ZonedDateTime zdtParis = zdtMontreal.withZone( zoneIdParis );

我们在时间轴上以两种方式表示相同的时刻,在两个对象中,zdtMontreal 和 zdtParis。

LocalDateTime

如果您nextMeeting没有时区,也没有从 UTC 偏移的信息,则表示为一个LocalDateTime对象。在数据库中,它将被存储为TIMESTAMP WITHOUT TIME ZONE.

这些值不代表时间轴中的某一时刻。它们仅代表一系列可能的时刻。要确定实际时刻,您必须提供特定时区的上下文。

为了存储未来的日期时间值,例如计划会议超过几周,在没有时区的情况下这样做可能是合适的。世界各地的政治家都表现出经常改变夏令时和重新定义他们的时区的倾向。他们经常在没有任何警告的情况下这样做,只有几个星期的警告。

要确定实际时刻,例如在日历中显示日程安排,请应用时区ZoneId以获取ZonedDateTime.

ISO 8601

java.time 类在解析/生成日期时间值的文本表示时默认使用标准ISO 8601格式。该类ZonedDateTime通过扩展 ISO 8601 以在方括号中附加时区名称更进一步。

如果通过文本序列化值,请使用 ISO 8601 的合理且明确的格式。

问题中提出的任何问题都不存在。使用出色的日期时间库和 ISO 8601(例如 java.time)可以解决问题。

数据库

您的数据库应该对生日使用仅日期类型,并为会议使用带时区的时间戳类型(请参阅Wikipedia 和数据库文档中的SQL 数据类型)。您的 JDBC 驱动程序为您调解这两种类型。

最终 JDBC 驱动程序将被更新为直接使用 java.time 类型。但在此之前,我们必须转换为 java.sql 类型,例如java.sql.Datejava.sql.Timestamp。新方法已添加到旧类中以支持这些转换。

java.sql.Date sqlDateOfBirth = java.sql.Date.valueOf( dateOfBirth );
java.sql.Timestamp sqlMeeting = java.sql.Timestamp.valueOf( zdtMontreal );

然后在您setDate的.setTimestampPreparedStatement

换个方向,从数据库到 Java,调用getDategetTimestampResultSet. 然后立即转换为 java.time 类型,避免在业务逻辑中使用 java.sql 类型。

对于日期时间值,我们必须通过一个Instant对象。这是UTCInstant时间线上的一个时刻。我们应用时区来为用户获取挂钟时间

LocalDate dateOfBirth = mySqlDate.toLocalDate();
Instant instant = mySqlTimestamp.toInstant();
ZonedDateTime zdtMontreal = ZonedDateTime.ofInstant( instant , zoneIdMontreal );
于 2016-03-31T18:18:10.613 回答
7

使用JodaTime中的LocalDate并且只存储生日的日期,而不是时间。

于 2010-10-27T08:06:53.573 回答
1

不知何故,两个 java 系统必须就日历/时区信息达成一致,否则 Date 对象在传递到远程系统时需要转换为时间戳。

最简单的方法可能是简单地要求所有客户将生日视为 GMT 时间 --- 当他们显示/比较/无论生日时,让他们使用 . 创建一个Calendar"GMT" TimeZone然后setTime()使用提供的Date.

如果您完全在本地使用模型,那么您真的应该有一个 Date 对象,而不仅仅是一个时间戳。

于 2010-10-27T08:03:52.667 回答
0

这是一个非常好的问题...

例如,Android 将生日存储为“yyyy-MM-dd”格式的字符串。我想知道他们为什么不使用 java.util.Date,我猜原因与您带来的问题相同。

所以我会推荐字符串或一些“时区独立日期”。但是在 Java 和 joda-time 文档中搜索了几分钟后,我不知道该怎么做。

编辑:似乎@Jeroen 是对的 - 使用 LocalDate。

于 2010-10-27T08:46:05.713 回答
0

对于操作,我会建议 java.util.Calendar

代表

生日为 java.util.Date
NextMeetin 为 java.sql.Timestamp

于 2010-10-27T08:07:06.733 回答
-1

如果你正在处理日期,你最好使用joda time。您可以使用日期/时间和时区信息构建DateTime对象,以便您拥有处理不同时区所需的所有信息。

于 2010-10-27T08:10:07.937 回答