Java Date与Calendar的区别

389

请问有人能指导一下目前关于DateCalendar类型的"最佳实践"吗?

在编写新代码时,是最好总是优先使用Calendar而不是Date,还是有一些情况下Date是更合适的数据类型呢?


28
Joda Time是一个Java日期和时间API库,旨在提供比Java标准库更丰富、更易用的功能。它支持ISO 8601标准,包括那些涉及时区的复杂日期和时间操作。Joda Time还具有良好的可扩展性,可以自定义日历系统和时间机制。 - R. Martinho Fernandes
3
注意,那些令人头疼的旧日期时间类,如 java.util.Datejava.util.Calendarjava.text.SimpleDateFormat,现在已经过时了,被 java.time 类所取代。许多 java.time 的功能通过 ThreeTen-Backport 项目移植到了 Java 6 和 Java 7 中。在 ThreeTenABP 项目中进一步适用于早期的 Android 版本。请参阅 如何使用ThreeTenABP…… - Basil Bourque
3
FYI,之前评论里提到的 Joda-Time 项目现在处于 维护模式,团队建议迁移到 java.time 类。请参考Oracle的教程 - Basil Bourque
13个回答

385

日期是一个简单的类,主要是为了向后兼容而存在。如果您需要设置特定的日期或进行日期运算,请使用日历(Calendar)。 日历还处理本地化。 Date之前的日期操作函数已被弃用。

个人倾向于在有选择时使用毫秒时间作为长整型(或适当的Long)或使用日历(Calendar)。

日期和日历(Calendar)都是可变的,这往往会在API中出现问题。


6
提醒一下,诸如java.util.Datejava.util.Calendarjava.text.SimpleDateFormat等旧的日期时间类现在已经成为遗留系统,被内置于Java 8及更高版本中的java.time类所替代。请参考Oracle的教程 - Basil Bourque

66

7
我赞同使用joda time的建议。它更易于使用和理解,并提供了更多可用的功能。 - Jeroen van Bergen
31
讨论是否使用Joda Time或坚持使用标准JDK类,请参见https://dev59.com/JnRB5IYBdhLWcg3wj32c - Jonik
5
不确定是否值得被踩,但是它并没有回答问题。或许只是作为评论更好。 - IcedDante
7
因为问题是关于使用Date和Calendar而不是使用第三方库,这会给项目增加单一供应商依赖风险。 - Archimedes Trajano
4
在Java 8之前,这是“最佳方法”,直到引入Java.time包 - DaBlick
显示剩余3条评论

60
  • DateCalendar实际上是同一个基本概念(都代表时间的瞬间,并且都是基于一个底层的long值封装而成)。

  • 可以说,CalendarDate还要糟糕,因为它似乎提供了关于星期几和时间的具体信息,但如果更改其timeZone属性,则具体信息将变成无用的。由于这个原因,这两个对象都不适合用作存储年月日时间的容器。

  • 只有在给定DateTimeZone对象时,才使用Calendar进行计算。避免在应用程序中将其用于属性类型定义。

  • 使用SimpleDateFormatTimeZoneDate一起生成显示字符串。

  • 如果你感到冒险,可以使用Joda-Time,尽管我认为它过于复杂,在任何情况下都很快就会被JSR-310日期API所取代。

  • 我曾经回答过,编写自己的YearMonthDay类并不难,它在日期计算时使用Calendar。尽管我的建议受到了负面评价,但我仍然认为这是一个有效的建议,因为对于大多数用例来说,Joda-Time(和JSR-310)实际上过于复杂。


1
JSR310有时间框架吗?它原本计划在Java 7中发布,但我相信现在不是这种情况了。 - Brian Agnew
只是检查一下,它已经变成了不活跃状态,这意味着他们已经18个月没有发布里程碑草稿了 :-( - Brian Agnew
我喜欢2009年末关于Joda“很快将被JSR-310日期API取代”的乐观态度。 - duelin markers
JSR-310已作为Java 8的一部分发布。 - miguel
我认为,与Joda-Time和甚至日历/日期组合相比,它并不是“超级复杂”的。 我会说,一旦你理解了计算机中日期/时间背后的理论以及它们如何计算(我认为这是日期最大的核心问题),那么API就更有意义了(虽然还有一些奇怪的边缘情况)。 Java Date/Calendar的问题在于,大多数人(包括我在内)在开始使用它们时并没有正确地理解日期时间的功能以及它们如何计算。 - Martin Marconcini
显示剩余3条评论

39

简述:

建议在使用 DateCalendar 时遵循当前的“最佳实践”

是否始终应优先使用 Calendar 而不是 Date

完全避免使用这些过时的类,而应改用 java.time 类。

  • 对于一个UTC的短暂时刻,使用InstantDate的现代等效物)
  • 对于一个特定时区的时刻,使用ZonedDateTimeGregorianCalendar的现代等效物)
  • 对于一个特定UTC偏移量的时刻,使用OffsetDateTime(遗留类中没有相应的等效物)
  • 对于日期时间(非瞬时时刻)并且未知时区或偏移量,使用LocalDateTime(遗留类中没有相应的等效物)

Table of all date-time types in Java, both modern and legacy

详情

Ortomala的回答建议使用现代java.time类而不是麻烦的旧日期时间类(DateCalendar等)。但是,该回答提供的等效类是错误的(请参阅我在该回答上的评论)。

使用java.time

java.time类是对旧日期时间类的巨大改进,完全不同。旧类设计不良,令人困惑且麻烦。尽可能避免使用旧类。但是当您需要将旧/新进行转换时,可以通过调用新方法添加到旧类中来实现。

有关转换的更多信息,请参见我的回答和巧妙的图表,另一个问题将java.util.Date转换为何种“java.time”类型?

在 Stack Overflow 上搜索可以找到许多关于使用 java.time 的问题和答案示例。但是这里有一个快速概述。

Instant

使用 Instant 获取当前时刻。Instant 类表示时间线上的一个时刻,在 UTC 时区中,精度为 纳秒(最多九位小数)。

Instant instant = Instant.now();

ZonedDateTime

通过一定地区的挂钟时间镜头看到同一时刻,应用时区(ZoneId)以获取ZonedDateTime

时区

请指定一个格式为大陆/地区正确时区名称,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用三到四个字母的缩写,如ESTIST,因为它们不是真正的标准化时区,也不是唯一的!
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...类中的“本地”表示任何地方,而不是特定的地方。因此,名称可能会让人感到反直觉。

LocalDateTimeLocalDateLocalTime故意缺乏有关偏移量或时区的任何信息。因此,它们不代表实际时刻,也不是时间轴上的点。当存在疑惑或混淆时,请使用ZonedDateTime而不是LocalDateTime。在Stack Overflow上搜索更多讨论。

字符串

不要将日期时间对象与表示其值的字符串混为一谈。您可以解析字符串以获取日期时间对象,并且可以从日期时间对象生成字符串。但是,字符串永远不是日期时间本身。

了解标准ISO 8601格式,这是java.time类中默认使用的格式。


关于 java.time

java.time 框架是内置于 Java 8 及更高版本中的。这些类替代了老旧的 遗留 日期时间类,如 java.util.DateCalendarSimpleDateFormat

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

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

使用符合JDBC 4.2或更高版本的JDBC驱动程序,您可以直接与数据库交换java.time对象。无需字符串或java.sql.*类。

如何获取java.time类?

Table of which java.time library to use with which version of Java or Android

ThreeTen-Extra项目扩展了java.time的附加类。该项目是java.time可能未来添加的试验场。您可能会在这里找到一些有用的类,例如Interval, YearWeek, YearQuarter更多

感谢您提供这么详细的解释。我是Java的新手,我将开始使用java.time。 - raphael75

26

使用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

17

如果可能的话,我通常使用日期(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/Calendarjava.time类使用了不可变对象模式。 - Basil Bourque
可能是真的,但不适用于2010年。 - Archimedes Trajano
是的,但现在有成千上万的人正在阅读这个页面,到目前为止已经超过了150,000人。我的评论是给他们的一条记录,而不是对你的批评。 - Basil Bourque
我知道,这就是为什么我点赞了另一个答案。然而,仍有一些人需要使用旧版JDK,因此他们也需要上面的答案。 - Archimedes Trajano
1
实际上,对于Java 6和7,我们有ThreeTen-Backport项目。这个项目使用几乎相同的API带来了大部分java.time功能。因此,没有必要再使用那些可怕的遗留日期时间类。 - Basil Bourque

15

Date应该被视为不可变的时间点;Calendar是可变的,如果您需要与其他类协作来确定最终日期,则可以传递并修改它们。将它们类比于StringStringBuilder,您就能理解我认为它们应该如何使用。

(是的,我知道Date实际上不是技术上不可变的,但意图是它不应该是可变的,并且如果没有调用已弃用的方法,它就是不可变的。)


是的,不可变对象对于日期时间处理很有意义。取代Date/Calendarjava.time类使用了不可变对象模式。具体来说,Instant替换了java.util.Date,而ZonedDateTime则替换了Calendar/GregorianCalendar - Basil Bourque

11

在Java 8中,应使用新的java.time包

对象是不可变的,考虑了时区和夏令时。

您可以像这样从旧的java.util.Date对象创建一个ZonedDateTime 对象:

    Date date = new Date();
    ZonedDateTime zonedDateTime = date.toInstant().atZone(ZoneId.systemDefault());

建议使用java.time类是好的。但是不建议使用LocalDateTime类。该类有意丢失与UTC偏移量和时区相关的任何信息。因此,该类与Date在UTC中的等效性以及Calendar具有分配的时区是不同的。请参见我的答案和巧妙的图表以回答另一个问题,将java.util.Date转换为“java.time”类型? - Basil Bourque

9

Java在JDK 8中推出了新的日期时间API,可能需要升级您的JDK版本并使用该标准API。不再需要混乱的日期/日历,也不需要第三方Jar包。


9
我一直推崇Joda-time,以下是原因:
  1. 其API一致且直观。不像java.util.Date/Calendar API
  2. 它不会出现线程问题,不像java.text.SimpleDateFormat等。(我见过很多客户问题与没有意识到标准日期/时间格式不是线程安全有关)
  3. 它是新Java日期/时间API的基础(JSR310),计划用于Java 8。因此,您将使用成为核心Java API的API。

编辑:如果您可以升级到Java 8,则Java 8引入的日期/时间类现在是首选解决方案。


3
我上次看的时候,JODA和JSR-310看起来非常不同,即使它们都是由Stephen Colebourne编写的。话虽如此,JODA会向您介绍日期时间问题的复杂性,而JSR-310也解决了这些问题。 - oxbow_lakes
2
因为问题是关于使用 Date 和 Calendar,而不是使用第三方库,这会给项目增加一个单一供应商依赖风险。 - Archimedes Trajano
2
我认为最佳实践就是不要使用那些类。 - Brian Agnew
3
考虑到java.util.Date和Calendar类存在已知问题,我认为建议使用Joda-Time(或JSR 310)是合适和负责任的。这不是一种口味或美学风格的问题。如果有人问我他们应该选择红色车还是银色车,而我知道红色车有一个瘪胎,银色车有一个破发动机,那我是该选车还是建议打车呢?现在这个问题的答案似乎很明显,即使Sun/Oracle也决定放弃这些废物并买一辆新车:JSR 310: 日期和时间API。 - Basil Bourque
2
FYI,Joda-Time 项目现已进入维护模式,建议迁移到java.time类。请参阅Oracle的教程 - Basil Bourque
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接