3

看看下面这段代码:

Calendar today1 = Calendar.getInstance();
today1.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
System.out.println(today1.getTime());

Calendar today2 = new GregorianCalendar(2010, Calendar.JULY, 14);
today2.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
System.out.println(today2.getTime());

我很困惑......假设我今天在 2010 年 7 月 14 日运行它,输出是:

Fri Jul 16 14:23:23 PDT 2010
Wed Jul 14 00:00:00 PDT 2010

最烦人的是,如果我添加 today2.getTimeInMillis() (或任何其他 get() 方法),它将产生一致的结果。对于下面的代码:

Calendar today2 = new GregorianCalendar(2010, Calendar.JULY, 14);
today2.getTimeInMillis();
today2.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
System.out.println(today2.getTime());

结果是:

Fri Jul 16 00:00:00 PDT 2010
4

3 回答 3

4

答案实际上记录在JavaDocjava.util.Calendar

在此引用:

set(f, value) 将日历字段 f 更改为值。此外,它设置一个内部成员变量来指示日历字段 f 已更改。尽管字段 f 立即更改,但在下一次调用 get()、getTime() 或 getTimeInMillis() 之前不会重新计算日历的毫秒数。

所以这解释了你所看到的行为,但我同意另一个回答你的问题的人,如果你要进行大量的日期编码,你应该考虑JodaTime 。

于 2010-07-14T22:00:34.220 回答
2

您实际上应该使用Calendar#getInstance()来获取实例而不是new GregorianCalendar(). 将该行替换为

Calendar today2 = Calendar.getInstance();
today2.set(2010, Calendar.JULY, 14);

一切都会顺利的。

抱歉,没有对该行为的详细解释,预计这也是当前 Java SE API 中Calendarjava.util.Date主要史诗故障之一。如果您正在进行密集的日期/时间操作,那么我建议您查看JodaTime。即将推出的新 Java 7 将附带基于 JodaTime ( JSR-310 ) 的改进的日期/时间 API。

于 2010-07-14T21:40:18.077 回答
0

(很抱歉编辑,我希望这更具可读性,但当我最初写答案时无法正确......现在是文章长度,但你去......)

只是为了补充已经说过的内容,问题出在返回的Calendar实例的准备方式不同。我个人觉得这是一个设计缺陷,但可能有充分的理由。

当您调用 时,它会使用默认构造函数Calendar.getInstance()创建一个新的。GregorianCalendar此构造函数setCurrentTimeMillis(time)以当前系统时间调用,然后调用受保护的方法complete()

但是,当您GregorianCalendar使用您所做的构造函数创建一个 new 时,complete()它永远不会被调用;相反,除其他外,只set(field, value)需要提供的各种信息。这一切都很好,但它有一些令人困惑的后果。

complete()在第一种情况下调用时,会检查提到的成员变量dustmachine以确定应该重新计算哪些信息。这会产生一个强制计算所有字段(DAYWEEK_OF_MONTH等)的分支。注意Calendar确实是懒惰的;碰巧使用这种实例化方法会在现场强制进行显式重新计算(或在这种情况下为初始计算)。

那么,这有什么影响呢?鉴于在第二个对象创建的情况下没有执行前期场计算,这两个对象具有截然不同的状态。第一个填充了所有字段信息,而第二个仅包含您提供的信息。当您调用各种get*()方法时,这无关紧要,因为任何更改都会在您检索信息时引发延迟重新计算步骤。然而,这种重新计算发生的顺序暴露了两种不同初始状态之间的差异。

在您的特定情况下,这是由于 中的以下相关代码computeTime(),当您请求它时,必须调用它来计算正确的时间getTime()

boolean weekMonthSet = isSet[WEEK_OF_MONTH] || isSet[DAY_OF_WEEK_IN_MONTH];
...
boolean useDate = isSet[DATE];

if (useDate && (lastDateFieldSet == DAY_OF_WEEK
      || lastDateFieldSet == WEEK_OF_MONTH
      || lastDateFieldSet == DAY_OF_WEEK_IN_MONTH)) {
    useDate = !(isSet[DAY_OF_WEEK] && weekMonthSet);
}

在第一种情况下,所有字段都因初始计算而设置。这允许weekMonthSet为真,与DAY_OF_WEEK您在调用中提供的set(field, value)设置一起导致useDate为假。

但是,在第二种情况下,由于没有计算任何字段,因此唯一设置的字段是您在构造函数和后续set(field, value)调用中提供的字段。因此,useDate将保持isSet[DATE]为真,因为根据您的构造函数为真,但weekMonthSet为假,因为对象中的其他字段尚未在任何地方计算,也未由您设置。

useDate为真时,正如暗示的那样,它使用您的日期信息来生成时间值。当useDate为假时,它能够使用您的DAY_OF_WEEK信息来计算您期望的时间,从而产生您所看到的差异。

最后,这提出了为什么在调用getTimeInMillis()之前调用getTime()会修复意外行为的问题。事实证明,由于您在两个对象中的调用,这些字段被重新计算。无论出于何种(可能是真实的)原因,set(field, value)这恰好发生在计算时间之后。因此,强制将时间在第二次计算一次Calendar将基本上对齐两个对象的状态。在那之后,我相信对这两个对象的调用都get*()应该一致。

理想情况下,您在第二种情况下使用的构造函数应该以一致性的名义执行此初始计算步骤(尽管可能出于性能原因,这不是首选),但事实并非如此,这就是您得到的。

所以,简而言之,正如其他人提到的,JodaTime是你的朋友,显然这些课程不那么重要。:)

于 2010-07-14T22:38:04.160 回答