看一下下面的代码:
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
答案 0 :(得分:4)
答案实际上记录在java.util.Calendar
的{{3}}
引用此处:
set(f,value)将日历字段f更改为值。此外,它设置内部成员变量以指示日历字段f已更改。虽然场f改变了 马上,日历的 毫秒不会重新计算直到 下一次调用get(),getTime()或 制作了getTimeInMillis()。
这样就解释了您所看到的行为,但我同意您的问题的另一个回答者,如果您要进行大量的日期编码,则应考虑JavaDoc。
答案 1 :(得分:2)
您实际上应该使用Calendar#getInstance()
来获取实例,而不是new GregorianCalendar()
。用
Calendar today2 = Calendar.getInstance();
today2.set(2010, Calendar.JULY, 14);
它会很顺利。
很抱歉,没有关于该行为的详细说明,期望Calendar
和java.util.Date
是当前Java SE API中的主要史诗失败之一。如果您正在进行密集的日期/时间操作,那么我建议您查看JodaTime。即将发布的新Java 7将附带基于JodaTime(JSR-310)的改进日期/时间API。
答案 2 :(得分:0)
(对不起编辑,我希望它更具可读性,但是当我最初写答案时却无法正确...现在它的篇幅很长,但是你去了...... )
只是添加到已经说过的内容,问题就来自返回的Calendar
个实例,这些实例的编写方式不同。我个人觉得这是一个设计缺陷,但可能有充分的理由。
当您致电Calendar.getInstance()
时,它会使用默认构造函数创建新的GregorianCalendar
。此构造函数使用当前系统时间调用setCurrentTimeMillis(time)
,然后调用受保护的方法complete()
。
但是,当您使用构造函数创建新的GregorianCalendar
时,永远不会调用complete()
;相反,除其他外,仅为所提供的各种信息位调用set(field, value)
。这一切都很好,但它有一些令人困惑的后果。
在第一种情况下调用complete()
时,会检查提到的成员变量dustmachine,以确定应重新计算哪些信息。这会导致分支强制计算所有字段(DAY
,WEEK_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
为false,因为对象中的其他字段尚未在任何地方计算,也未由您设置。 / p>
如果useDate
为真,则暗示它使用您的日期信息来生成时间值。当useDate
为false时,它可以使用您的DAY_OF_WEEK
信息来计算您期望的时间,从而产生您看到的差异。
最后,这提出了一个问题:为什么在调用getTimeInMillis()
之前调用getTime()
将解决意外行为。事实证明,由于您在两个对象中进行了set(field, value)
调用,因此将重新计算字段。这恰好发生在计算时间之后,无论是什么(可能是真的)原因。因此,强制在第二个Calendar
上计算一次的时间将基本上对齐两个对象的状态。在那之后,我相信对get*()
的调用应该对两个对象都一致。
理想情况下,你在第二种情况下使用的构造函数应该以一致性的名义执行这个初始计算步骤(虽然可能出于性能原因这不是首选),但它没有,这就是你得到。
所以,简而言之,正如其他人所提到的那样,JodaTime是你的朋友,显然这些类不那么重要。 :)