Java和Android中Calendar类的不同行为

Vis*_*yas 20 java android calendar

我正在努力检索一年中第一周的日期,我发现了非常奇怪的行为.

我在Java控制台应用程序和Android模拟器中测试了以下代码片段,它产生了不同的输出.

    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.WEEK_OF_YEAR, 1);
    cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
    System.out.println(sdf.format(cal.getTime()));
Run Code Online (Sandbox Code Playgroud)

产出以下产出

Android日志猫:2012/09/17(不正确)

Java控制台:2012/01/01(正确)

奇怪的是,如果我在Android和Java中使用以下代码,它会产生相同的正确输出.唯一的区别是我从上面的代码中交换了第2行和第3行.

    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
    cal.set(Calendar.WEEK_OF_YEAR, 1);
    System.out.println(sdf.format(cal.getTime()));
Run Code Online (Sandbox Code Playgroud)

Android日志猫:2012/01/01(正确)

Java控制台:2012/01/01(正确)

我非常想知道这件事.

提前致谢.

Hee*_*jin 13

似乎Calendar类内部有两个数据容器.

protected long time

protected int[] fields

所以当你打电话时cal.set(Calendar.WEEK_OF_YEAR, 1),你改变的是fields,而不是time类.

在Java API中

protected abstract void computeFields()

将当前毫秒时间值时间转换为字段[]中的日历字段值.这允许您将日历字段值与为日历设置的新时间同步.时间不会先重新计算; 重新计算时间,然后是字段,调用complete()方法.

我认为在Android的第一个案例中computeFields()并没有内部调用.

为了检查我的理论,我测试了以下代码:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd");
Calendar cal = Calendar.getInstance();
System.out.println(cal);
System.out.println(sdf.format(cal.getTime()));
cal.set(Calendar.WEEK_OF_YEAR, 1);
System.out.println(cal);
System.out.println(sdf.format(cal.getTime()));
cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
System.out.println(cal);
System.out.println(sdf.format(cal.getTime()));
Run Code Online (Sandbox Code Playgroud)

logcat的:

java.util.GregorianCalendar [ time = 1348010308802,areFieldsSet = true,lenient = true,zone = org.apache.harmony.luni.internal.util.ZoneInfo ["null",mRawOffset = 0,mUseDst = false],firstDayOfWeek = 1 ,minimalDaysInFirstWeek = 4,ERA == 1,YEAR == 2012,MONTH == 8,WEEK_OF_YEAR == 38,WEEK_OF_MONTH == 4,DAY_OF_MONTH == 18,DAY_OF_YEAR == 262,DAY_OF_WEEK == 3,DAY_OF_WEEK_IN_MONTH == 3, AM_PM == 1,HOUR == 11,HOUR_OF_DAY = 23,MINUTE == 18,SECOND == 28,微差== 802,ZONE_OFFSET == 0,DST_OFFSET == 0]

2012年9月18日

java.util.GregorianCalendar [ time =?,areFieldsSet = false,lenient = true,zone = org.apache.harmony.luni.internal.util.ZoneInfo ["null",mRawOffset = 0,mUseDst = false],firstDayOfWeek = 1 ,minimalDaysInFirstWeek = 4,ERA == 1,YEAR == 2012,MONTH == 8,WEEK_OF_YEAR == 1,WEEK_OF_MONTH == 4,DAY_OF_MONTH == 18,DAY_OF_YEAR == 262,DAY_OF_WEEK == 3,DAY_OF_WEEK_IN_MONTH == 3, AM_PM == 1,HOUR == 11,HOUR_OF_DAY = 23,MINUTE == 18,SECOND == 28,微差== 802,ZONE_OFFSET == 0,DST_OFFSET == 0]

2012年1月3日

java.util.GregorianCalendar [ time =?,areFieldsSet = false,lenient = true,zone = org.apache.harmony.luni.internal.util.ZoneInfo ["null",mRawOffset = 0,mUseDst = false],firstDayOfWeek = 1 ,minimalDaysInFirstWeek = 4,ERA == 1,YEAR == 2012,MONTH == 8,WEEK_OF_YEAR == 1,WEEK_OF_MONTH == 4,DAY_OF_MONTH == 18,DAY_OF_YEAR == 262,DAY_OF_WEEK == 1,DAY_OF_WEEK_IN_MONTH == 3, AM_PM == 1,HOUR == 11,HOUR_OF_DAY = 23,MINUTE == 18,SECOND == 28,微差== 802,ZONE_OFFSET == 0,DST_OFFSET == 0]

2012年9月16日

如上所示,字段中的值已更改,但内部时间表示为?,表示time未同步fields.

你没有time使用getTime()方法并打印出来.

我认为Android中的日历旨在延迟同步,直到真正需要它为止.

添加

我在Java API中发现了以下内容:

可以使用三种方法更改日历字段:set(),add()和roll().

set(f,value)将字段f更改为值.此外,它设置内部成员变量以指示字段f已更改.虽然字段f立即更改,但在下一次调用get(),getTime()或getTimeInMillis()之前,不会重新计算日历的毫秒数.因此,对set()的多次调用不会触发多次不必要的计算.使用set()更改字段的结果是,其他字段也可能会更改,具体取决于字段,字段值和日历系统.此外,在重新计算字段后,get(f)不一定会返回值.具体情况由具体的日历类决定.

添加

要检查"详细信息是否由具体的日历类确定"是真的,我检查了Dalvik和JDK 6的实际代码.

在Dalvik的日历中设置方法

来自https://www.codeaurora.org/git/projects/qrd-gb-dsds-7225/repository/revisions/cc99b832a941dc8cbb86f1607d04eb87935ddbfd/entry/android/dalvik/libcore/luni/src/main/java/java/util/Calendar的.java

public void set(int field, int value) {
    fields[field] = value;
    isSet[field] = true;
    areFieldsSet = isTimeSet = false;
    if (field > MONTH && field < AM_PM) {
        lastDateFieldSet = field;
    }
    if (field == HOUR || field == HOUR_OF_DAY) {
        lastTimeFieldSet = field;
    }
    if (field == AM_PM) {
        lastTimeFieldSet = HOUR;
    }
}
Run Code Online (Sandbox Code Playgroud)

在JDK 6的日历中设置方法

public void set(int field, int value) {
    if (isLenient() && areFieldsSet && !areAllFieldsSet) {
        computeFields();
    }
    internalSet(field, value);
    isTimeSet = false;
    areFieldsSet = false;
    isSet[field] = true;
    stamp[field] = nextStamp++;
    if (nextStamp == Integer.MAX_VALUE) {
        adjustStamp();
    }
}
Run Code Online (Sandbox Code Playgroud)

具体实现是非常不同的.要找出问题的确切原因,您应该详细了解这两种实现.

  • @VishalVyas我检查了Dalvik Calendar和JDK 6的Calendar的实际代码,发现它们有不同的实现.我在答案中添加了更多相关内容. (2认同)