Java Convertir GMT / UTC a hora local no funciona como se esperaba

Para mostrar un escenario reproducible, estoy haciendo lo siguiente

  1. Obtener la hora actual del sistema (hora local)

  2. Convertir la hora local en UTC // Funciona bien Hasta aquí

  3. Invierte la hora UTC, vuelve a la hora local. Siguió 3 enfoques diferentes (enumerados a continuación) pero los 3 enfoques conservan el tiempo en UTC solamente.

    {

    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()); 

    }

También recomiendo usar Joda como se mencionó anteriormente.

Solo puede resolver su problema utilizando objetos estándar de Date Java de la siguiente manera:

  // **** 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()); 

Salida:

 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


ACTUALIZACIÓN: El proyecto Joda-Time ahora está en modo de mantenimiento , y el equipo recomienda la migración a las clases java.time . Ver Tutorial de Oracle .

Vea mi otra respuesta usando las clases java.time líderes en la industria.


Normalmente consideramos que es una mala forma en StackOverflow.com responder a una pregunta específica al sugerir una tecnología alternativa. Pero en el caso de las clases de fecha, hora y calendario incluidas con Java 7 y versiones anteriores, esas clases son tan notoriamente malas tanto en diseño como en ejecución, por lo que me veo obligado a sugerir el uso de una biblioteca de terceros: Joda-Time .

Joda-Time funciona al crear objetos inmutables . Entonces, en lugar de modificar el huso horario de un objeto DateTime, simplemente instanciamos un nuevo DateTime con una zona horaria diferente asignada.

Su preocupación central de usar tanto la hora local como la hora UTC es muy simple en Joda-Time, tomando solo 3 líneas 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 ) ); 

La salida cuando se ejecuta en la costa oeste de América del Norte podría 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

Aquí hay una clase con varios ejemplos y más comentarios. Usando 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 classes 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 ) ) ); } } 

Me estoy uniendo al coro recomendando que te saltes las ahora obsoletas clases Date , Calendar , SimpleDateFormat y SimpleDateFormat amigos. En particular, advertiría contra el uso de los métodos obsoletos y los constructores de la clase Date , como el constructor Date(String) que usaste. Desaprobaron porque no funcionan de manera confiable en las zonas horarias, por lo tanto, no los use. Y sí, la mayoría de los constructores y métodos de esa clase están en desuso.

Mientras que en el momento en que hiciste la pregunta, Joda-Time era (por lo que sé) una alternativa claramente mejor, el tiempo ha cambiado nuevamente. Hoy Joda-Time es un proyecto en gran parte terminado, y sus desarrolladores recomiendan utilizar java.time , la moderna API de fecha y hora de Java. Te mostraré cómo.

  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 empezar, tenga en cuenta que el código no solo es la mitad que el suyo, sino que también es más claro de leer.

En mi computadora, el código se 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] 

Olvidé los milisegundos de la época. Siempre puede obtenerlos de System.currentTimeMillis(); como en su pregunta, y son independientes de la zona horaria, así que no los encontré intersting aquí.

localTime mantuve tu nombre de variable localTime . Creo que es un buen nombre. La API moderna tiene una clase llamada LocalTime , por lo que usar ese nombre, que no está en mayúscula, para un objeto que no tiene el tipo LocalTime podría confundir algo (un LocalTime no contiene información de zona horaria, que debemos mantener aquí para que sea capaz de hacer la conversión correcta, sino que solo contiene la hora del día, no la fecha).

Su conversión de la hora local a UTC fue incorrecta e imposible

La clase Date desactualizada no contiene información de zona horaria (puede decir que internamente siempre usa UTC), por lo que no existe la conversión de una Date de una zona horaria a otra. Cuando acabo de ejecutar su código en mi computadora, la primera línea que imprimió fue:

 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 es correcto, por supuesto. La hora UTC correcta habría sido 05:25:45 UTC , pero dice CEST nuevamente, lo cual es incorrecto.

Ahora nunca volverás a necesitar la clase Date , 🙂 pero si alguna vez ibas a hacerlo, la lectura obligatoria sería All about java.util.Date en el blog de encoding de Jon Skeet.

Pregunta: ¿Puedo usar la API moderna con mi versión de Java?

Si usa al menos Java 6 , puede hacerlo.

  • En Java 8 y versiones posteriores, la nueva API viene incorporada.
  • En Java 6 y 7 obtenemos el ThreeTen Backport , el backport de las nuevas clases (eso es ThreeTen for JSR-310, donde se definió por primera vez la API moderna).
  • En Android, use la edición de Android de ThreeTen Backport. Se llama ThreeTenABP, y creo que hay una explicación maravillosa en esta pregunta: Cómo usar ThreeTenABP en Android Project .

Recomiendo utilizar Joda Time http://joda-time.sourceforge.net/faq.html

Tienes una fecha con una zona horaria conocida (Aquí Europe/Madrid ) y una zona horaria objective ( UTC )

Solo necesitas dos SimpleDateFormats:

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

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

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

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

         // Inversa Convertir hora UTC a hora local
         localTime = sdfUTC.parse (sdfLocal.format (utcTime));
         System.out.println ("UTC:" + utcTime.toString () + "," + utcTime.getTime () + "-> Hora local:" + localTime.toString () + "-" + localTime.getTime ( ));

Entonces, después de verlo funcionar, puede agregar este método a sus utilidades:

     public Date convertDate (Date dateFrom, String fromTimeZone, String toTimeZone) throws ParseException {
         Patrón de cadena = "aaaa / MM / dd HH: mm: ss";
         SimpleDateFormat sdfFrom = new SimpleDateFormat (patrón);
         sdfFrom.setTimeZone (TimeZone.getTimeZone (fromTimeZone));

         SimpleDateFormat sdfTo = new SimpleDateFormat (patrón);
         sdfTo.setTimeZone (TimeZone.getTimeZone (toTimeZone));

         Fecha dateTo = sdfFrom.parse (sdfTo.format (dateFrom));
         fecha de regreso a;
     }

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

El enfoque moderno usa las clases java.time que suplantaron las viejas clases heredadas de fecha y hora ( Date , Calendar , etc.).

El uso de la palabra “local” contradice el uso en la clase java.time . En java.time , “local” significa cualquier localidad o todas las localidades, pero no una localidad en particular. Las clases java.time con nombres que comienzan con “Local …” carecen de cualquier concepto de zona horaria o desplazamiento desde UTC. Por lo tanto, no representan un momento específico, no son un punto en la línea de tiempo, mientras que su Pregunta se trata de momentos, puntos en la línea de tiempo vistos a través de varios tiempos de reloj de pared.

Obtener la hora actual del sistema (hora local)

Si desea capturar el momento actual en UTC, use Instant . La clase Instant representa un momento en la línea de tiempo en UTC con una resolución de nanosegundos (hasta nueve (9) dígitos de una fracción decimal).

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

Ajuste en un huso horario aplicando un ZoneId para obtener un ZonedDateTime . Mismo momento, mismo punto en la línea de tiempo, diferente hora del reloj de pared.

Especifique un nombre de zona horaria adecuada en el formato continent/region , como America/Montreal , Africa/Casablanca o Pacific/Auckland . Nunca utilice la abreviatura de 3-4 letras, como EST o IST ya que no son zonas horarias verdaderas, no están estandarizadas y ni siquiera son únicas (!).

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

Como acceso directo, puede omitir el uso de Instant para obtener un ZonedDateTime .

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

Convertir la hora local en UTC // Funciona bien Hasta aquí

Puede ajustar desde la fecha-hora zonal a UTC extrayendo un Instant desde un ZonedDateTime .

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

Invierte la hora UTC, vuelve a la hora local.

Como se muestra arriba, aplique un ZoneId para ajustar el mismo momento en otro reloj de pared utilizado por las personas de una determinada región (una zona horaria).

 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 ) ; 

Volviendo a UTC desde una fecha zonal, llame a ZonedDateTime::toInstant . Considérelo conceptualmente como: ZonedDateTime = Instant + ZoneId .

 Instant instant = zdtAuckland.toInstant() ; 

Todos estos objetos, Instant y los tres objetos ZonedDateTime , representan el mismo momento simultáneo, el mismo punto en la historia.

Siguió 3 enfoques diferentes (enumerados a continuación) pero los 3 enfoques conservan el tiempo en UTC solamente.

Olvídate de intentar arreglar el código usando esas espantosas clases de Date , Calendar y GregorianCalendar . Son un desorden miserable de mal diseño y defectos. No necesitas volver a tocarlos nunca más. Si debe interactuar con un código antiguo que aún no se ha actualizado a java.time , puede convertirlo de ida y vuelta mediante nuevos métodos de conversión agregados a las clases anteriores.


Acerca de java.time

El marco java.time está integrado en Java 8 y posterior. Estas clases suplantan a las problemáticas antiguas clases heredadas de fecha y hora como java.util.Date , Calendar y SimpleDateFormat .

El proyecto Joda-Time , ahora en modo de mantenimiento , aconseja la migración a las clases java.time .

Para obtener más información, consulte el Tutorial de Oracle . Y busque Stack Overflow para obtener muchos ejemplos y explicaciones. La especificación es JSR 310 .

Puede intercambiar objetos java.time directamente con su base de datos. Use un controlador JDBC que cumpla con JDBC 4.2 o posterior. Sin necesidad de cadenas, sin necesidad de clases java.sql.* .

¿Dónde obtener las clases de java.time?

  • Java SE 8 , Java SE 9 , Java SE 10 y posterior
    • Incorporado.
    • Parte de la API Java estándar con una implementación integrada.
    • Java 9 agrega algunas características y correcciones menores.
  • Java SE 6 y Java SE 7
    • Gran parte de la funcionalidad de java.time se transfiere a Java 6 y 7 en ThreeTen-Backport .
  • Androide
    • Versiones posteriores de las implementaciones del paquete de Android de las clases java.time.
    • Para Android anterior (<26), el proyecto ThreeTenABP adapta ThreeTen-Backport (mencionado anteriormente). Consulte Cómo utilizar ThreeTenABP ….

El proyecto ThreeTen-Extra amplía java.time con clases adicionales. Este proyecto es un terreno de prueba para posibles adiciones futuras a java.time. Puede encontrar algunas clases útiles aquí, como Interval , YearWeek , YearQuarter y más .