Значения параметров Java Calendar не дают ожидаемых дат-времени

У меня есть метка времени, минуты, даты и миллисекунды, и я пытаюсь создать объект Date, представляющий время. Временная метка указана в восточном дневном свете.

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

    Date today = new Date();
    int hour = 4, min  = 0, sec  = 0, ms   = 64;
    boolean print = true;

    Calendar cal = GregorianCalendar.getInstance();
    if(print)
        System.out.println("After initializing, time is: "+cal.getTime());
    cal.clear();
    if(print)
        System.out.println("After clearing, time is: "+cal.getTime());
    cal.setTime(today);
    if(print)
        System.out.println("After setting date, time is: "+cal.getTime());
    cal.set(Calendar.HOUR_OF_DAY,hour);
    if(print)
        System.out.println("After setting hour, time is: "+cal.getTime());
    cal.set(Calendar.MINUTE,min);
    if(print)
        System.out.println("After setting minute, time is: "+cal.getTime());
    cal.set(Calendar.SECOND,sec);
    if(print)
        System.out.println("After setting second, time is: "+cal.getTime());
    cal.set(Calendar.MILLISECOND,ms);
    if(print)
        System.out.println("After setting milliseconds, time is: "+cal.getTime());
    cal.setTimeZone(TimeZone.getTimeZone("EDT"));

    System.out.println("After setting time zone, time is: "+cal.getTime());

Это приводит к выходу:

After initializing, time is: Tue Jan 07 16:01:59 EST 2014
After clearing, time is: Thu Jan 01 00:00:00 EST 1970
After setting date, time is: Tue Jan 07 16:01:59 EST 2014
After setting hour, time is: Tue Jan 07 04:01:59 EST 2014
After setting minute, time is: Tue Jan 07 04:00:59 EST 2014
After setting second, time is: Tue Jan 07 04:00:00 EST 2014
After setting milliseconds, time is: Tue Jan 07 04:00:00 EST 2014
After setting time zone, time is: Tue Jan 07 04:00:00 EST 2014

Однако, если я немного изменил код:

boolean print = false;

Я получаю следующий (другой) результат (!)

After setting time zone, time is: Mon Jan 06 23:00:00 EST 2014

Кто-нибудь знает, почему это происходит?

Ответ 1

Сначала необходимо установить часовой пояс. См. Определение GregorianCalendar.setTimeZone ниже:

public void setTimeZone(TimeZone value)
{
    zone = value;
    sharedZone = false;
    /* Recompute the fields from the time using the new zone.  This also
     * works if isTimeSet is false (after a call to set()).  In that case
     * the time will be computed from the fields using the new zone, then
     * the fields will get recomputed from that.  Consider the sequence of
     * calls: cal.setTimeZone(EST); cal.set(HOUR, 1); cal.setTimeZone(PST).
     * Is cal set to 1 o'clock EST or 1 o'clock PST?  Answer: PST.  More
     * generally, a call to setTimeZone() affects calls to set() BEFORE AND
     * AFTER it up to the next call to complete().
     */
    areAllFieldsSet = areFieldsSet = false;
}

Ответ 2

Как упоминалось gtgaxiola: Из Календарь Документация

В разделе Поле Манипуляции:

set (f, value) изменяет поле календаря f на значение. Кроме того, он устанавливает внутреннюю переменную-член, чтобы указать, что поле календаря f было изменено. Хотя поле календаря f изменяется немедленно, значение времени календаря в миллисекундах не пересчитывается до тех пор, пока не будет сделан следующий вызов get(), getTime(), getTimeInMillis(), add() или roll().

Проблема заключается в том, что ваш вызов getTime() пересчитывает дату, но setTimeZone (..) не устанавливает внутреннюю переменную участника isTimeSet в false. Таким образом, последняя строка в вашем первом выходе неверна для вас, потому что вы ожидаете, что часовой пояс будет рассмотрен, а это не так.

Ответ 3

Из Документация календаря

В разделе Поле Манипуляции:

set (f, value) изменяет поле календаря f на значение. Кроме того, он устанавливает внутреннюю переменную-член, чтобы указать, что поле календаря f было изменено. Хотя поле календаря f изменяется немедленно, значение времени календаря в миллисекундах не пересчитывается до тех пор, пока не будет сделан следующий вызов get(), getTime(), getTimeInMillis(), add() или roll().

Таким образом, множественные вызовы set() не запускают несколько ненужных вычислений. В результате изменения поля календаря с помощью set() другие поля календаря могут также изменяться в зависимости от поля календаря, значения поля календаря и системы календаря. Кроме того, get (f) не обязательно будет возвращать значение, заданное вызовом методу set после пересчета полей календаря. Специфика определяется конкретным классом календаря.

Ответ 4

Я бы просто сначала установил часовой пояс:

 Calendar cal = GregorianCalendar.getInstance();

    cal.clear();
    cal.setTimeZone(TimeZone.getTimeZone("EDT"));
    cal.setTime(today);
    cal.set(Calendar.HOUR_OF_DAY,hour);
    cal.set(Calendar.MINUTE,min);
    cal.set(Calendar.SECOND,sec);
    cal.set(Calendar.MILLISECOND,ms);

Однако он делал то, что должен, как сказано в комментариях 4 утра 11 вечера в EST.

И даже лучшее решение было бы не использовать Calendar вообще, кроме, например, joda-time.

EDIT: Это дает мне подходящее время.

    Date today = new Date();
    int hour = 4, min  = 0, sec  = 0, ms   = 64;
    boolean print = false;

    Calendar cal = GregorianCalendar.getInstance();
    if(print)
        System.out.println("After initializing, time is: "+cal.getTime());
    cal.clear();
    if(print)
        System.out.println("After clearing, time is: "+cal.getTime());
    cal.setTimeZone(TimeZone.getTimeZone("EDT"));
    if(print)
        System.out.println("After setting time zone, time is: "+cal.getTime());
    cal.setTime(today);
    if(print)
        System.out.println("After setting date, time is: "+cal.getTime());
    cal.set(Calendar.HOUR_OF_DAY,hour);
    if(print)
        System.out.println("After setting hour, time is: "+cal.getTime());
    cal.set(Calendar.MINUTE,min);
    if(print)
        System.out.println("After setting minute, time is: "+cal.getTime());
    cal.set(Calendar.SECOND,sec);
    if(print)
        System.out.println("After setting second, time is: "+cal.getTime());
    cal.set(Calendar.MILLISECOND,ms);
    if(print)
        System.out.println("After setting milliseconds, time is: "+cal.getTime());

    System.out.println("TIME: "+cal.getTime());

Ответ 5

Вы создаете экземпляр текущей даты в GMT:

Calendar cal = GregorianCalendar.getInstance();
cal.setTime(today);

Затем вы меняете время на 4 часа:

cal.set(Calendar.HOUR_OF_DAY,hour);
cal.set(Calendar.MINUTE,min);
cal.set(Calendar.SECOND,sec);

Затем вы конвертируете дату с GMT в EST, которая составляет 23 00:

cal.setTimeZone(TimeZone.getTimeZone("EDT"));

Отладчик поможет вам увидеть эти изменения на каждом шагу:)