21

その年の最初の週の日付を取得する作業をしていましたが、非常に奇妙な動作を見つけました。

次のコードスニペットを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()));

次の出力が生成されました

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

Androidログ猫:2012/01/01(正解)

Javaコンソール:2012/01/01(正しい)

私はこれについて知りたいと思っています。

前もって感謝します。

4

1 に答える 1

14

Calendar クラスは内部的に 2 つのデータ コンテナを持っているようです。

protected long time

protected int[] fields

したがって、 を呼び出すときは、クラスではなくのcal.set(Calendar.WEEK_OF_YEAR, 1)値を変更します。fieldstime

Java API で

保護された抽象ボイドcomputeFields()

現在のミリ秒の時間値 time を fields[] のカレンダー フィールド値に変換します。これにより、カレンダー フィールドの値を、カレンダーに設定された新しい時間と同期させることができます。時間は最初に再計算されません。時間を再計算してからフィールドを再計算するには、complete() メソッドを呼び出します。

computeFields()Androidの最初のケースでは内部的に呼び出されていないと思います。

私の理論を確認するために、次のコードをテストしました。

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

上記のように、フィールドの値は変更されますが、内部時間は と表示され?、 とtime同期されていないことがわかりますfields

timeメソッドを使用して非同期にgetTime()なり、印刷しました。

Android のカレンダーは、本当に必要になるまで同期を遅らせるように設計されていると思います。

追加した

Java API で次のことがわかりました。

カレンダー フィールドは、set()、add()、roll() の 3 つのメソッドを使用して変更できます。

set(f, value) は、フィールド f を value に変更します。さらに、フィールド 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;
    }
}

JDK 6 のカレンダーの set メソッド

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

特定の実装はかなり異なります。問題の正確な理由を理解するには、両方の実装を詳細に調べる必要があります。

于 2012-09-18T23:29:44.030 に答える