【发布时间】:2010-01-30 15:33:55
【问题描述】:
操作和存储日期的最佳做法是什么?在企业 Java 应用程序中使用 GregorianCalendar?
寻求反馈,我会将任何好的答案整合到其他人可以使用的最佳实践中。
【问题讨论】:
-
bla bla bla,joda time,bla bla bla ...
操作和存储日期的最佳做法是什么?在企业 Java 应用程序中使用 GregorianCalendar?
寻求反馈,我会将任何好的答案整合到其他人可以使用的最佳实践中。
【问题讨论】:
最佳实践通常是不考虑繁重的日期对象,而是存储一个时间点。这通常是通过存储一个不受极端情况或潜在解析问题影响的值来完成的。为此,人们通常存储自我们称为纪元 (1970-01-01) 的固定点以来经过的毫秒数(或秒数)。这很常见,任何 Java API 都将始终允许您将任何类型的日期转换为自纪元以来以毫秒表示的时间。
那是为了存储。例如,如果需要,您还可以存储用户的首选时区。
现在这样的日期以毫秒为单位,例如:
System.out.println( System.currentTimeMillis() );
1264875453
当它显示给最终用户时不是很有用,这是理所当然的。
这就是为什么您使用示例 Joda time 将其转换为某种用户友好的格式,然后再显示给最终用户的原因。
您要求最佳实践,这是我的看法:在数据库中存储“日期”对象而不是以毫秒为单位的时间,使用浮点数表示货币金额。
这通常是一种巨大的代码气味。
所以 Java 中的 Joda 时间是操纵日期的方式,是的。但是 Joda 是去 store 约会的方法吗? 肯定不会。
【讨论】:
Joda 是要走的路。为什么?
在某个阶段,Java 日期/时间 API 将被取代(由 JSR-310)。我相信这将基于 Joda 背后的工作人员所做的工作,因此您将学习影响新标准 Java API 的 API。
【讨论】:
在UTC 而非任何时区中思考、工作和存储数据。将 UTC 视为 一个真实时间,而所有其他时区只是变体。因此,在编码时,请忘记您自己的时区。以 UTC 执行您的业务逻辑、日志记录、数据存储和数据交换。我建议每个程序员在他们的办公桌上放第二个时钟,设置为 UTC。
现代方式是java.time 类。
提到的Joda-Time 项目为 java.time 类提供了灵感,该项目现在处于维护模式,团队建议迁移到 java.time 类。
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date、.Calendar 和 java.text.SimpleDateFormat。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
从哪里获得 java.time 类?
ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。
将日期时间值序列化为文本时,请使用ISO 8601 标准。
例如,UTC 的日期时间是2016-10-17T01:24:35Z,其中Z 是Zulu 的缩写,表示UTC。对于其他offset-from-UTC,小时和分钟的偏移量出现在末尾,例如2016-01-23T12:34:56+05:30。 java.time 类扩展了这种标准格式,以在方括号中附加时区名称(如果知道),例如2016-01-23T12:34:56+05:30[Asia/Kolkata]。
该标准还有许多其他方便的格式,包括durations、intervals、ordinals 和year-week。
对于数据库存储,对日期时间值使用日期时间类型,例如 SQL standard data types,主要是 DATE、TIME 和 TIMESTAMP WITH TIME ZONE。
让您的JDBC driver 完成繁重的工作。驱动程序处理有关在 Java 如何处理数据的内部结构和数据库如何处理其一侧的数据之间进行调解和调整的基本细节。但请务必使用示例数据进行练习,以了解驱动程序和数据库的行为。 SQL 标准对日期时间处理的定义很少,因此行为差异很大,令人惊讶。
如果使用符合 JDBC 4.2 及更高版本的 JDBC 驱动程序,您可以通过 ResultSet::getObject 和 PreparedStatement::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 的子类,它确实在UTC 中将时间设置为00:00:00。而且,哦,您应该忽略类文档中说的继承这一事实。一团糟。
获取 UTC 中的当前时刻。
Instant instant = Instant.now();
上面显示了将 Instant 对象存储到/从数据库中获取。
要生成 ISO 8601 字符串,只需调用 toString。默认情况下,java.time 类都使用 ISO 8601 格式来解析和生成各种日期时间值的字符串。
String output = instant.toString();
通过应用ZoneOffset 来调整到任何与UTC 的偏移量以获得OffsetDateTime。调用 toString 生成 ISO 8601 格式的字符串。
ZoneOffset offset = ZoneOffset.ofHoursMinutes( 5 , 30 );
OffsetDateTime odt = instant.atOffset( offset );
时区是一个偏移量加上一组用于处理异常的规则,例如Daylight Saving Time (DST)。当您需要通过某个地区自己的wall-clock time 的镜头看到同一时刻时,应用时区 (ZoneId) 以获取 ZonedDateTime 对象。
以continent/region 的格式指定proper time zone name。切勿使用 3-4 个字母的缩写,例如 EST 或 IST,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
ZoneId z = ZoneId.of( "Asia/Kolkata" );
ZonedDateTime zdt = instant.atZone( z );
反过来,您可以通过调用toInstant 从OffsetDateTime 或ZonedDateTime 中提取Instant。
Instant instant = zdt.toInstant();
用于以非 ISO 8601 格式的字符串形式呈现给用户,search 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.time 类型转换。详情见问题Convert java.util.Date to what “java.time” type?。
使用对象,而不仅仅是编码的原语和简单的字符串。例如:
DayOfWeek 枚举,例如 DayOfWeek.TUESDAY。 LocalDate 对象。 YearMonth 对象。 Month 枚举,而不是一个月的1-12,例如Month.JANUARY。使用此类对象可以让您的代码更加自记录,确保有效值,并提供type-safety。
【讨论】:
为了开始讨论,这是我的经验:
在为典型的 3 层 Java Enterprise 项目创建标准时,我通常建议项目使用 GregorianCalendar 来处理日期。原因是 GregorianCalendar 是任何其他 Calendar 实例的事实上的标准,例如儒略历等。它是大多数国家/地区公认的日历,并且可以正确处理闰年等。最重要的是,我建议应用程序将其日期存储为 UTC,以便您可以轻松执行日期计算,例如查找两者之间的差异日期(例如,如果它存储为 EST,则必须考虑夏令时)。然后可以将日期本地化到您需要将其显示给用户的任何时区,例如,如果您是美国东海岸的一家公司,并且您希望在 EST 中显示您的时间信息,则将其本地化为 EST。
【讨论】:
java.util.Date、java.util.Calendar 和 java.text.SimpleTextFormat 等旧日期时间类现在是 legacy,被 java.time 类取代。