Изменение часового пояса без изменения времени в Java

Я получаю datetime из веб-службы SOAP без информации о часовом поясе. Следовательно, десериализатор оси принимает UTC. Тем не менее, дата-время действительно находится в Сиднее. Я решил проблему, вычитая смещение часового пояса:

Calendar trade_date = trade.getTradeDateTime();
TimeZone est_tz = TimeZone.getTimeZone("Australia/Sydney");
long millis = trade_date.getTimeInMillis() - est_tz.getRawOffset();
trade_date.setTimeZone( est_tz );
trade_date.setTimeInMillis( millis );

Однако я не уверен, что это решение также учитывает летнее время. Я думаю, что это нужно, потому что все операции выполняются в UTC. Любой опыт манипулирования временем в Java? Лучшие идеи о том, как решить эту проблему?

Ответ 1

Я решил переписать строку даты и времени, полученную с заданным часовым поясом. Это также должно учитывать летнее время:

public class DateTest {

    private static SimpleDateFormat soapdatetime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

    /**
     * @param args
     */
    public static void main(String[] args) {
        TimeZone oztz = TimeZone.getTimeZone("Australia/Sydney");
        TimeZone gmtz = TimeZone.getTimeZone("GMT");
        Calendar datetime = Calendar.getInstance( gmtz );

        soapdatetime.setTimeZone( gmtz );
        String soap_datetime = soapdatetime.format( datetime.getTime() );
        System.out.println( soap_datetime );

        soapdatetime.setTimeZone( oztz );
        datetime.setTimeZone( oztz );
        try {
            datetime.setTime(
                    soapdatetime.parse( soap_datetime )
            );
        } catch (ParseException e) {
            e.printStackTrace();
        }

        soapdatetime.setTimeZone( gmtz );
        soap_datetime = soapdatetime.format( datetime.getTime() );
        System.out.println( soap_datetime );
    }
}

Ответ 2

Мне жаль дурака, который должен делать даты на Java.

То, что вы сделали, почти наверняка будет ошибочным в переходах перехода на летнее время. Лучший способ сделать это, вероятно, создать новый объект Calendar, установить на нем Timezone, а затем установить все поля отдельно, так что год, месяц, день, час, минута, секунда, получение значений из объекта Date.

Edit:
Чтобы все были счастливы, вы должны, вероятно, сделать это:

Calendar utcTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Calendar sydneyTime = Calendar.getInstance(TimeZone.getTimeZone("Australia/Sydney");
utcTime.setTime(trade_date);
for (int i = 0; i < Calendar.FIELD_COUNT; i++) {
  sydneyTime.set(i, utcTime.get(i));
}

Тогда вы не будете использовать устаревшие методы.

Ответ 3

Я хочу поблагодарить человека за ответ 6. Это было отличное начало для меня и подход, который я не рассматривал. Для приведения его в уровень производственного кода необходимы некоторые дополнительные шаги. В частности, обратите внимание на шаги, необходимые для DST_OFFSET и ZONE_OFFSET. Я хочу поделиться решением, которое я придумал.

Это занимает время от входного объекта Calendar, копирует его на время вывода, устанавливает новый часовой пояс на выход. Это используется, когда требуется время от времени в базе данных и настройка часового пояса без изменения времени.

public static Calendar setNewTimeZoneCopyOldTime( Calendar inputTime, 
        TimeZone timeZone ) {
    if( (inputTime == null) || (timeZone == null) ) { return( null ); }

    Calendar outputTime = Calendar.getInstance( timeZone );
    for( int i = 0; i < Calendar.FIELD_COUNT; i++ ) {
        if( (i != Calendar.ZONE_OFFSET) && (i != Calendar.DST_OFFSET) ) { 
            outputTime.set(i, inputTime.get(i));
        }
    }

    return( (Calendar) outputTime.clone() );
}

Ответ 4

Однако я не уверен, что это решение также переносит летнее время в Счет. Я думаю, это должно, потому что все операции выполняются в режиме UTC.

Да, вы должны учитывать летнее время, так как оно влияет на смещение на UTC.

Есть ли опыт манипулирования временем в Java? Лучшие идеи о том, как решить эту проблему?

Joda-Time - это лучший API времени. Возможно, следующий фрагмент может помочь:

DateTimeZone zone; // TODO : get zone
DateTime fixedTimestamp = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond, zone);

Типы JodaTime неизменяемы, что также является преимуществом.

Ответ 5

Я обычно делаю это так

Calendar trade_date_utc = trade.getTradeDateTime();
TimeZone est_tz = TimeZone.getTimeZone("Australia/Sydney");
Calendar trade_date = Calendar.GetInstance(est_tz);
trade_date.setTimeInMillis( millis );

Ответ 6

Вы получаете строку стиля ISO 8601 из этой испорченной веб-службы? Если это так, библиотека Joda-Time 2.3 делает это очень просто.

Если вы получаете строку ISO 8601 без какого-либо смещения часового пояса, вы передаете объект часового пояса конструктору DateTime.

DateTimeZone timeZone = DateTimeZone.forID( "Australia/Sydney" );
String input = "2014-01-02T03:00:00"; // Note the lack of time zone offset at end.
DateTime dateTime = new DateTime( input, timeZone );

Дамп для консоли...

System.out.println( "dateTime: " + dateTime );

При запуске...

dateTime: 2014-01-02T03:00:00.000+11:00

Ответ 7

 @Test
 public void tzTest() {
     SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z");
     TimeZone tz1 = TimeZone.getTimeZone("Europe/Moscow");
     Calendar cal1 = Calendar.getInstance(tz1);
     long l1 = cal1.getTimeInMillis();
     df.setTimeZone(tz1);
     System.out.println(df.format(cal1.getTime()));
     System.out.println(l1);
     TimeZone tz2 = TimeZone.getTimeZone("Africa/Douala");
     Calendar cal2 = Calendar.getInstance(tz2);
     long l2 = l1 + tz1.getRawOffset() - tz2.getRawOffset();
     cal2.setTimeInMillis(l2);
     df.setTimeZone(tz2);
     System.out.println(df.format(cal2.getTime()));
     System.out.println(l2);
     assertNotEquals(l2, l1);
 }


Запуск CalendarTest
2016-06-30 19: 09: 16.522 +0300
1467302956522
2016-06-30 19: 09: 16.522 +0100
1467310156522
Выполнение тестов: 1, Ошибки: 0, Ошибки: 0, Пропущено: 0, Истекшее время: 0.137 сек