Lidando com o horário de verão em Java
1. Visão geral
Daylight Saving Time, ou DST, é uma prática de avançar os relógios durante os meses de verão para aproveitar uma hora adicional de luz natural (economizando energia de aquecimento, energia de iluminação, melhorando o clima e assim por diante).
É usado porseveral countriese precisa ser levado em consideração ao trabalhar com datas e carimbos de data / hora.
Neste tutorial, veremos como lidar corretamente com o DST em Java de acordo com diferentes locais.
2. Mutabilidade JRE e DST
Em primeiro lugar, é extremamente importante entender que as zonas DST em todo o mundochange very oftene não há nenhuma autoridade central coordenando isso.
Um país, ou em alguns casos até uma cidade, pode decidir se e como aplicá-la ou revogá-la.
Sempre que isso acontece, a mudança é registrada emIANA Time Zone Database, e a atualização será lançada em um futurorelease of the JRE.
In case it’s not possible to wait, we can force the modified Time Zone data containing the new DST settings into the JRE por meio de uma ferramenta oficial da Oracle chamadaJava Time Zone Updater Tool, disponível emJava SE download page.
3. A maneira errada: ID do fuso horário de três letras
No JDK 1.1 dias, a API permitia IDs de fuso horário com três letras, mas isso levou a vários problemas.
Primeiro, isso ocorreu porque o mesmo ID de três letras poderia se referir a vários fusos horários. Por exemplo,CST poderia ser U.S. “Horário Padrão Central”, mas também “Horário Padrão da China”. A plataforma Java poderia então reconhecer apenas um deles.
Outra questão foi que os fusos horários padrão nunca levam o horário de verão para uma conta. Várias áreas / regiões / cidades podem ter seu horário de verão local dentro do mesmo fuso horário padrão, portanto, o horário padrão não o observa.
Devido à compatibilidade com versões anteriores,it’s still possible to instantiate a java.util.Timezone with a three-letter ID. However, this method is deprecated and shouldn’t be used anymore.
4. O caminho certo: ID do fuso horário TZDB
A maneira certa de lidar com o horário de verão em Java é instanciar umTimezone com um ID de fuso horário TZDB específico, por exemplo. “Europe/Rome”.
Então, vamos usar isso em conjunto com classes específicas de tempo comojava.util.Calendar para obter uma configuração adequada do deslocamento bruto deTimeZone’s (para o fuso horário GMT) e ajustes automáticos de mudança de horário de verão.
Vamos ver como a mudança deGMT+1 paraGMT+2 (que acontece na Itália em 25 de março de 2018, às 02:00) é tratada automaticamente ao usar oTimeZone: certo
TimeZone tz = TimeZone.getTimeZone("Europe/Rome");
TimeZone.setDefault(tz);
Calendar cal = Calendar.getInstance(tz, Locale.ITALIAN);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ITALIAN);
Date dateBeforeDST = df.parse("2018-03-25 01:55");
cal.setTime(dateBeforeDST);
assertThat(cal.get(Calendar.ZONE_OFFSET)).isEqualTo(3600000);
assertThat(cal.get(Calendar.DST_OFFSET)).isEqualTo(0);
Como podemos ver,ZONE_OFFSET é 60 minutos (porque a Itália éGMT+1) enquantoDST_OFFSET é 0 naquele momento.
Vamos adicionar dez minutos aoCalendar:
cal.add(Calendar.MINUTE, 10);
AgoraDST_OFFSET também se tornou 60 minutos, e o país fez a transição de seu horário local deCET (horário da Europa Central) paraCEST (horário de verão da Europa Central), que éGMT+2 :
Date dateAfterDST = cal.getTime();
assertThat(cal.get(Calendar.DST_OFFSET))
.isEqualTo(3600000);
assertThat(dateAfterDST)
.isEqualTo(df.parse("2018-03-25 03:05"));
Se exibirmos as duas datas no console, veremos a mudança de fuso horário também:
Before DST (00:55 UTC - 01:55 GMT+1) = Sun Mar 25 01:55:00 CET 2018
After DST (01:05 UTC - 03:05 GMT+2) = Sun Mar 25 03:05:00 CEST 2018
Como teste final, podemos medir a distância entre os doisDates, 1:55 e 3:05:
Long deltaBetweenDatesInMillis = dateAfterDST.getTime() - dateBeforeDST.getTime();
Long tenMinutesInMillis = (1000L * 60 * 10);
assertThat(deltaBetweenDatesInMillis)
.isEqualTo(tenMinutesInMillis);
Como seria de se esperar, a distância é de 10 minutos em vez de 70.
Vimos como evitar cair nas armadilhas comuns que podemos encontrar ao trabalhar comDate por meio do uso correto deTimeZoneeLocale.
5. A melhor maneira: API Java 8 Date / Time
Trabalhar com essas classesjava.util thread-inseguras e nem sempre amigáveis ao usuário sempre foi difícil, especialmente devido a questões de compatibilidade que as impediam de serem adequadamente refatoradas.
Por esta razão, Java 8 introduziu um novo pacote,java.time, e um conjunto de API totalmente novo, oDate/Time API.. Este é ISO-centric, totalmente thread-safe e fortemente inspirado na famosa biblioteca Joda- Tempo.
Vamos dar uma olhada mais de perto nessas novas classes, começando com o sucessor dejava.util.Date,java.time.LocalDateTime:
LocalDateTime localDateTimeBeforeDST = LocalDateTime
.of(2018, 3, 25, 1, 55);
assertThat(localDateTimeBeforeDST.toString())
.isEqualTo("2018-03-25T01:55");
Podemos observar como aLocalDateTime está em conformidade com o perfilISO8601, uma notação data-hora padrão e amplamente adotada.
Ele não tem conhecimento deZones eOffsets, porém, é por isso que precisamos convertê-lo ema fully DST-aware java.time.ZonedDateTime:
ZoneId italianZoneId = ZoneId.of("Europe/Rome");
ZonedDateTime zonedDateTimeBeforeDST = localDateTimeBeforeDST
.atZone(italianZoneId);
assertThat(zonedDateTimeBeforeDST.toString())
.isEqualTo("2018-03-25T01:55+01:00[Europe/Rome]");
Como podemos ver, agora a data incorpora duas informações fundamentais:+01:00 éZoneOffset, enquanto[Europe/Rome] éZoneId.
Como no exemplo anterior, vamos acionar o DST adicionando dez minutos:
ZonedDateTime zonedDateTimeAfterDST = zonedDateTimeBeforeDST
.plus(10, ChronoUnit.MINUTES);
assertThat(zonedDateTimeAfterDST.toString())
.isEqualTo("2018-03-25T03:05+02:00[Europe/Rome]");
Mais uma vez, vemos como o tempo e o deslocamento da zona estão mudando para a frente e mantendo a mesma distância:
Long deltaBetweenDatesInMinutes = ChronoUnit.MINUTES
.between(zonedDateTimeBeforeDST,zonedDateTimeAfterDST);
assertThat(deltaBetweenDatesInMinutes)
.isEqualTo(10);
6. Conclusão
Vimos o que é o horário de verão e como lidar com isso por meio de alguns exemplos práticos em diferentes versões da API principal do Java.
Ao trabalhar com Java 8 e superior, o uso do novo pacotejava.time é encorajado graças à facilidade de uso e à sua natureza thread-safe padrão.
Como sempre, o código-fonte completo está disponívelover on Github.