请问有人能指导一下目前关于Date
和Calendar
类型的"最佳实践"吗?
在编写新代码时,是最好总是优先使用Calendar
而不是Date
,还是有一些情况下Date
是更合适的数据类型呢?
日期是一个简单的类,主要是为了向后兼容而存在。如果您需要设置特定的日期或进行日期运算,请使用日历(Calendar)。 日历还处理本地化。 Date之前的日期操作函数已被弃用。
个人倾向于在有选择时使用毫秒时间作为长整型(或适当的Long)或使用日历(Calendar)。
日期和日历(Calendar)都是可变的,这往往会在API中出现问题。
java.util.Date
、java.util.Calendar
和java.text.SimpleDateFormat
等旧的日期时间类现在已经成为遗留系统,被内置于Java 8及更高版本中的java.time类所替代。请参考Oracle的教程。 - Basil Bourque如果您的政策允许使用第三方代码,那么新代码最好使用Joda Time库。
日期(Date)和日历(Calendar)都存在很多设计问题,因此它们都不是新代码的好解决方案。
Date
和Calendar
实际上是同一个基本概念(都代表时间的瞬间,并且都是基于一个底层的long
值封装而成)。
可以说,Calendar
比Date
还要糟糕,因为它似乎提供了关于星期几和时间的具体信息,但如果更改其timeZone
属性,则具体信息将变成无用的。由于这个原因,这两个对象都不适合用作存储年月日或时间的容器。
只有在给定Date
和TimeZone
对象时,才使用Calendar
进行计算。避免在应用程序中将其用于属性类型定义。
使用SimpleDateFormat
与TimeZone
和Date
一起生成显示字符串。
如果你感到冒险,可以使用Joda-Time,尽管我认为它过于复杂,在任何情况下都很快就会被JSR-310日期API所取代。
我曾经回答过,编写自己的YearMonthDay
类并不难,它在日期计算时使用Calendar
。尽管我的建议受到了负面评价,但我仍然认为这是一个有效的建议,因为对于大多数用例来说,Joda-Time(和JSR-310)实际上过于复杂。
建议在使用
Date
和Calendar
时遵循当前的“最佳实践”是否始终应优先使用
Calendar
而不是Date
完全避免使用这些过时的类,而应改用 java.time 类。
Instant
(Date
的现代等效物)ZonedDateTime
(GregorianCalendar
的现代等效物)OffsetDateTime
(遗留类中没有相应的等效物)LocalDateTime
(遗留类中没有相应的等效物)Ortomala的回答建议使用现代java.time类而不是麻烦的旧日期时间类(Date
,Calendar
等)。但是,该回答提供的等效类是错误的(请参阅我在该回答上的评论)。
java.time类是对旧日期时间类的巨大改进,完全不同。旧类设计不良,令人困惑且麻烦。尽可能避免使用旧类。但是当您需要将旧/新进行转换时,可以通过调用新方法添加到旧类中来实现。
有关转换的更多信息,请参见我的回答和巧妙的图表,另一个问题将java.util.Date转换为何种“java.time”类型?。
在 Stack Overflow 上搜索可以找到许多关于使用 java.time 的问题和答案示例。但是这里有一个快速概述。Instant
使用 Instant
获取当前时刻。Instant
类表示时间线上的一个时刻,在 UTC 时区中,精度为 纳秒(最多九位小数)。
Instant instant = Instant.now();
ZonedDateTime
通过一定地区的挂钟时间镜头看到同一时刻,应用时区(ZoneId
)以获取ZonedDateTime
。
大陆/地区
的正确时区名称,例如America/Montreal
,Africa/Casablanca
或Pacific/Auckland
。切勿使用三到四个字母的缩写,如EST
或IST
,因为它们不是真正的标准化时区,也不是唯一的!ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = instant.atZone();
时区是一个地区其与协调世界时 (UTC) 的 偏移量历史变化的记录。但有时您只能看到偏移量而没有完整的时区信息。在这种情况下,可以使用 OffsetDateTime
类。
ZoneOffset offset = ZoneOffset.parse( "+05:30" );
OffsetDateTime odt = instant.atOffset( offset );
使用时区比仅使用偏移量更可取。
LocalDateTime
Local...
类中的“本地”表示任何地方,而不是特定的地方。因此,名称可能会让人感到反直觉。
LocalDateTime
、LocalDate
和LocalTime
故意缺乏有关偏移量或时区的任何信息。因此,它们不代表实际时刻,也不是时间轴上的点。当存在疑惑或混淆时,请使用ZonedDateTime
而不是LocalDateTime
。在Stack Overflow上搜索更多讨论。
不要将日期时间对象与表示其值的字符串混为一谈。您可以解析字符串以获取日期时间对象,并且可以从日期时间对象生成字符串。但是,字符串永远不是日期时间本身。
了解标准ISO 8601格式,这是java.time类中默认使用的格式。
java.time 框架是内置于 Java 8 及更高版本中的。这些类替代了老旧的 遗留 日期时间类,如 java.util.Date
、Calendar
和 SimpleDateFormat
。
Joda-Time项目现在处于维护模式,建议迁移到java.time类。
要了解更多信息,请参阅Oracle教程。并搜索Stack Overflow以获取许多示例和解释。规范为JSR 310。
使用符合JDBC 4.2或更高版本的JDBC驱动程序,您可以直接与数据库交换java.time对象。无需字符串或java.sql.*类。
如何获取java.time类?
Interval
, YearWeek
, YearQuarter
和更多。使用Date对象存储日期是最佳的选择,它是持久化和序列化的。
使用Calendar操作日期是最好的选择。
注意:有时我们也会喜欢使用java.lang.Long而不是Date,因为Date是可变的,因此不是线程安全的。在Date对象上,使用setTime()和getTime()在两者之间进行切换。例如,应用程序中的常量日期(例如:1970/01/01零日期或将其设置为2099/12/31的应用END_OF_TIME;这些非常有用,以替换空值作为开始时间和结束时间,特别是当您将它们持久化到数据库中时,因为SQL对null值非常特殊)。
java.lang.Long
。 - pjp如果可能的话,我通常使用日期(Date)。尽管它是可变的,但实际上修改器已经被弃用了。最后,它基本上包装了一个表示日期/时间的长整型。相反,如果我必须操作这些值,我会使用日历(Calendars)。
你可以这样想:只有在需要轻松操纵字符串并使用toString()方法将其转换为字符串时,才会使用StringBuffer。同样地,我只会在需要操作时间数据时使用日历(Calendar)。
出于最佳实践的考虑,在domain model以外,我倾向于尽可能使用不可变对象。这显著减少了任何副作用的可能性,并且由编译器而非JUnit测试为您完成。您可以通过在类中创建private final字段来使用此技术。
回到StringBuffer类比。下面是一些代码,展示如何在Calendar和Date之间进行转换:
String s = "someString"; // immutable string
StringBuffer buf = new StringBuffer(s); // mutable "string" via StringBuffer
buf.append("x");
assertEquals("someStringx", buf.toString()); // convert to immutable String
// immutable date with hard coded format. If you are hard
// coding the format, best practice is to hard code the locale
// of the format string, otherwise people in some parts of Europe
// are going to be mad at you.
Date date = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse("2001-01-02");
// Convert Date to a Calendar
Calendar cal = Calendar.getInstance();
cal.setTime(date);
// mutate the value
cal.add(Calendar.YEAR, 1);
// convert back to Date
Date newDate = cal.getTime();
//
assertEquals(new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse("2002-01-02"), newDate);
Date
应该被视为不可变的时间点;Calendar
是可变的,如果您需要与其他类协作来确定最终日期,则可以传递并修改它们。将它们类比于String
和StringBuilder
,您就能理解我认为它们应该如何使用。
(是的,我知道Date实际上不是技术上不可变的,但意图是它不应该是可变的,并且如果没有调用已弃用的方法,它就是不可变的。)
Date
/Calendar
的java.time类使用了不可变对象模式。具体来说,Instant
替换了java.util.Date
,而ZonedDateTime
则替换了Calendar
/GregorianCalendar
。 - Basil Bourque在Java 8中,应使用新的java.time包。
对象是不可变的,考虑了时区和夏令时。
您可以像这样从旧的java.util.Date
对象创建一个ZonedDateTime
对象:
Date date = new Date();
ZonedDateTime zonedDateTime = date.toInstant().atZone(ZoneId.systemDefault());
LocalDateTime
类。该类有意丢失与UTC偏移量和时区相关的任何信息。因此,该类与Date
在UTC中的等效性以及Calendar
具有分配的时区是不同的。请参见我的答案和巧妙的图表以回答另一个问题,将java.util.Date转换为“java.time”类型?。 - Basil BourqueJava在JDK 8中推出了新的日期时间API,可能需要升级您的JDK版本并使用该标准API。不再需要混乱的日期/日历,也不需要第三方Jar包。
编辑:如果您可以升级到Java 8,则Java 8引入的日期/时间类现在是首选解决方案。
java.util.Date
、java.util.Calendar
和java.text.SimpleDateFormat
,现在已经过时了,被 java.time 类所取代。许多 java.time 的功能通过 ThreeTen-Backport 项目移植到了 Java 6 和 Java 7 中。在 ThreeTenABP 项目中进一步适用于早期的 Android 版本。请参阅 如何使用ThreeTenABP……。 - Basil Bourque