SimpleDateFormat.parse() игнорируя часовой пояс?

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

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZZZZZ", Locale.US);
Calendar calendar = Calendar.getInstance();
calendar.setTime(sdf.parse("2017-07-26T06:00-06:00"));
int offset = calendar.getTimeZone().getRawOffset();

Я пытаюсь изменить часовой пояс с -06 в +09, но offset всегда содержит 10800000,

Как правильно разобрать дату с часовым поясом (мне нужно время и часовой пояс оба)?

1 ответ

Решение

Замечания: -06:00 это смещение, а не часовой пояс - эти два понятия связаны, но это разные вещи (подробнее об этом ниже).


Проблема с SimpleDateFormat а также Calendar является то, что они используют часовой пояс системы по умолчанию, так что, даже если вы анализируете дату с другим смещением (например, -06:00), результирующий Calendar будет иметь часовой пояс по умолчанию (вы можете проверить, что это за зона, позвонив TimeZone.getDefault()).

Это только одна из многих проблем и проблем дизайна этого старого API.

К счастью, есть лучшая альтернатива, если вы не возражаете добавить зависимость к своему проекту (в этом случае, я думаю, это того стоит). В Android вы можете использовать ThreeTen Backport, отличный бэкпорт для новых классов даты / времени в Java 8. А для Android вам также понадобится ThreeTenABP, чтобы он работал (подробнее об этом здесь).

Для работы со смещениями вы можете использовать org.threeten.bp.OffsetDateTime учебный класс:

// parse the String
OffsetDateTime odt = OffsetDateTime.parse("2017-07-26T06:00-06:00");

Это позволит правильно проанализировать все поля (дата / время и смещение). Чтобы получить значение смещения, аналогичное calendar.getTimeZone().getRawOffset(), ты можешь сделать:

// get offset in milliseconds
int totalSeconds = odt.getOffset().getTotalSeconds() * 1000;

Я должен был умножить на 1000, потому что calendar возвращает значение в миллисекундах, но ZoneOffset возвращается в секундах.

Чтобы преобразовать это в другое смещение (+09:00), это просто:

// convert to +09:00 offset
OffsetDateTime other = odt.withOffsetSameInstant(ZoneOffset.ofHours(9));

Как я уже сказал, часовой пояс и смещение это разные вещи:

  • Смещение - это отличие от UTC: -06:00 означает "6 часов после UTC" и +09:00 означает "9 часов вперед по UTC"
  • Часовой пояс - это набор всех различных смещений, которые регион имел, имеет и будет иметь в течение своей истории (а также когда происходят эти изменения). Наиболее распространенными случаями являются смены летнего времени, когда часы переключаются на 1 час назад или вперед в определенном регионе. Все эти правила о том, когда менять (и каково смещение до и после изменения), заключены в концепции часового пояса.

Итак, приведенный выше код отлично работает, если вы работаете со смещениями и хотите преобразовать его в другой. Но если вы хотите работать с часовым поясом, вы должны преобразовать OffsetDateTime к ZonedDateTime:

// convert to a timezone
ZonedDateTime zdt = odt.atZoneSameInstant(ZoneId.of("Asia/Tokyo"));
// get the offset
totalSeconds = zdt.getOffset().getTotalSeconds() * 1000;

getOffset() Приведенный выше метод проверит историю указанного часового пояса и получит смещение, которое было активным в тот соответствующий момент (поэтому, если вы берете дату во время летнего времени, например, смещение (а также дата и время) будут соответственно скорректированы).

API использует имена часовых поясов IANA (всегда в формате Region/City, лайк America/Sao_Paulo или же Europe/Berlin). Избегайте использования трехбуквенных сокращений (например, CST или же PST) потому что они неоднозначны и не стандартны.

Вы можете получить список доступных часовых поясов (и выбрать тот, который лучше всего подходит вашей системе), позвонив ZoneId.getAvailableZoneIds(),

Вы также можете использовать часовой пояс системы по умолчанию с ZoneId.systemDefault(), но это может быть изменено без уведомления, даже во время выполнения, так что лучше для простоты использовать определенный.

Другие вопросы по тегам