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()
, но это может быть изменено без уведомления, даже во время выполнения, так что лучше для простоты использовать определенный.