CalendarクラスのWEEK_OF_YEAR

CalendarクラスのWEEK_OF_YEAR

CalendarクラスのWEEK_OF_YEARは、一年の中で何週目かを返すfieldなのですが、week1:第一週目をいつから数えるかは、getFirstDayOfWeek() (週が何曜日からはじまるか)とgetMinimalDaysInFirstWeek()(最初の週を何日以上とするか)によって計算されます。

https://docs.oracle.com/javase/jp/6/api/java/util/Calendar.html#WEEK_OF_YEAR

WEEK_OF_YEAR public static final int WEEK_OF_YEAR get および set のためのフィールド値で、現在の年の何週目かを示します。getFirstDayOfWeek() および getMinimalDaysInFirstWeek() で定義される年の最初の週には、値 1 が使用されます。サブクラスでは、年の最初の週より前の日に対して WEEK_OF_YEAR の値が定義されます。

今年2016年は、1/1は金曜日なので、例えば1/4の週をweek1と数えるようにするためには、

setFirstDayOfWeek(Calendar.MONDAY);
setMinimalDaysInFirstWeek(4);

とします。

Android だと、、、

しかし、Android apk でコレを実装してみると、うまく動かない。。。

例えばこの記事を執筆している今日は2/10で、以下のようなコードだとweek6となってほしいのですが、week7と出力されてしまう。

private void getWeekNumber() {
Calendar calendar = Calendar.getInstance();

Log.d("###", "Original week:" + calendar.get(Calendar.WEEK_OF_YEAR));
calendar.setFirstDayOfWeek(Calendar.MONDAY);
calendar.setMinimalDaysInFirstWeek(4);

Log.d("###", "Modified week:" + calendar.get(Calendar.WEEK_OF_YEAR));
}

実行結果

Original week:7

Modified week:7

少しハマって試行錯誤した結果、何かしらのfieldにset()すれば、期待どおりにweek6と出力されることがわかりました。

private void getWeekNumber() {
Calendar calendar = Calendar.getInstance();
Log.d("###", "Time:" + calendar.getTime());
Log.d("###", "Original week:" + calendar.get(Calendar.WEEK_OF_YEAR));

calendar.setFirstDayOfWeek(Calendar.MONDAY);
calendar.setMinimalDaysInFirstWeek(4);

Log.d("###", "Time:" + calendar.getTime());
Log.d("###", "Modified week:" + calendar.get(Calendar.WEEK_OF_YEAR));

calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR)); // これを実行した以降は正しい結果に

Log.d("###", "Time:" + calendar.getTime());
Log.d("###", "Modified week:" + calendar.get(Calendar.WEEK_OF_YEAR));

}

実行結果

Time:Wed Feb 10 22:36:22 JST 2016

Original week:7

Time:Wed Feb 10 22:36:22 JST 2016

Modified week:7

Time:Wed Feb 10 22:36:22 JST 2016

Modified week:6 // 期待通り

不思議な結果になりました。getTime()の値は全部同じなのに、、、 Androidのバグ?? と思ってCalendarクラスの実装を見てみる。

http://tools.oesf.biz/android-6.0.0_r1.0/xref/libcore/luni/src/main/java/java/util/Calendar.java

まずはget(). fieldの値を返す前にcomplete() を呼んでます。

898 public int get(int field) {
899 complete();
900 return fields[field];
901 }

complete() の中では、computeTime() というやつを呼んで、最新の設定をもとにCalendar情報を計算しているのでしょうかね、名前からして。それを実行するためには、areFieldsSetfalseである必要がありますと。

838 protected void complete() {
839 if (!isTimeSet) {
840 computeTime();
841 isTimeSet = true;
842 }
843 if (!areFieldsSet) {
844 computeFields();
845 areFieldsSet = true;
846 }
847 }

そんでsetFirstDayOfWeek()setMinimalDaysInFirstWeek()をみてみると

1174 public void setFirstDayOfWeek(int value) {
1175 firstDayOfWeek = value;
1176 }
...
1189 public void setMinimalDaysInFirstWeek(int value) {
1190 minimalDaysInFirstWeek = value;
1191 }

値をセットしているだけで、特にフラグなどいじってませんので、このままget() しても再計算された値が取得できないように見えます。

一方 set() では、areFieldsSetfalse をセットしてます。 つまり、あとでcomputeする必要がありまっせということでしょうかね。

1123 public void set(int field, int value) {
1124 fields[field] = value;
1125 isSet[field] = true;
1126 areFieldsSet = isTimeSet = false; // ここ
...

set()を呼ぶと問題を回避できるのはこのためでしょうかね。

ということで、 Androidのバグ??

その他の環境では

ちなみにmacJava SE 1.8.0_45-b14では、こういった動作にはならなかった。

import java.util.Calendar;

public class MyCal {
public static void main(String[] args){
Calendar calendar = Calendar.getInstance();

System.out.println("Original:" + calendar.get(Calendar.WEEK_OF_YEAR));

calendar.setFirstDayOfWeek(Calendar.MONDAY);
calendar.setMinimalDaysInFirstWeek(4);

System.out.println("Modified:" + calendar.get(Calendar.WEEK_OF_YEAR));

}
}

実行結果

Original:7

Modified:6

スッキリわかるJava入門 第2版 (スッキリシリーズ)

スッキリわかるJava入門 第2版 (スッキリシリーズ)

スッキリわかる Java入門 実践編 第2版 (スッキリシリーズ)

スッキリわかる Java入門 実践編 第2版 (スッキリシリーズ)

EFFECTIVE JAVA 第2版 (The Java Series)

EFFECTIVE JAVA 第2版 (The Java Series)