Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different behavior of Calendar class in Java and Android

Tags:

I was working on retrieving date of first week of the year and I found very strange behavior.

I tested the following code snippet both in Java console app and Android emulator and it produces different output.

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

Following output was produced

Android log cat : 2012/09/17 (incorrect)

Java console : 2012/01/01 (correct)

And the strange thing is if I used following code both in Android and Java it produces same correct output. The only difference was I swapped the 2nd and 3rd line from the above code.

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

Android log cat : 2012/01/01 (correct)

Java console : 2012/01/01 (correct)

I am very curious to know regarding this.

Thanks in advance.

like image 321
Vishal Vyas Avatar asked Sep 18 '12 16:09

Vishal Vyas


1 Answers

It seems that Calendar class internally has two data containers.

protected long time

protected int[] fields

So when you call cal.set(Calendar.WEEK_OF_YEAR, 1), you change the values in fields, not time of the class.

In Java API

protected abstract void computeFields()

Converts the current millisecond time value time to calendar field values in fields[]. This allows you to sync up the calendar field values with a new time that is set for the calendar. The time is not recomputed first; to recompute the time, then the fields, call the complete() method.

I think in the first case of Android computeFields() is not called internally.

To check my theory, I tested the following code:

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

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,MILLISECOND==802,ZONE_OFFSET==0,DST_OFFSET==0]

2012.09.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,MILLISECOND==802,ZONE_OFFSET==0,DST_OFFSET==0]

2012.01.03

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,MILLISECOND==802,ZONE_OFFSET==0,DST_OFFSET==0]

2012.09.16

As we can see above, values in the fields are changed, but internal time is denoted as ?, telling that time is not synced with fields.

You got unsynced time using getTime() method and printed it out.

I think Calendar in Android is designed to delay the sync until it is really needed.

ADDED

I found the following in Java API:

Calendar fields can be changed using three methods: set(), add(), and roll().

set(f, value) changes field f to value. In addition, it sets an internal member variable to indicate that 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. Thus, multiple calls to set() do not trigger multiple, unnecessary computations. As a result of changing a field using set(), other fields may also change, depending on the field, the field value, and the calendar system. In addition, get(f) will not necessarily return value after the fields have been recomputed. The specifics are determined by the concrete calendar class.

ADDED

To check whether "The specifics are determined by the concrete calendar class" is true, I checked actual codes of Dalvik and JDK 6.

Set method in Dalvik's Calendar

from 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;
    }
}

Set method in JDK 6's Calendar

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

Specific implementations are pretty different. To figure out the exact reason of your problem, you should look at both implementations in detail.

like image 166
Heejin Avatar answered Sep 21 '22 05:09

Heejin