在Java中操作和存储日期的最佳实践是什么?

操作和存储日期的最佳实践是什么,例如在企业Java应用程序中使用GregorianCalendar?

寻找反馈,我会将任何好的答案合并到其他人可以使用的最佳实践中。 谢谢!

Joda是要走的路。 为什么?

  1. 它具有比标准日期/时间API更强大和直观的界面
  2. 日期/时间格式没有线程问题。 java.text.SimpleDateFormat不是线程安全的(不是很多人都知道这个!)

在某个阶段,Java Date / Time API将被取代(由JSR-310)。 我相信这将基于Joda背后的人所做的工作,因此您将学习一种影响新标准Java API的API。

最佳实践通常不是考虑重日期对象而是考虑存储时间点。 这通常通过存储不受角落情况影响的值或潜在解析问题来完成。 要做到这一点,人们通常会存储自我们称之为纪元(1970-01-01)的固定点以来经过的毫秒数(或秒数)。 这是非常常见的,任何Java API总是允许您将任何类型的日期转换为自纪元以来以ms表示的时间。

这是为了存储。 如果有此需要,您还可以存储用户首选的时区。

现在这样的日期以毫秒为单位,如:

System.out.println( System.currentTimeMillis() ); 1264875453 

当它显示给最终用户时不是很有用,这是理所当然的。

这就是为什么你使用例如Joda时间将它转换为某种用户友好格式然后再显示给最终用户的原因。

您要求最佳实践,这是我的看法:在数据库中存储“日期”对象而不是以毫秒为单位的时间就在那里使用浮点数来表示货币金额。

它通常是一个巨大的代码味道。

所以Joda在Java中的时间是操纵日期的方式,是的。 但是Joda是去商店约会的方式吗? 某些不是

Joda时间 (100%与JDK互操作)

Joda-Time为Java日期和时间类提供了高质量的替代品。 该设计允许多个日历系统,同时仍提供简单的API

世界标准时间

以UTC而不是任何时区思考,工作和存储数据。 将UTC视为一个真实时间 ,所有其他时区仅仅是变化。 因此,在编码时,忘记所有关于您自己的时区。 以UTC格式进行业务逻辑,日志记录,数据存储和数据交换。 我建议每个程序员在他们的桌面上设置第二个时钟设置为UTC。

java.time

现代方式是java.time类。

上面提到的Joda-Time项目为java.time类提供了灵感,项目现在处于维护模式,团队建议迁移到java.time类。

java.time框架内置于Java 8及更高版本中。 这些类取代了麻烦的旧遗留日期时间类,例如java.util.Date.Calendarjava.text.SimpleDateFormat

要了解更多信息,请参阅Oracle教程 。 并搜索Stack Overflow以获取许多示例和解释。 规范是JSR 310 。

从哪里获取java.time类?

  • Java SE 8SE 9及更高版本
    • 内置。
    • 带有捆绑实现的标准Java API的一部分。
    • Java 9增加了一些小function和修复。
  • Java SE 6SE 7
    • 许多java.timefunction都被反向移植到ThreeTen-Backport中的 Java 6和7。
  • Android的
    • ThreeTenABP项目特别适用于Android的ThreeTen-Backport (如上所述)。
    • 请参阅如何使用….

ThreeTen-Extra项目使用其他类扩展了java.time。 该项目是未来可能添加到java.time的试验场。 您可以在这里找到一些有用的课程,如IntervalYearWeekYearQuarter等。

ISO 8601

将日期时间值序列化为文本时,请使用ISO 8601标准。

例如,UTC的日期时间是2016-10-17T01:24:35Z ,其中ZZulu缩写,表示UTC。 对于其他来自UTC的偏移,小时和分钟的偏移将出现在最后,例如2016-01-23T12:34:56+05:30 。 java.time类扩展此标准格式以将时区的名称(如果已知)附加在方括号中,例如2016-01-23T12:34:56+05:30[Asia/Kolkata]

该标准还有许多其他方便的格式,包括持续时间 , 间隔 , 序数和年周 。

数据库

对于数据库存储,请将日期时间类型用于日期时间值,例如主要为DATETIMETIMESTAMP WITH TIME ZONE的SQL标准数据类型 。

让你的JDBC驱动程序完成繁重的工作。 驱动程序处理有关如何在Java处理数据的内部以及数据库如何处理数据的内部之间进行调解和调整的细节。 但请务必使用示例数据来了解驱动程序和数据库的行为。 SQL标准对日期时间处理的定义很少,因此行为差异很大,令人惊讶的是。

如果使用符合JDBC 4.2及更高版本的JDBC驱动程序,则可以通过ResultSet::getObjectPreparedStatement::setObject方法直接获取和存储java.time类型。

 Instant instant = myResultSet.getObject( … ); myPreparedStatement.setObject( … , instant ); 

对于较旧的驱动程序,您将需要回退到通过java.sql类型进行转换。 寻找添加到旧类的新转换方法。 例如, java.sql.Timestamp.toInstant()

 Instant instant = myResultSet.getTimestamp( … ).toInstant(); myPreparedStatement.setObject( … , java.sql.Timestamp.from( instant ) ); 

尽可能简短地使用java.sql类型。 它们是一个设计糟糕的黑客,比如java.sql.Date伪装成仅限日期的值,但实际上是java.util.Date的子类,它确实有一个时间设置为00:00:00在UTC中。 而且,哦,你应该忽略inheritance说类课程的事实。 丑陋的混乱。

示例代码

以UTC格式获取当前时刻。

 Instant instant = Instant.now(); 

以上显示了将Instant对象存储到数据库或从数据库获取该对象的方法。

要生成ISO 8601字符串,只需调用toString 。 默认情况下,java.time类都使用ISO 8601格式来解析和生成各种日期时间值的字符串。

 String output = instant.toString(); 

通过应用ZoneOffset来调整到UTC的任何偏移量以获得OffsetDateTime 。 调用toString以生成ISO 8601格式的String。

 ZoneOffset offset = ZoneOffset.ofHoursMinutes( 5 , 30 ); OffsetDateTime odt = instant.atOffset( offset ); 

时区是偏移量加上一组用于处理夏令时(DST)等exception的规则。 当您需要通过某个区域自己的挂钟时间镜头看到同一时刻时 ,应用时区( ZoneId )来获取ZonedDateTime对象。

continent/region格式指定适当的时区名称 。 切勿使用3-4字母缩写,例如ESTIST因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

 ZoneId z = ZoneId.of( "Asia/Kolkata" ); ZonedDateTime zdt = instant.atZone( z ); 

走向另一个方向,您可以通过调用toInstantOffsetDateTimeZonedDateTime提取Instant

 Instant instant = zdt.toInstant(); 

格式化

要以非ISO 8601格式的字符串呈现给用户,请搜索Stack Overflow以使用DateTimeFormatter类。

虽然您可以指定自定义格式,但通常最好让java.time自动本地化。 要进行本地化,请指定:

  • FormatStyle用于确定字符串的长度或缩写。
  • 用于确定(a)用于翻译日期名称,月份名称等的人类语言的Locale ,以及(b)决定缩写,大小写,标点符号等问题的文化规范。

例:

 Locale l = Locale.CANADA_FRENCH ; DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( l ); String output = zdt.format( f ); 

转变

尽可能避免遗留日期时间类型。 但是如果使用尚未针对java.time类型更新的旧代码,则可以转换为java.time类型。 有关详细信息,请参阅问题, 将java.util.Date转换为“java.time”类型? 。

使用对象

使用对象而不仅仅是编码原语和简单字符串。 例如:

  • 不要使用1-7来表示星期几,请使用DayOfWeek枚举,例如DayOfWeek.TUESDAY
  • 而不是将字符串作为日期传递,而不是传递LocalDate对象。
  • 而不是传递一对和一个月的整数,传递YearMonth对象。
  • 而不是一个月的1-12,使用更可读的Month枚举,如Month.JANUARY

使用此类对象可使您的代码更加自我记录,确保有效值并提供类型安全性 。

为了开始讨论,以下是我的经验:

在为典型的3层Java Enterprise项目创建标准时,我通常建议项目使用GregorianCalendar来操作日期。 理由是GregorianCalendar是任何其他Calendar实例的事实上的标准,例如Julian日历等。它是大多数国家公认的日历,并且正确处理闰年等。最重要的是,我建议应用程序将其日期保存为UTC您可以轻松地执行日期计算,例如查找两个日期之间的差异(例如,如果它存储为EST,则必须考虑日光节省时间)。 然后可以将日期本地化为您需要将其显示给用户的任何时区 – 例如,如果您是美国东海岸公司并且您希望在EST中显示您的时间信息,则将其本地化为EST。