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と数えるようにするためには、

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

とします。

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

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

```java
    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と出力されることがわかりました。


```java
    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()` を呼んでます。

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

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

```java
    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()`をみてみると

```java
   1174     public void setFirstDayOfWeek(int value) {
   1175         firstDayOfWeek = value;
   1176     }
...
   1189     public void setMinimalDaysInFirstWeek(int value) {
   1190         minimalDaysInFirstWeek = value;
   1191     }
```
値をセットしているだけで、特にフラグなどいじってませんので、このまま`get()` しても再計算された値が取得できないように見えます。

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

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

```

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

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


#その他の環境では

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

```java
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


#多分、、、
`setFirstDayOfWeek()` をした後も、`computeFields()`しなきゃいけないのかなと。
例えばメソッドの中で

```java
 areFieldsSet　= false
```
するとか。