在企业级Java应用程序中,使用GregorianCalendar处理和存储日期的最佳实践是什么?
希望得到反馈,并将任何优秀答案整合成其他人可以使用的最佳实践。
System.out.println( System.currentTimeMillis() );
1264875453
在向最终用户显示时,时间戳并不是非常有用,这是肯定的。
这就是为什么你要使用例如 Joda time 这样的工具,在向最终用户显示之前将其转换为一些用户友好的格式。
你询问了最佳实践,这是我的看法:将“日期”对象存储在数据库中,而不是以毫秒为单位的时间,就像使用浮点数表示货币金额一样。
这通常是一个很大的代码异味。
因此,在Java中使用Joda时间来操作日期是正确的方式,但是使用Joda来存储日期吗?当然不是。
Joda 时间库 是首选。为什么呢?
在某个时候,Java 日期/时间 API 将被 JSR-310 取代。我相信这将基于 Joda 制作,因此您将学习一个在新标准 Java API 中影响力的 API。
在编程中,不要考虑任何时区,而是在协调世界时中思考、工作和存储数据。将协调世界时视为“唯一真实时间”,而所有其他时区都只是变化。因此,在编码时,请忘记自己所在的时区。在协调世界时中进行业务逻辑、日志记录、数据存储和数据交换。我建议每个程序员在他们的桌子上放置第二个设置为协调世界时的时钟。
现代化的方式是使用java.time类。
提到的Joda-Time项目为java.time类提供了灵感,该项目现已进入维护模式,并建议团队迁移到java.time类。
java.time框架已内置于Java 8及更高版本中。这些类取代了老旧的legacy日期时间类,例如java.util.Date
, .Calendar
和java.text.SimpleDateFormat
等。
ThreeTen-Extra项目扩展了java.time的额外类。该项目是java.time可能未来添加的一个试验场。您可能会在这里找到一些有用的类,例如Interval
, YearWeek
, YearQuarter
, 以及更多。
在将日期时间值序列化为文本时,请使用ISO 8601标准。
例如,UTC中的日期时间为2016-10-17T01:24:35Z
,其中Z
代表Zulu
,表示UTC。对于其他相对于UTC的偏移量,小时和分钟的偏移量会出现在末尾,例如2016-01-23T12:34:56+05:30
。java.time类扩展了此标准格式,以方括号形式附加时区名称(如果已知),例如2016-01-23T12:34:56+05:30[Asia/Kolkata]
。对于数据库存储,请使用日期时间类型来存储日期时间值,例如SQL标准数据类型,这些类型主要有DATE
,TIME
和TIMESTAMP WITH TIME ZONE
。
让您的JDBC驱动程序来处理繁琐的细节。驱动程序处理有关介绍和适应Java如何处理数据以及数据库如何在其一侧处理数据的内部的琐碎细节。但务必通过示例数据进行实践,以了解驱动程序和数据库的行为。 SQL标准对于日期时间处理定义很少,因此行为差异很大,出乎意料。
如果使用符合JDBC 4.2及更高版本的JDBC驱动程序,则可以直接通过ResultSet :: getObject
和PreparedStatement :: setObject
方法获取和存储java.time类型。
Instant instant = myResultSet.getObject( … );
myPreparedStatement.setObject( … , instant );
java.sql.Timestamp.toInstant()
。Instant instant = myResultSet.getTimestamp( … ).toInstant();
myPreparedStatement.setObject( … , java.sql.Timestamp.from( instant ) );
获取当前UTC时间。
Instant instant = Instant.now();
以上展示了将Instant
对象存储到数据库中并从中提取的方法。
要生成ISO 8601字符串,只需调用toString
。Java.time类默认使用ISO 8601格式来解析和生成各种日期时间值的字符串。
String output = instant.toString();
ZoneOffset
并获取 OffsetDateTime
。调用 toString
以生成ISO 8601格式的字符串。ZoneOffset offset = ZoneOffset.ofHoursMinutes( 5 , 30 );
OffsetDateTime odt = instant.atOffset( offset );
ZoneId
)以获取一个ZonedDateTime
对象。大陆/地区
格式指定一个正确的时区名称。永远不要使用三到四个字母的缩写,如EST
或IST
,因为它们不是真正的时区,不标准化,甚至不唯一(!)。ZoneId z = ZoneId.of( "Asia/Kolkata" );
ZonedDateTime zdt = instant.atZone( z );
如果你想从一个OffsetDateTime
或者ZonedDateTime
中提取出一个Instant
,你可以调用toInstant
方法。
Instant instant = zdt.toInstant();
如果要将时间以 ISO 8601 以外的其他格式呈现给用户,请搜索 Stack Overflow 使用 DateTimeFormatter
类。
虽然您可以指定自定义格式,但通常最好让 java.time 自动本地化。为了本地化,请指定:
FormatStyle
来确定字符串应该有多长或缩写。Locale
来确定 (a) 翻译日、月名称等人类语言和 (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”类型?。
使用对象而不是简单的编码原始数据和字符串。例如:
DayOfWeek
枚举,例如DayOfWeek.TUESDAY
。LocalDate
对象。YearMonth
对象。Month
枚举,例如Month.JANUARY
。为了开始讨论,这是我的经验:
在创建典型的三层Java企业项目的标准时,我通常建议使用GregorianCalendar来操作日期。原因是GregorianCalendar是事实上的标准,优于任何其他日历实例,例如Julian日历等。它是大多数国家公认的日历,并正确处理闰年等。除此之外,我建议应用程序将其日期存储为UTC,以便您可以轻松执行日期计算,例如查找两个日期之间的差异(如果它存储为EST,例如,您必须考虑夏令时)。然后可以将日期本地化为您需要显示给用户的任何时区,例如将其本地化为EST,如果您是美国东海岸公司,并且希望将时间信息显示为EST。
java.util.Date
,java.util.Calendar
和java.text.SimpleTextFormat
)现在已经成为遗留系统,被Java.time类所取代。 - Basil Bourque