GregorianCalendar 的另一个奇怪行为

发布于 2024-09-10 02:31:20 字数 819 浏览 7 评论 0原文

看看下面的这段代码:

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

Take a look at the piece of code bellow:

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

I'm quite confused... Assuming I am running it today as July 14th, 2010, the output is:

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

The most annoying thing is that if I add today2.getTimeInMillis() (or any other get() method) it will produce consistent result. For the code bellow:

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

The result is:

Fri Jul 16 00:00:00 PDT 2010

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

挽手叙旧 2024-09-17 02:31:20

答案实际上记录在 JavaDoc 中对于 java.util.Calendar

此处引用:

set(f, value) 将日历字段 f 更改为 value。此外,它还设置一个内部成员变量来指示日历字段 f 已更改。虽然f字段改变了
立即,日历的
毫秒不会重新计算,直到
下一次调用 get()、getTime() 或
getTimeInMillis() 已完成。

因此,这解释了您所看到的行为,但我同意您问题的另一位答复者的观点,即您应该考虑 JodaTime 如果您要进行大量日期编码。

The answer is actually documented in the JavaDoc for java.util.Calendar

Quoted here:

set(f, value) changes calendar field f to value. In addition, it sets an internal member variable to indicate that calendar field f has been changed. Although field f is changed
immediately, the calendar's
milliseconds is not recomputed until
the next call to get(), getTime(), or
getTimeInMillis() is made.

So that explains the behavior you are seeing, but I concur with another responder to your question that you should consider JodaTime if you're going to do a lot of Date coding.

素衣风尘叹 2024-09-17 02:31:20

事实上,您应该使用 Calendar#getInstance() 来获取实例,而不是 new GregorianCalendar()。替换该行

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

,一切都会顺利。

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

You should in fact be using Calendar#getInstance() to get an instance and not new GregorianCalendar(). Replace that line by

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

and it will go well.

Sorry, no detailed explanation for the behaviour, expect that Calendar along with java.util.Date are one of the major epic failures in the current Java SE API. If you're doing intensive date/time operations, then I'd recommend to have a look at JodaTime. The upcoming new Java 7 will ship with an improved date/time API based on JodaTime (JSR-310).

吐个泡泡 2024-09-17 02:31:20

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

只是补充一下已经说过的内容,问题是由于返回的 Calendar 实例的准备方式不同而引起的。我个人认为这是一个设计缺陷,但可能有充分的理由。

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

但是,当您使用您所做的构造函数创建新的 GregorianCalendar 时,永远不会调用 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 为 true,与您在调用 set(field, value) 时提供的 DAY_OF_WEEK 一起设置,导致 useDate 为 false。

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

useDate 为 true 时,正如暗示的那样,它会使用您的日期信息来生成时间值。当 useDate 为 false 时,它​​可以使用您的 DAY_OF_WEEK 信息来计算您预期的时间,从而得出您看到的差异。

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

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

因此,简而言之,正如其他人提到的, JodaTime 是您的朋友,显然这些课程较少所以。 :)

(Sorry for the edit, I wanted this to be a little more readable, but couldn't get it right when I originally wrote the answer...now it's essay length, but there you go...)

Just to add to what's already been said, the issue arises from the returned Calendar instances being prepared differently. I personally feel like this is a design flaw, but there may be good reason for it.

When you call Calendar.getInstance(), it creates a new GregorianCalendar using the default constructor. This constructor calls setCurrentTimeMillis(time) with the current system time, and then calls the protected method complete().

However, when you create a new GregorianCalendar using the constructor that you did, complete() is never called; instead, among other things, only set(field, value) is called for the various bits of information that is provided. This is all well and good, but it has some confusing consequences.

When complete() is called in the first case, the member variables dustmachine alluded to are checked to determine what information should be recalculated. This results in a branch that forces calculation all of the fields (DAY, WEEK_OF_MONTH, etc.). Note that Calendar is indeed lazy; it just happens that using this method of instantiation forces an explicit recalculation (or in this case initial calculation) on the spot.

So, what impact does this have? Given that no upfront field computation was performed in the case of the second object creation, the two objects have vastly different states. The first has all of its field information populated, while the second only has the information which you provided. When you call the various get*() methods, it shouldn't matter, because any changes should provoke the lazy recalculation step when you retrieve the information. However, the order in which this recalculation occurs exposes the differences between the two varying initial states.

In your particular case, this is due to the following relevant code in computeTime(), which is necessarily invoked to compute the correct time when you request it with 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);
}

In the first case, all fields are set due to that initial calculation. This allows weekMonthSet to be true, which, along with the DAY_OF_WEEK that you provided in your call to set(field, value) being set, causes useDate to be false.

However, in the second case, as no fields have been calculated, the only fields set are the ones you provided in the constructor and in the subsequent set(field, value) call. Thus, useDate will remain true, because isSet[DATE] is true per your constructor, but weekMonthSet is false as the other fields in the object have not been computed anywhere, nor set by you.

When useDate is true, as implied, it uses your date information to generate the value for the time. When useDate is false, it's able to use your DAY_OF_WEEK information to compute the time you expect, resulting in the difference you see.

Finally, this raises the question of why calling getTimeInMillis() before calling getTime() will fix the unexpected behaviour. As it turns out, the fields will be recalculated as a result of your set(field, value) call in both objects. This just happens to occur after the time is calculated, for whatever (probably genuine) reason. Therefore, forcing the time to be calculated once on the second Calendar will essentially align the states of the two objects. After that, I believe the calls to get*() should all work consistently for both objects.

Ideally, the constructor you used in the second case should perform this initial calculation step in the name of consistency (although maybe for reasons of performance this wouldn't be preferred), but it doesn't, and this is what you get.

So, in short, as the others mentioned, JodaTime is your friend, and clearly these classes are less so. :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文