代码之家  ›  专栏  ›  技术社区  ›  L. Holanda

公历的另一个奇怪行为

  •  3
  • L. Holanda  · 技术社区  · 15 年前

    请看下面的代码:

    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
    

    最烦人的是,如果我今天添加2.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
    
    3 回复  |  直到 15 年前
        1
  •  4
  •   dustmachine    15 年前

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

    此处引用:

    set(f,value)将日历字段f更改为value。此外,它还设置了一个内部成员变量,以指示日历字段f已更改。尽管字段F已更改 马上,日历的 在以下时间之前不重新计算毫秒 下一个get()、gettime()或 生成getTimeInMillis()。

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

        2
  •  2
  •   BalusC    15 年前

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

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

    一切都会好起来的。

    对不起,没有详细的行为解释,希望 Calendar 随着 java.util.Date 是当前JavaSeAPI中的主要史诗失败之一。如果你在做密集的日期/时间操作,那么我建议你看一下 JodaTime . 即将到来的新Java 7将基于JordaMe提供一个改进的日期/时间API。 JSR-310 )

        3
  •  0
  •   Tim Stone    15 年前

    (很抱歉编辑,我想让这篇文章更易读一点,但在我最初写答案的时候没法正确理解……现在是论文长度,但你看……)

    为了补充已经说过的话,这个问题是由返回的 Calendar 实例准备不同。我个人认为这是一个设计缺陷,但可能有很好的理由。

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

    但是,当您创建新的 公历日历 使用您所做的构造函数, 完成() 从未被召唤;相反,除了其他事情,只有 set(field, value) 对所提供的各种信息位进行调用。这一切都很好,但它有一些令人困惑的后果。

    什么时候? 完成() 在第一种情况下,会检查引用的成员变量dustmachine,以确定应重新计算哪些信息。这将产生一个分支,强制计算所有字段( DAY , WEEK_OF_MONTH 等)。注意 历法 确实很懒惰;使用这种实例化方法会在现场强制进行显式的重新计算(或者在本例中是初始计算)。

    那么,这有什么影响呢?考虑到在第二个对象创建的情况下没有进行前期场计算,这两个对象的状态有很大的不同。第一个部分填充了所有字段信息,而第二个部分只填充了您提供的信息。当你叫各种各样的 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 你在电话中提供给 设置(字段,值) 设置,原因 useDate 是假的。

    但是,在第二种情况下,由于没有计算任何字段,所以只有您在构造函数和后面提供的字段集。 设置(字段,值) 打电话。因此, 使用过的 将保持真实,因为 isSet[DATE] 对于构造函数来说是正确的,但是 周刊 为假,因为对象中的其他字段未在任何位置计算,也未由您设置。

    什么时候? 使用过的 是真的,正如所暗示的,它使用您的日期信息来生成时间值。什么时候? 使用过的 是假的,它可以使用你的 星期日 用于计算预期时间的信息,从而导致所看到的差异。

    最后,这提出了为什么打电话 getTimeInMillis() 打电话之前 获取时间() 将解决意外行为。事实证明,田地 由于您的 设置(字段,值) 调用两个对象。这只是碰巧发生的 之后 无论出于什么(可能是真实的)原因,时间都是计算出来的。因此,强制在第二次计算一次时间 历法 将基本上对齐两个对象的状态。在那之后,我相信 获取*() 这两个对象的工作是否一致?

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

    因此,简而言之,正如其他人提到的, JodaTime 是你的朋友,很明显这些课程不那么好。:)

    推荐文章