您如何在您的Java模型中表示出生日期?

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

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

你从db中取出它,“生日”将由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组件中一样……

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

其他答案使用过时的类。

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 (日期时间值)inheritance的时间。

 LocalDate dateOfBirth = LocalDate.new( 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)解决了这个问题。

数据库

您的数据库应该使用dated only类型作为birthdate,并使用timestamp-with-zone-zone类型进行会议(请参阅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 ); 

然后在PreparedStatement上调用setDatesetTimestamp

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

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

 LocalDate dateOfBirth = mySqlDate.toLocalDate(); Instant instant = mySqlTimestamp.toInstant(); ZonedDateTime zdtMontreal = ZonedDateTime.ofInstant( instant , zoneIdMontreal ); 

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

代表

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

不知何故,两个java系统必须同意Calendar / TimeZone信息,或者Date对象在传递给远程系统时需要转换为时间戳。

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

如果你在本地使用模型,你应该有一个Date对象,而不仅仅是一个时间戳。

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

例如,Android以’yyyy-MM-dd’格式将生日存储为String。 我想知道他们为什么不使用java.util.Date,我想原因就是你带来的问题。

所以我会推荐String或一些“Timezone-independent-date”。 但是在Java和joda-time文档中搜索几分钟后,我不知道该怎么做。

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

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