Java Converter GMT / UTC para a hora local não funciona como esperado

Para mostrar um cenário reproduzível, estou fazendo o seguinte

  1. Obter a hora atual do sistema (hora local)

  2. Converter Hora local para UTC // Funciona bem até aqui

  3. Inverta a hora UTC, de volta à hora local. Seguiu três abordagens diferentes (listadas abaixo), mas todas as três abordagens retêm o tempo apenas em UTC.

    {

    long ts = System.currentTimeMillis(); Date localTime = new Date(ts); String format = "yyyy/MM/dd HH:mm:ss"; SimpleDateFormat sdf = new SimpleDateFormat (format); // Convert Local Time to UTC (Works Fine) sdf.setTimeZone(TimeZone.getTimeZone("UTC")); Date gmtTime = new Date(sdf.format(localTime)); System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime()); // Reverse Convert UTC Time to Locale time (Doesn't work) Approach 1 sdf.setTimeZone(TimeZone.getDefault()); localTime = new Date(sdf.format(gmtTime)); System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime()); // Reverse Convert UTC Time to Locale time (Doesn't work) Approach 2 using DateFormat DateFormat df = new SimpleDateFormat (format); df.setTimeZone(TimeZone.getDefault()); localTime = df.parse((df.format(gmtTime))); System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime()); // Approach 3 Calendar c = new GregorianCalendar(TimeZone.getDefault()); c.setTimeInMillis(gmtTime.getTime()); System.out.println("Local Time " + c.toString()); 

    }

Eu também recomendo usar o Joda como mencionado anteriormente.

Resolver seu problema usando apenas objects Java Date padrão pode ser feito da seguinte maneira:

  // **** YOUR CODE **** BEGIN **** long ts = System.currentTimeMillis(); Date localTime = new Date(ts); String format = "yyyy/MM/dd HH:mm:ss"; SimpleDateFormat sdf = new SimpleDateFormat(format); // Convert Local Time to UTC (Works Fine) sdf.setTimeZone(TimeZone.getTimeZone("UTC")); Date gmtTime = new Date(sdf.format(localTime)); System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "," + gmtTime.getTime()); // **** YOUR CODE **** END **** // Convert UTC to Local Time Date fromGmt = new Date(gmtTime.getTime() + TimeZone.getDefault().getOffset(localTime.getTime())); System.out.println("UTC time:" + gmtTime.toString() + "," + gmtTime.getTime() + " --> Local:" + fromGmt.toString() + "-" + fromGmt.getTime()); 

Saída:

 Local:Tue Oct 15 12:19:40 CEST 2013,1381832380522 --> UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000 UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000 --> Local:Tue Oct 15 12:19:40 CEST 2013-1381832380000 

Joda-Time


ATUALIZAÇÃO: O projeto Joda-Time está agora no modo de manutenção , com a equipe recomendando a migration para as classs java.time . Veja o tutorial do Oracle .

Veja minha outra resposta usando as classs java.time líderes do setor .


Normalmente, consideramos uma má forma no StackOverflow.com responder a uma pergunta específica sugerindo uma tecnologia alternativa. Mas no caso das classs de data, hora e calendar empacotadas com o Java 7 e versões anteriores, essas classs são tão notoriamente ruins em design e execução que eu sou compelido a sugerir o uso de uma biblioteca de terceiros: Joda-Time .

Joda-Time trabalha criando objects imutáveis . Portanto, em vez de alterar o fuso horário de um object DateTime, simplesmente instanciamos um novo DateTime com um fuso horário diferente atribuído.

Sua preocupação central de usar tanto a hora local quanto a hora UTC é muito simples no Joda-Time, tomando apenas 3 linhas de código.

  org.joda.time.DateTime now = new org.joda.time.DateTime(); System.out.println( "Local time in ISO 8601 format: " + now + " in zone: " + now.getZone() ); System.out.println( "UTC (Zulu) time zone: " + now.toDateTime( org.joda.time.DateTimeZone.UTC ) ); 

Saída quando executado na costa oeste da América do Norte pode ser:

Local time in ISO 8601 format: 2013-10-15T02:45:30.801-07:00

UTC (Zulu) time zone: 2013-10-15T09:45:30.801Z

Aqui está uma aula com vários exemplos e mais comentários. Usando o Joda-Time 2.5.

 /** * Created by Basil Bourque on 2013-10-15. * © Basil Bourque 2013 * This source code may be used freely forever by anyone taking full responsibility for doing so. */ public class TimeExample { public static void main(String[] args) { // Joda-Time - The popular alternative to Sun/Oracle's notoriously bad date, time, and calendar classs bundled with Java 8 and earlier. // http://www.joda.org/joda-time/ // Joda-Time will become outmoded by the JSR 310 Date and Time API introduced in Java 8. // JSR 310 was inspired by Joda-Time but is not directly based on it. // http://jcp.org/en/jsr/detail?id=310 // By default, Joda-Time produces strings in the standard ISO 8601 format. // https://en.wikipedia.org/wiki/ISO_8601 // You may output to strings in other formats. // Capture one moment in time, to be used in all the examples to follow. org.joda.time.DateTime now = new org.joda.time.DateTime(); System.out.println( "Local time in ISO 8601 format: " + now + " in zone: " + now.getZone() ); System.out.println( "UTC (Zulu) time zone: " + now.toDateTime( org.joda.time.DateTimeZone.UTC ) ); // You may specify a time zone in either of two ways: // • Using identifiers bundled with Joda-Time // • Using identifiers bundled with Java via its TimeZone class // ----| Joda-Time Zones |--------------------------------- // Time zone identifiers defined by Joda-Time… System.out.println( "Time zones defined in Joda-Time : " + java.util.Arrays.toString( org.joda.time.DateTimeZone.getAvailableIDs().toArray() ) ); // Specify a time zone using DateTimeZone objects from Joda-Time. // http://joda-time.sourceforge.net/apidocs/org/joda/time/DateTimeZone.html org.joda.time.DateTimeZone parisDateTimeZone = org.joda.time.DateTimeZone.forID( "Europe/Paris" ); System.out.println( "Paris France (Joda-Time zone): " + now.toDateTime( parisDateTimeZone ) ); // ----| Java Zones |--------------------------------- // Time zone identifiers defined by Java… System.out.println( "Time zones defined within Java : " + java.util.Arrays.toString( java.util.TimeZone.getAvailableIDs() ) ); // Specify a time zone using TimeZone objects built into Java. // http://docs.oracle.com/javase/8/docs/api/java/util/TimeZone.html java.util.TimeZone parisTimeZone = java.util.TimeZone.getTimeZone( "Europe/Paris" ); System.out.println( "Paris France (Java zone): " + now.toDateTime(org.joda.time.DateTimeZone.forTimeZone( parisTimeZone ) ) ); } } 

Estou entrando no coro recomendando que você pule as classs desatualizadas, Date , Calendar , SimpleDateFormat e amigos. Em particular, gostaria de alertar contra o uso de methods e construtores obsoletos da class Date , como o construtor Date(String) você usou. Eles foram reprovados porque não funcionam de maneira confiável nos fusos horários, portanto, não os use. E sim, a maioria dos construtores e methods dessa class são obsoletos.

Enquanto no momento em que você fez a pergunta, Joda-Time foi (de tudo que eu sei) uma alternativa claramente melhor, o tempo mudou novamente. Hoje o Joda-Time é um projeto amplamente finalizado, e seus desenvolvedores recomendam que você use java.time , a moderna API de data e hora Java. Eu vou te mostrar como.

  ZonedDateTime localTime = ZonedDateTime.now(ZoneId.systemDefault()); // Convert Local Time to UTC OffsetDateTime gmtTime = localTime.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.UTC); System.out.println("Local:" + localTime.toString() + " --> UTC time:" + gmtTime.toString()); // Reverse Convert UTC Time to Local time localTime = gmtTime.atZoneSameInstant(ZoneId.systemDefault()); System.out.println("Local Time " + localTime.toString()); 

Para começar, note que não só o código tem metade do tamanho do seu, como também é mais claro de ler.

No meu computador, o código imprime:

 Local:2017-09-02T07:25:46.211+02:00[Europe/Berlin] --> UTC time:2017-09-02T05:25:46.211Z Local Time 2017-09-02T07:25:46.211+02:00[Europe/Berlin] 

Deixei de fora os milissegundos da época. Você sempre pode obtê-los de System.currentTimeMillis(); como na sua pergunta, e eles são independentes do fuso horário, então eu não os achei interesantes aqui.

Eu hesitantemente mantive seu nome de variável localTime . Eu acho que é um bom nome. A API moderna tem uma class chamada LocalTime , então usar esse nome, só que não maiúscula, pois um object que não tenha o tipo LocalTime pode confundir alguns (um LocalTime não armazena informações de fuso horário, o que precisamos manter aqui para ser capaz de fazer a conversão correta; também só mantém a hora do dia, não a data).

Sua conversão da hora local para a UTC estava incorreta e impossível

A class de Date desatualizada não contém nenhuma informação de fuso horário (você pode dizer que internamente ela sempre usa UTC), portanto, não há como converter uma Date de um fuso horário para outro. Quando eu apenas corri o seu código no meu computador, a primeira linha impressa era:

 Local:Sat Sep 02 07:25:45 CEST 2017,1504329945967 --> UTC time:Sat Sep 02 05:25:45 CEST 2017-1504322745000 

07:25:45 CEST está correto, claro. O horário UTC correto teria sido 05:25:45 UTC , mas diz CEST novamente, o que é incorreto.

Agora você nunca vai precisar da class Date novamente, 🙂 mas se você fosse, a leitura obrigatória seria All about java.util.Date no blog de codificação de Jon Skeet.

Pergunta: Posso usar a API moderna com minha versão Java?

Se você usar pelo menos o Java 6 , você pode.

  • No Java 8 e posterior, a nova API vem incorporada.
  • No Java 6 e 7, obtenha o ThreeTen Backport , o backport das novas classs (que é o ThreeTen para JSR-310, onde a API moderna foi definida pela primeira vez).
  • No Android, use a edição Android do ThreeTen Backport. Chama-se ThreeTenABP, e acho que há uma explicação maravilhosa nesta questão: Como usar o ThreeTenABP no Android Project .

Eu recomendo fortemente usar o Joda Time http://joda-time.sourceforge.net/faq.html

Você tem um encontro com um fuso horário conhecido (Aqui Europe/Madrid ) e um fuso horário ( UTC )

Você só precisa de dois SimpleDateFormats:

         long ts = System.currentTimeMillis ();
         Data localTime = new Date (ts);

         SimpleDateFormat sdfLocal = new SimpleDateFormat ("aaaa / MM / dd HH: mm: ss");
         sdfLocal.setTimeZone (TimeZone.getTimeZone ("Europe / Madrid"));

         SimpleDateFormat sdfUTC = new SimpleDateFormat ("aaaa / MM / dd HH: mm: ss");
         sdfUTC.setTimeZone (TimeZone.getTimeZone ("UTC"));

         // Converter hora local em UTC
         Data utcTime = sdfLocal.parse (sdfUTC.format (localTime));
         System.out.println ("Local:" + localTime.toString () + "," + localTime.getTime () + "-> hora UTC:" + utcTime.toString () + "-" + utcTime.getTime ( ));

         // Reverse Convert UTC Time para Locale time
         localTime = sdfUTC.parse (sdfLocal.format (utcTime));
         System.out.println ("UTC:" + utcTime.toString () + "," + utcTime.getTime () + "-> Hora local:" + localTime.toString () + "-" + localTime.getTime ( ));

Então, depois de vê-lo funcionando, você pode adicionar este método aos seus utilitários:

     public Date convertDate (Date dateFrom, String fromTimeZone, String toTimeZone) lança ParseException {
         Padrão de cadeia = "aaaa / MM / dd HH: mm: ss";
         SimpleDateFormat sdfFrom = new SimpleDateFormat (padrão);
         sdfFrom.setTimeZone (TimeZone.getTimeZone (fromTimeZone));

         SimpleDateFormat sdfTo = new SimpleDateFormat (padrão);
         sdfTo.setTimeZone (TimeZone.getTimeZone (toTimeZone));

         Data dateTo = sdfFrom.parse (sdfTo.format (dateFrom));
         return dateTo;
     }

tl; dr

 Instant.now() // Capture the current moment in UTC. .atZone( ZoneId.systemDefault() ) // Adjust into the JVM's current default time zone. Same moment, different wall-clock time. Produces a `ZonedDateTime` object. .toInstant() // Extract a `Instant` (always in UTC) object from the `ZonedDateTime` object. .atZone( ZoneId.of( "Europe/Paris" ) ) // Adjust the `Instant` into a specific time zone. Renders a `ZonedDateTime` object. Same moment, different wall-clock time. .toInstant() // And back to UTC again. 

java.time

A abordagem moderna usa as classs java.time que suplantaram as antigas classs legadas de data e hora ( Date , Calendar , etc.).

Seu uso da palavra “local” contradiz o uso na class java.time . Em java.time , “local” significa qualquer localidade ou todas as localidades, mas não uma localidade específica. As classs java.time com nomes começando com “Local …” não possuem nenhum conceito de fuso horário ou deslocamento de UTC. Então, eles não representam um momento específico, eles não são um ponto na linha do tempo, enquanto a sua pergunta é sobre momentos, pontos na linha do tempo vista através de vários tempos de relógio de parede.

Obter a hora atual do sistema (hora local)

Se você quiser capturar o momento atual no UTC, use Instant . A class Instant representa um momento na linha do tempo no UTC com uma resolução de nanossegundos (até nove (9) dígitos de uma fração decimal).

 Instant instant = Instant.now() ; // Capture the current moment in UTC. 

Ajustar em um fuso horário, aplicando um ZoneId para obter um ZonedDateTime . Mesmo momento, mesmo ponto na linha do tempo, tempo de relógio de parede diferente.

Especifique um nome de fuso horário adequado no formato de continent/region , como America/Montreal , Africa/Casablanca ou Pacific/Auckland . Nunca use a abreviação de 3 a 4 letras, como EST ou IST pois eles não são verdadeiros fusos horários, não são padronizados e nem mesmo exclusivos (!).

 ZoneId z = ZoneId.of( "America/Montreal" ) ; ZonedDateTime zdt = instant.atZone( z ) ; // Same moment, different wall-clock time. 

Como um atalho, você pode pular o uso de Instant para obter um ZonedDateTime .

 ZoneId z = ZoneId.of( "America/Montreal" ) ; ZonedDateTime zdt = ZonedDateTime.now( z ) ; 

Converter Hora local para UTC // Funciona bem até aqui

Você pode ajustar a partir da data e hora dividida em zonas para UTC extraindo um Instant de um ZonedDateTime .

 ZoneId z = ZoneId.of( "America/Montreal" ) ; ZonedDateTime zdt = ZonedDateTime.now( z ) ; Instant instant = zdt.toInstant() ; 

Inverta a hora UTC, de volta à hora local.

Como mostrado acima, aplique um ZoneId para ajustar o mesmo momento em outro tempo de relógio de parede usado pelas pessoas de uma determinada região (um fuso horário).

 Instant instant = Instant.now() ; // Capture current moment in UTC. ZoneId zDefault = ZoneId.systemDefault() ; // The JVM's current default time zone. ZonedDateTime zdtDefault = instant.atZone( zDefault ) ; ZoneId zTunis = ZoneId.of( "Africa/Tunis" ) ; // The JVM's current default time zone. ZonedDateTime zdtTunis = instant.atZone( zTunis ) ; ZoneId zAuckland = ZoneId.of( "Pacific/Auckland" ) ; // The JVM's current default time zone. ZonedDateTime zdtAuckland = instant.atZone( zAuckland ) ; 

Voltando ao UTC a partir de uma data-hora zoneada, chame ZonedDateTime::toInstant . Pense nisso conceitualmente como: ZonedDateTime = Instant + ZoneId .

 Instant instant = zdtAuckland.toInstant() ; 

Todos esses objects, o Instant e os três objects ZonedDateTime , representam o mesmo momento simultâneo, o mesmo ponto na história.

Seguiu três abordagens diferentes (listadas abaixo), mas todas as três abordagens retêm o tempo apenas em UTC.

Esqueça-se de tentar consertar código usando essas terríveis classs Date , Calendar e GregorianCalendar . Eles são uma bagunça miserável de design ruim e falhas. Você nunca precisa tocá-los novamente. Se você precisar fazer interface com o código antigo ainda não atualizado para java.time , poderá converter de volta para frente por meio de novos methods de conversão adicionados às classs antigas.


Sobre o java.time

O framework java.time está embutido no Java 8 e posterior. Essas classs substituem as antigas classs herdadas de data e hora legadas , como java.util.Date , Calendar e SimpleDateFormat .

O projeto Joda-Time , agora em modo de manutenção , aconselha a migration para as classs java.time .

Para saber mais, veja o Oracle Tutorial . E pesquise o Stack Overflow para muitos exemplos e explicações. A especificação é JSR 310 .

Você pode trocar objects java.time diretamente com seu database. Use um driver JDBC compatível com o JDBC 4.2 ou posterior. Não há necessidade de strings, não há necessidade de classs java.sql.* .

Onde obter as classs java.time?

  • Java SE 8 , Java SE 9 , Java SE 10 e posterior
    • Construídas em.
    • Parte da API Java padrão com uma implementação integrada.
    • O Java 9 adiciona alguns resources e correções menores.
  • Java SE 6 e Java SE 7
    • Grande parte da funcionalidade java.time é transferida para o Java 6 e 7 no ThreeTen-Backport .
  • Android
    • Versões posteriores das implementações do pacote Android das classs java.time.
    • Para Android anterior (<26), o projeto ThreeTenABP adapta o ThreeTen-Backport (mencionado acima). Veja Como usar o ThreeTenABP… .

O projeto ThreeTen-Extra estende java.time com classs adicionais. Este projeto é um campo de testes para possíveis futuras adições ao java.time. Você pode encontrar algumas classs úteis aqui como Interval , YearWeek , YearQuarter e muito mais .