Java-календарь возвращает неправильную неделю

У меня есть следующий тест, который из моего понимания должен пройти. Есть ли что-то, что мне не хватает или это ошибка в календаре?

Calendar inputC = new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.US);  

// Sunday  
inputC.set(2014, Calendar.JUNE, 22, 0, 0, 0);  
inputC.set(Calendar.MILLISECOND, 0);  

// START code impl
// Given a date returns back the date with it day being first day of week and resets time.  
Calendar dc = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.US);  
dc.set(inputC.get(Calendar.YEAR), inputC.get(Calendar.MONTH), inputC.get(Calendar.DAY_OF_MONTH), 0, 0 , 0);  
dc.set(Calendar.MILLISECOND, 0);  
// dc.getTimeInMillis();  
// dc.set(Calendar.WEEK_OF_YEAR, inputC.get(Calendar.WEEK_OF_YEAR));  
dc.set(Calendar.DAY_OF_WEEK, dc.getFirstDayOfWeek());  

Date output = dc.getTime();  
// END code impl

Calendar expectedSundayC = new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.US);  
expectedSundayC.set(2014, Calendar.JUNE, 22, 0, 0, 0);  
expectedSundayC.set(Calendar.MILLISECOND, 0);  

assertEquals(output, expectedSundayC.getTime());  

Вывод:     2014-06- 15 Т00: 00: 00Z
Ожидаемое: 2014-06- 22 T00: 00: 00Z

Вышеуказанный тест завершится неудачно, если я не раскомментирую ни одну из двух следующих строк:

// dc.getTimeInMillis();  
// dc.set(Calendar.WEEK_OF_YEAR, inputC.get(Calendar.WEEK_OF_YEAR));

Почему этот dc.getTimeInMillis() влияет на выход?
Недопустимая строка 2 выше кажется излишней, потому что я уже установил поля года, месяца, дня, часа, минуты, секунды, миллисекунд, которые должны быть достаточными для полного времени.

Ответ 1

Ты, конечно же, дал мне начальника, но я понял это.

Технические характеристики

Из JavaDoc:

Значения полей календаря можно задать, вызвав методы набора. Любые значения полей, заданные в Календаре, не будут интерпретироваться, пока не потребуется вычислять его значение времени (миллисекунды из эпохи) или значения полей календаря. Вызов get, getTimeInMillis, getTime, add и roll включает такие вычисления.

Это означает, что всякий раз, когда вы меняете свои значения с помощью set, вы должны сказать ему, чтобы обновить себя, используя один из методов get для распространения изменений. Однако ваш код не был, за исключением случаев, когда вы раскомментировали код. Так что что-то в вашем коде явно не играло хорошо с вашими наборами в computeTime.

Где делали плохое

Итак... мы можем посмотреть на эту устаревшую, но тем не менее чудовищно трудную для навигации копию GregorianCalendar источник и проанализируйте, что происходит, когда вы делаете getTime() после установки дня недели со всеми другими каналами.

Ух, копирование из этого - боль. Я пройду через это.

Сначала мы знаем, что getTimeInMillis() вызывает computeTime() в строке 505.

Вы объявили экземпляр Календаря. Он имеет предопределенные значения. Сегодня, если быть точным. А также мы на третьей неделе сентября. Важно позже!

Теперь мы назначим ваш день в строке 511. Здесь ваш день установлен на 22.

int day = fields[DAY_OF_MONTH];

Линия 523 - это то, где дела идут бонкерами и нарушают вашу дату! Мы оцениваем это как false и переходим к else в строке 553.

if (! isSet[MONTH] && (! isSet[DAY_OF_WEEK] || isSet[WEEK_OF_YEAR]))
//evaluates to true

Мы вводим оператор if в 555, потому что DAY_OF_WEEK установлен. И у нас есть DAY_OF_WEEK_IN_MONTH, но мы не поставили его сами. Календарь сделал, когда он был создан. Помните, что 3-я неделя сентября? Да, он здесь, где он нас укусил. Он рассчитывает воскресенье 3-й недели в июне и перезаписывает наш день = 22 к 15-му, что является третьим воскресеньем в июне.

    if (isSet[DAY_OF_WEEK]) { //yup we set this
        int first = getFirstDayOfMonth(year, month);

        // 3: YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
        if (isSet[DAY_OF_WEEK_IN_MONTH]) { //we didn't set this, but it been set!
            if (fields[DAY_OF_WEEK_IN_MONTH] < 0) { 
                month++;
                first = getFirstDayOfMonth(year, month);
                day = 1 + 7 * (fields[DAY_OF_WEEK_IN_MONTH]);
            } else
                day = 1 + 7 * (fields[DAY_OF_WEEK_IN_MONTH] - 1); 
                 // 15 = 1 + 7 * (3 - 1)

            int offs = fields[DAY_OF_WEEK] - first;
            if (offs < 0)
                offs += 7;
            day += offs;
        }
    }

Как решить проблему

Проверьте исходный javadoc на метод set, который вы вызывали с помощью года, месяца и дня.

Устанавливает значения для полей календаря YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY и MINUTE. Предыдущие значения других полей сохраняются. Если это не требуется, сначала вызовите clear().

Конечно, добавив dc.clear();, прежде чем ваш набор вернет желаемый результат. Я не прошел через него, но, учитывая странное поведение, которое в настоящее время испытано, лучшим вариантом будет getTime() после каждого изменения, чтобы ваше время не стало неожиданным результатом.

Итак, в основном, проблема заключалась в том, что у вас были пол-грязные данные с полу-новыми данными, которые противоречили себе и перезаписывали вещи. Если бы этот код был запущен на следующей неделе, вы бы даже не поймали ошибку. Как это для неудобного зависящего от времени состояния?