Java 日期和时间API有哪些问题?

123

我经常看到有人对Java中的Date和其他与日期时间相关的类提出负面反馈。作为一名.NET开发人员,如果没有使用过它们,我不能完全理解它们的问题所在。

有人可以给我指点一下吗?


5个回答

160
啊,Java的Date类。也许是任何语言中最不应该这样做的最好例子之一。我应该从哪里开始?
阅读JavaDoc可能会让人认为开发人员实际上有一些好主意。它详细介绍了UTCGMT之间的区别,尽管两者之间的差异基本上是闰秒(这种情况发生得pretty rarely)。
然而,设计决策真的浪费了任何想成为良好设计API的想法。以下是一些常见错误:
  • 尽管在千年之交的最后十年设计,但是Java中将年份作为自1900年以来的两个数字进行评估。由于这个平凡的决策,Java世界中有成千上万的解决方法使用1900+(或1900-)。
  • 月份是从零开始计数的,以适应极不寻常的情况,即拥有一个月份数组并且不使用包含null的13元素数组。因此,我们有0..11(今天是109年的第11个月)。有类似数量的++和-- 的操作来将其转换为字符串。
  • 它们mutable。因此,任何时候您想要返回日期(例如,作为实例结构),您需要返回该日期的克隆而不是日期对象本身(否则,人们可能会改变您的结构)。
  • Calendar旨在“修复”这个问题,实际上犯了相同的错误。它们仍然可变。
  • Date表示DateTime,但为了推迟到SQL领域中的那些人,还有另一个子类java.sql.Date,表示单个日期(但没有与之关联的时区)。
  • Date没有与之关联的TimeZone,因此范围(例如“整天”)通常表示为从午夜到午夜(通常在某个任意时区)。
最后值得一提的是,闰秒通常会根据与ntp同步的良好系统时钟自行纠正(请参见上面的链接)。一个系统在引入两个闰秒(每六个月至少一次,每几年实际上)时仍然运行的可能性非常小,特别是考虑到您不时需要重新部署新版本的代码。即使使用动态语言重新生成类或类似于WAR引擎的东西也会污染类空间并最终耗尽permgen。

可变性并不是一个太大的问题 - Enerccio
可变性作为一个问题可能取决于您的语言和编程风格。对于函数式编程来说,这确实是一个问题。 - PaulaG

49

JSR 310,它在Java 8中用java.time替换了旧的日期时间类,其在original JSR中的自我证明如下:

2.5 提议的规范将解决Java社区的哪些需求?
目前,Java SE有两个独立的日期和时间API - java.util.Date和java.util.Calendar。这两个API都被Java开发人员在博客和论坛上一致描述为难以使用。值得注意的是,它们都使用零索引来表示月份,这是许多错误的原因。Calendar也在多年来遭受了许多错误和性能问题,主要是由于在内部以两种不同的方式存储其状态。
一个经典的错误(4639407)阻止了在Calendar对象中创建某些日期。可以编写一系列代码,可以在某些年份但不在其他年份创建日期,从而导致防止某些用户输入正确的出生日期。这是由于Calendar类仅在夏季允许夏令时增加1小时,而历史上在第二次世界大战期间周围的时间是加2小时。虽然这个错误现在已经修复,但如果将来某个国家选择在夏季引入夏令时增加三个小时,那么日历类将再次出现故障。
当前的Java SE API在多线程环境中也存在问题。不可变类被认为是固有的线程安全,因为它们的状态不能改变。然而,Date和Calendar都是可变的,这需要程序员显式考虑克隆和线程。此外,DateTimeFormat中缺乏线程安全性并不广为人知,并且已经成为许多难以跟踪的线程问题的原因。
除了Java SE用于日期时间的类存在问题外,它还没有用于建模其他概念的类。非时区日期或时间、持续时间、期间和间隔在Java SE中没有类表示。因此,开发人员经常使用int来表示时间持续时间,并使用javadoc指定单位。
缺乏全面的日期和时间模型也导致许多常见操作比应该更棘手。例如,计算两个日期之间的天数是一个特别困难的问题。
这个JSR将解决完整的日期和时间模型问题,包括带有和不带有时区的日期和时间、持续时间和时间段、间隔、格式化和解析。

31
  • 日期实例是可变的,这几乎总是不方便的。(参见此处)
  • 它们具有双重性质。它们既表示时间戳,又表示日历日期。事实证明,在处理日期时这是有问题的。
  • 许多情况下,日历数据的数字表示方式很反直觉。例如:getMonth()从0开始计数,getYear()是以1900年为基础(即2009年用109来表示)。
  • Date类缺少你期望从中获得的许多功能。

15

我理解你的痛苦...作为一名曾经的.NET程序员,我也曾经问过同样的问题,在.NET中时间API(时间跨度、操作符重载)非常方便。

首先,要创建一个特定的日期,您可以使用已弃用的API或者:

Calendar c = Calendar.getInstance();
c.set(2000, 31, 12)

为了减去一天,你可以这样做邪恶的事情:

Date firstDate = ...
Calendar c = Calendar.getInstance();
c.setTime(fistDate);
c.add(Calendar.DATE,-1);
Date dayAgo = c.getTime();
或者更糟
Date d = new Date();
Date d2 = new Date(d.getTime() - 1000*60*60*24);

要计算两个日期之间的时间差(以天/周/月为单位)...... 这变得更加困难

然而,来自Apache的DateUtilsorg.apache.commons.lang.time.DateUtils)提供了一些方便的方法,我最近发现自己只使用它们。

正如Brabster所说,Joda Time也是一个不错的外部库,但是Apache似乎比其他任何东西都更“常见”...


请您展示一下如何实现“计算两个日期之间的时间差”这个功能! - Anton Gogolev
我希望我知道一种简单的方法...包括闰年、跳跃月和不稳定的天数... - Eran Medan
1
另请参阅 org.apache.commons.lang.time:http://commons.apache.org/lang//api/org/apache/commons/lang/time/package-summary.html - trashgod
java.time 中,使用 PeriodDuration 类来计算和表示经过的时间,分别以年-月-日和小时-分钟-秒为单位。 - Basil Bourque

3
我认为Java的日期API很实用。老实说,我看到和听到的大部分问题都与冗长有关,需要涉及多个类才能执行任何有用的操作(Calendar,Date,DateFormat/SimpleDateFormat),以及缺乏像getDayOfWeek()这样的简单访问器。 Joda Time是Java中备受尊重的替代API,在“为什么选择Joda Time”一节中提供了更多的论据,说明它是一个可行的替代方案,可能会引起兴趣。

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