【问题标题】:another strange behaviour with GregorianCalendarGregorianCalendar 的另一个奇怪行为
【发布时间】:2010-07-14 21:35:50
【问题描述】:

看看下面这段代码:

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

【问题讨论】:

    标签: java calendar dayofweek


    【解决方案1】:

    答案实际上记录在JavaDoc for java.util.Calendar

    在此引用:

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

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

    【讨论】:

    • Calendar 确实是延迟加载的,但这并没有实际上解释问题。为什么它不适用于new GregorianCalendar(y, m, d),但它适用于set(y, m, d)
    • 是的,它确实解释了。这是因为使用 GregorianCalendar(y, m, d) 您还没有计算 WEEK_OF_YEAR。在使用 set(y, m, d) 时,您首先调用了 Calendar.getInstance() ,它为我们提供了一个所有字段都已填满的对象。
    【解决方案2】:

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

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

    一切都会顺利的。

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

    【讨论】:

      【解决方案3】:

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

      只是为了补充已经说过的内容,问题源于返回的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 为真,这与您在对set(field, value) 的调用中提供的DAY_OF_WEEK 一起设置,导致useDate 为假。

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 2011-02-26
        • 2022-07-26
        • 2017-07-29
        • 2011-10-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-12
        • 2013-01-14
        • 2018-08-07
        相关资源
        最近更新 更多