Java 日期与日历

Posted

技术标签:

【中文标题】Java 日期与日历【英文标题】:Java Date vs Calendar 【发布时间】:2010-11-27 02:44:39 【问题描述】:

有人可以就DateCalendar 类型的当前“最佳实践”提出建议。

在编写新代码时,最好始终偏爱Calendar 而不是Date,还是在某些情况下Date 是更合适的数据类型?

【问题讨论】:

乔达时间:joda-time.sourceforge.net 仅供参考,java.util.Datejava.util.Calendarjava.text.SimpleDateFormat 等麻烦的旧日期时间类现在已被 java.time 类所取代。许多 java.time 功能在ThreeTen-Backport 项目中被反向移植到Java 6 和Java 7。在ThreeTenABP 项目中进一步适用于早期的android。见How to use ThreeTenABP… 仅供参考,Joda-Time 项目(在另一条评论中提到)现在位于maintenance mode,团队建议迁移到java.time 类。见Tutorial by Oracle。 【参考方案1】:

Date 是一个更简单的类,主要是出于向后兼容的原因。如果您需要设置特定日期或进行日期算术,请使用日历。日历也处理本地化。 Date 之前的日期操作函数已被弃用。

我个人倾向于使用以毫秒为单位的时间作为 long(或 Long,视情况而定)或 Calendar(如果有选择)。

日期和日历都是可变的,这在 API 中使用时往往会出现问题。

【讨论】:

仅供参考,非常麻烦的旧日期时间类,如 java.util.Datejava.util.Calendarjava.text.SimpleDateFormat 现在是 legacy,被 Java 8 中内置的 java.time 类所取代,之后。见Tutorial by Oracle。【参考方案2】:

新代码(如果您的政策允许第三方代码)的最佳方式是使用Joda Time library。

Date 和 Calendar 都有很多设计问题,对于新代码来说都不是很好的解决方案。

【讨论】:

我同意使用 joda time 的建议。它更易于使用和理解,并提供更多您可以使用的功能。 关于是使用 Joda Time 还是坚持使用标准 JDK 类的讨论,请参阅***.com/questions/589870/… 我不知道它是否值得被否决,但它没有回答这个问题。作为评论可能会更好。 因为问题是关于使用日期和日历而不是使用第三方库,这会给项目增加单一供应商依赖风险。 在 Java 8 提供 java.time package 之前,这是“最佳方式”。【参考方案3】:

DateCalendar 实际上是相同的基本概念(两者都代表即时,并且是底层long 值的包装器)。

有人可能会争辩说,Calendar 实际上比Date 更糟糕,因为它似乎提供了关于诸如星期几和一天中的时间之类的具体事实,而如果你改变它的timeZone 属性,混凝土就会变成blancmange!由于这个原因,这两个对象都不能真正用作 year-month-daytime-of-day 的存储。

仅将Calendar 用作计算器,当给定DateTimeZone 对象时,它将为您进行计算。避免将其用于应用程序中的属性输入。

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

如果您喜欢冒险,请使用 Joda-Time,尽管恕我直言,它不必要地复杂,并且无论如何很快就会被 JSR-310 日期 API 取代。

我之前已经回答过,创建自己的 YearMonthDay 类并不难,它在底层使用 Calendar 进行日期计算。我对这个建议投了反对票,但我仍然相信这是一个有效的建议,因为Joda-Time(和JSR-310)对于大多数用例来说确实过于复杂了。

【讨论】:

JSR310 有时间框架吗?它将在 Java 7 中使用,但我相信现在情况并非如此。 @Brian - 在那个邮件列表上肯定已经很安静了! 只是检查一下,它已经处于非活动状态,这意味着他们在 18 个月内没有发布里程碑草案 :-( 邮件列表上的最新评论来自 7 月和斯蒂芬,因此该项目可能仍在进行中 同意。如果您知道如何安全地使用 Date 作为不可变对象,并且知道如何使用 Calendar 来操作 Dates,那么大多数人应该是安全的。在多线程代码中使用 SimpleDateFormat 时要小心。【参考方案4】:

日期最适合存储日期对象。它是持久化的,序列化的......

日历最适合处理日期。

注意:我们有时也喜欢 java.lang.Long 而不是 Date,因为 Date 是可变的,因此不是线程安全的。在 Date 对象上,使用 setTime() 和 getTime() 在两者之间切换。例如,应用程序中的常量 Date(例如:零 1970/01/01,或您设置为 2099/12/31 的应用 END_OF_TIME ;这些对于将空值替换为开始时间和结束时间非常有用,尤其是当您将它们持久保存在数据库中时,因为 SQL 对空值非常特殊)。

【讨论】:

我认为你在谈论不可变的java.lang.Long【参考方案5】:

tl;博士

建议围绕DateCalendar 的当前“最佳实践”

最好总是支持Calendar 而不是Date

完全避免使用这些遗留类。请改用java.time 类。

暂时在UTC 中使用Instant(现代相当于Date) 在特定的time zone 中,使用ZonedDateTime(现代相当于GregorianCalendar) 在特定的offset-from-UTC 中暂时使用OffsetDateTime(旧类中没有等效项) 对于时区或偏移量未知的日期时间(不是片刻),请使用 LocalDateTime(旧类中没有等效项)

详情

Answer by Ortomala Lokni 建议使用现代的java.time 类而不是麻烦的旧的旧日期时间类(DateCalendar 等)是正确的。但是该答案表明错误的类是等效的(请参阅我对该答案的评论)。

使用 java.time

java.time 类是对传统日期时间类(昼夜差异)的巨大改进。旧的类设计不良、混乱且麻烦。您应该尽可能避免使用旧课程。但是当您需要转换到/从旧/新时,您可以通过调用添加到 old 类的新方法来实现。

有关转换的更多信息,请参阅my Answer and nifty diagram 到另一个问题,Convert java.util.Date to what “java.time” type?。

搜索 Stack Overflow 提供了数百个有关使用 java.time 的示例问题和答案。但这里有一个简短的概要。

Instant

使用Instant 获取当前时刻。 Instant 类代表UTC 时间线上的时刻,分辨率为nanoseconds(最多九 (9) 位小数)。

Instant instant = Instant.now();

ZonedDateTime

要通过某个特定区域的wall-clock time 的镜头看到同一时刻,请应用时区 (ZoneId) 以获得ZonedDateTime

时区

continent/region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 3-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = instant.atZone();

偏移

时区是一个地区在其offset-from-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 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。

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

从哪里获得 java.time 类?

Java SE 8Java SE 9 及更高版本 内置。 标准 Java API 的一部分,带有捆绑实现。 Java 9 添加了一些小功能和修复。 Java SE 6Java SE 7 大部分 java.time 功能都在ThreeTen-Backport 中向后移植到 Java 6 和 7。 Android 更高版本的 Android 捆绑包实现 java.time 类。 对于早期的 Android,ThreeTenABP 项目采用 ThreeTen-Backport(如上所述)。见How to use ThreeTenABP…

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuarter 和more。

【讨论】:

感谢您的详细解释。我是 Java 新手,我将开始使用 java.time。【参考方案6】:

如果可能,我通常使用日期。尽管它是可变的,但实际上不推荐使用 mutators。最后,它基本上包装了一个代表日期/时间的 long 。相反,如果我必须操纵这些值,我会使用日历。

您可以这样想:仅当您需要拥有可以轻松操作的字符串然后使用 toString() 方法将它们转换为字符串时才使用 StringBuffer。同样,我只在需要操作时态数据时才使用日历。

为了最佳实践,我倾向于在领域模型之外尽可能多地使用不可变对象。它显着减少了任何副作用的机会,并且它是由编译器为您完成的,而不是 JUnit 测试。您可以通过在类中创建 private final 字段来使用此技术。

回到 StringBuffer 的类比。下面是一些代码,向您展示如何在日历和日期之间进行转换

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);

【讨论】:

是的,immutable objects 对日期时间工作有意义。取代 Date/Calendarjava.time 类使用不可变对象模式。 这可能是真的,但在 2010 年不会。 是的。但是现在有成千上万的人在阅读这个页面,到目前为止已经超过 150,000 人。我的评论是给他们的便条,而不是对你的批评。 我知道,这就是为什么我赞成另一个答案。但是,仍然有一些人需要忍受旧 JDK 的痛苦,他们也需要上述答案。 实际上,对于 Java 6 和 7,我们有 ThreeTen-Backport 项目。这带来了具有几乎相同 API 的大部分 java.time 功能。所以没有必要使用那些糟糕的遗留日期时间类。【参考方案7】:

Dates 应该用作不可变的时间点; Calendars 是可变的,如果您需要与其他类协作以得出最终日期,可以传递和修改。将它们与StringStringBuilder 类似,您就会明白我认为应该如何使用它们。

(是的,我知道 Date 在技术上实际上不是不可变的,但目的是它不应该是可变的,如果没有调用过时的方法,那么它就是这样。)

【讨论】:

是的,immutable objects 对日期时间工作有意义。取代 Date/Calendarjava.time 类使用不可变对象模式。具体来说,Instant 替换 java.util.DateZonedDateTime 替换 Calendar/GregorianCalendar【参考方案8】:

对于 Java 8,应该使用新的 java.time package。

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

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

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

【讨论】:

很好地推荐 java.time 类。但不好建议LocalDateTime。该类故意丢失有关从 UTC 偏移和时区的任何信息。因此,该类 等效于 Date 是 UTC 并且 Calendar 具有指定的时区。请参阅 my Answer and nifty diagram 到另一个问题 Convert java.util.Date to what “java.time” type?。【参考方案9】:

我一直提倡Joda-time。原因如下。

    API 一致且直观。与 java.util.Date/Calendar API 不同 与java.text.SimpleDateFormat 等不同,它不受线程问题的影响(我见过许多与没有意识到标准日期/时间格式不是线程安全的客户问题有关) 它是新 Java 日期/时间 API(JSR310,计划用于 Java 8)的基础。因此,您将使用将成为核心 Java API 的 API。

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

【讨论】:

我上次查看时,JODA 和 JSR-310 看起来非常不同,即使它们都是由 Stephen Colebourne 编写的。也就是说,JODA 将向您介绍 JSR-310 也解决的日期时间问题的复杂性 因为问题是关于使用日期和日历而不是使用第三方库,这会给项目增加单一供应商依赖风险。 我认为最好的做法就是不使用这些类 鉴于 java.util.Date 和 Calendar 类的已知问题,建议 Joda-Time(或 JSR 310)对我来说似乎合适且负责。我们不是在谈论品味或审美风格的问题。如果有人问红车还是银车,我知道红车轮胎漏气,银车散热器坏了,我应该选车还是建议叫出租车?这个问题的答案现在似乎很明显,因为即使是 Sun/Oracle 也决定抛开这些垃圾并购买一辆新车:JSR 310:日期和时间 API。 仅供参考,Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。见Tutorial by Oracle。【参考方案10】:

聚会有点晚了,但是 Java 在 JDK 8 中有一个新的日期时间 API。您可能想要升级您的 JDK 版本并采用该标准。不再有凌乱的日期/日历,不再有第 3 方罐子。

【讨论】:

【参考方案11】:

日期应该重新制定。它不应该是一个长整数,而是应该将年、月、日、小时、分钟、秒作为单独的字段保存。存储与此日期相关联的日历和时区可能会更好。

在我们的自然对话中,如果在 2013 年 11 月 1 日下午 1 点纽约时间安排约会,这是一个日期时间。它不是日历。所以我们在 Java 中也应该能够像这样进行对话。

当 Date 存储为长整数(自 1970 年 1 月 1 日或其他时间以来的毫秒数)时,计算其当前日期取决于日历。不同的日历会给出不同的日期。这是从给出一个绝对时间的角度来看(例如大爆炸后的 1 万亿秒)。但通常我们还需要一种方便的对话方式,比如封装年、月等的对象。

我想知道 Java 是否有新的进展来协调这两个目标。也许我的java知识太老了。

【讨论】:

您可以及时存储相同的瞬间,但根据不同的日历系统报告不同的小时/分钟/天/周/年/foo,这是一种优势,而不是劣势。它反映了(复杂的)现实。 确实,Date 已经重新开发了;替换为 java.time.Instant 类。而Calendar/GregorianCalendarjava.time.ZonedDateTime 类取代。【参考方案12】:

顺便说一句,“日期”通常被标记为“过时/已弃用”(我不知道确切原因)——那里写了一些关于它的东西 Java: Why is the Date constructor deprecated, and what do I use instead?

看起来这是构造函数的问题-通过 new Date(int year, int month, int day),推荐的方法是通过 Calendar 和 set参数分开 .. (Calendar cal = Calendar.getInstance(); )

【讨论】:

【参考方案13】:

当我需要对日期进行一些特定操作(如时间移动)时,我会使用日历,但是当您需要格式化日期以适应您的需求时,我发现它很有帮助,最近我发现 Locale 有很多有用的操作和方法。所以我现在正在使用 Locale!

【讨论】:

仅供参考,麻烦的 CalendarDate 类在几年前被 java.time 类所取代。无需使用DateCalendar。而Locale 与日期时间对象的含义无关。 Locale 仅用于指定用于本地化的人类语言和文化规范,同时生成表示日期时间对象值的文本。

以上是关于Java 日期与日历的主要内容,如果未能解决你的问题,请参考以下文章

调试与日历日期 GMT 相关的简单 java 代码

时间操作类与日历类

JAVA编写简单的日历,输入日期即可查看日历

用java编辑日历记事本

Java输出日历

日历的日期对象 [Java]