2 月 java.util.Calendar 的一个奇怪行为

2022-01-11 00:00:00 calendar java

我遇到了来自 java.util.Calendar 的奇怪行为:

I faced with a strange behavior from java.util.Calendar:

import static org.junit.Assert.*;
import org.junit.Test;

import java.util.Calendar;

public class Tester1 {
    @Test
    public void test_monthOfDate() {
        assertEquals(1, monthOfDate(2013, 1, 30)); // OK
        assertEquals(1, monthOfDate(2013, 1, 31)); // OK

        // Start of February
        assertEquals(2, monthOfDate(2013, 2, 1));  // FAIL
        assertEquals(2, monthOfDate(2013, 2, 28)); // FAIL
        // to the end of it

        // and after that it is okay also
        assertEquals(3, monthOfDate(2013, 3, 1));  // OK
    }

    public int monthOfDate(int year, int month, int day) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, year);
        cal.set(Calendar.MONTH, month - 1);

        // just a simple get! but seems it is very important
        cal.get(Calendar.MONTH);
        //

        cal.set(Calendar.DAY_OF_MONTH, day);

        return cal.get(Calendar.MONTH) + 1;
    }
}

我想知道为什么会这样?

I want to know why exactly this is happening?

推荐答案

问题是您从 2013 年 1 月 30 日开始的日历.

The problem is that you're starting off with a calendar on January 30th 2013.

然后您将年份设置为 2013 年 - 这不是问题.

You're then setting the year to 2013 - that's not a problem.

然后您将月份设置为 1(即二月).你期望在这里发生什么?实际发生的是它会记住它需要将月份设置为 1,但 不会 重新计算实际时间值.根据 文档(重点是我的):

You're then setting the month to 1 (i.e. February). What do you expect to happen here? What actually happens is that it will remember that it needs to set the month to 1, but not recompute the actual time value. The time value will be recomputed on your call to get though, as per the documentation (emphsis mine):

set(f, value) 将日历字段 f 更改为 value.此外,它设置一个内部成员变量来指示日历字段 f 已更改.尽管日历字段 f 立即更改,但在下一次调用 get()、getTime()、getTimeInMillis()、add() 或 roll() 之前,不会重新计算日历的时间值(以毫秒为单位).因此,多次调用 set() 不会触发多次不必要的计算.作为使用 set() 更改日历字段的结果,其他日历字段也可能更改,具体取决于日历字段、日历字段值和日历系统.另外,get(f)在重新计算日历字段后,不一定会返回调用set方法设置的值.具体由具体的日历类决定.

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 calendar field f is changed immediately, the calendar's time value in milliseconds is not recomputed until the next call to get(), getTime(), getTimeInMillis(), add(), or roll() is made. Thus, multiple calls to set() do not trigger multiple, unnecessary computations. As a result of changing a calendar field using set(), other calendar fields may also change, depending on the calendar field, the calendar field value, and the calendar system. In addition, get(f) will not necessarily return value set by the call to the set method after the calendar fields have been recomputed. The specifics are determined by the concrete calendar class.

当您尝试将1 月 30 日"更改为2 月 30 日"并强制进行计算时,实际上会发生的是您在 3 月 2 日结束了我的盒子 - 但您的可能会有所不同实施.

When you try to change "January 30th" to "February 30th" and force a computation, what actually happens is that you end up on March 2nd on my box - but it may differ on your implementation.

最好的修复方法是:

  • 使用 Calendar.set(year, month, date) 来避免这个排序问题
  • 首先使用 Joda Time 作为更合理的 API.
  • Use Calendar.set(year, month, date) instead to avoid this ordering issue
  • Use Joda Time as a more sensible API in the first place.

相关文章