El análisis SimpleDateFormat pierde la zona horaria

Código:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z"); sdf.setTimeZone(TimeZone.getTimeZone("GMT")); System.out.println(new Date()); try { String d = sdf.format(new Date()); System.out.println(d); System.out.println(sdf.parse(d)); } catch (Exception e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } 

Salida:

 Thu Aug 08 17:26:32 GMT+08:00 2013 2013.08.08 09:26:32 GMT Thu Aug 08 17:26:32 GMT+08:00 2013 

Tenga en cuenta que el format() formatea la Date correctamente en GMT, pero parse() perdió los detalles GMT. Sé que puedo usar la substring() y eludir esto, pero ¿cuál es la razón subyacente de este fenómeno?

Aquí hay una pregunta duplicada que no tiene ninguna respuesta.

Editar: Permítame plantear la pregunta de otra manera, ¿cuál es la forma de recuperar un objeto Date para que siempre esté en GMT?

Todo lo que necesitaba era esto:

 SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); sdf.setTimeZone(TimeZone.getTimeZone("GMT")); SimpleDateFormat sdfLocal = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); try { String d = sdf.format(new Date()); System.out.println(d); System.out.println(sdfLocal.parse(d)); } catch (Exception e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } 

Salida: ligeramente dudosa, pero solo quiero que la fecha sea coherente

 2013.08.08 11:01:08 Thu Aug 08 11:01:08 GMT+08:00 2013 

La solución de OP a su problema, como él dice, tiene resultados dudosos. Ese código todavía muestra confusión sobre las representaciones del tiempo. Para aclarar esta confusión y crear un código que no conduzca a tiempos incorrectos, considere esta extensión de lo que hizo:

 public static void _testDateFormatting() { SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT")); SimpleDateFormat sdfGMT2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z"); sdfGMT2.setTimeZone(TimeZone.getTimeZone("GMT")); SimpleDateFormat sdfLocal1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); SimpleDateFormat sdfLocal2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z"); try { Date d = new Date(); String s1 = d.toString(); String s2 = sdfLocal1.format(d); // Store s3 or s4 in database. String s3 = sdfGMT1.format(d); String s4 = sdfGMT2.format(d); // Retrieve s3 or s4 from database, using LOCAL sdf. String s5 = sdfLocal1.parse(s3).toString(); //EXCEPTION String s6 = sdfLocal2.parse(s3).toString(); String s7 = sdfLocal1.parse(s4).toString(); String s8 = sdfLocal2.parse(s4).toString(); // Retrieve s3 from database, using GMT sdf. // Note that this is the SAME sdf that created s3. Date d2 = sdfGMT1.parse(s3); String s9 = d2.toString(); String s10 = sdfGMT1.format(d2); String s11 = sdfLocal2.format(d2); } catch (Exception e) { e.printStackTrace(); } } 

examinar valores en un depurador:

 s1 "Mon Sep 07 06:11:53 EDT 2015" (id=831698113128) s2 "2015.09.07 06:11:53" (id=831698114048) s3 "2015.09.07 10:11:53" (id=831698114968) s4 "2015.09.07 10:11:53 GMT+00:00" (id=831698116112) s5 "Mon Sep 07 10:11:53 EDT 2015" (id=831698116944) s6 -- omitted, gave parse exception s7 "Mon Sep 07 10:11:53 EDT 2015" (id=831698118680) s8 "Mon Sep 07 06:11:53 EDT 2015" (id=831698119584) s9 "Mon Sep 07 06:11:53 EDT 2015" (id=831698120392) s10 "2015.09.07 10:11:53" (id=831698121312) s11 "2015.09.07 06:11:53 EDT" (id=831698122256) 

sdf2 y sdfLocal2 incluyen zona horaria, por lo que podemos ver lo que realmente está sucediendo. s1 y s2 son a las 06:11:53 en la zona EDT. s3 y s4 son a las 10:11:53 en la zona GMT, lo que equivale a la hora EDT original. Imaginemos que guardamos s3 o s4 en una base de datos, donde estamos usando GMT por coherencia, para que podamos tener tiempos desde cualquier parte del mundo, sin almacenar diferentes husos horarios.

s5 analiza la hora GMT, pero la trata como hora local. Entonces dice “10:11:53” – la hora GMT – pero piensa que son las 10:11:53 en hora local . No está bien.

s7 analiza la hora GMT, pero ignora la GMT en la cadena, por lo que todavía la trata como hora local.

s8 funciona, porque ahora incluimos GMT en la cadena, y el analizador de zona local lo usa para convertir de una zona horaria a otra.

Supongamos ahora que no desea almacenar la zona, desea poder analizar s3, pero mostrarla como hora local. La respuesta es analizar usando la misma zona horaria en la que se almacenó , por lo tanto, use el mismo sdf tal como fue creado en sdfGMT1. s9, s10, y s11 son todas representaciones del tiempo original. Todos son “correctos”. Es decir, d2 == d1. Entonces solo es una cuestión de cómo desea PANTALLA. Si desea visualizar lo que está almacenado en DB – hora GMT – entonces necesita formatearlo usando un sdf GMT. Ths es s10.

Así que aquí está la solución final, si no desea almacenar explícitamente con “GMT” en la cadena, y desea mostrar en formato GMT:

 public static void _testDateFormatting() { SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT")); try { Date d = new Date(); String s3 = sdfGMT1.format(d); // Store s3 in DB. // ... // Retrieve s3 from database, using GMT sdf. Date d2 = sdfGMT1.parse(s3); String s10 = sdfGMT1.format(d2); } catch (Exception e) { e.printStackTrace(); } } 

tl; dr

¿Cuál es la forma de recuperar un objeto Date para que siempre esté en GMT?

 Instant.now() 

Detalles

Está utilizando clases conflictivas antiguas de fecha y hora que ahora suplantan las clases de java.time.

Instant = UTC

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() ; // Current moment in UTC. 

ISO 8601

Para intercambiar estos datos como texto, use los formatos estándar ISO 8601 exclusivamente. Estos formatos están sensiblemente diseñados para ser inequívocos, fáciles de procesar por máquina y fáciles de leer en muchas culturas por personas.

Las clases java.time utilizan los formatos estándar de forma predeterminada al analizar y generar cadenas.

 String output = instant.toString() ; 

2017-01-23T12: 34: 56.123456789Z

Zona horaria

Si desea ver el mismo momento que se presentó en la hora del reloj de pared de una región en particular, aplique un ZoneId para obtener un ZonedDateTime .

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( "Asia/Singapore" ) ; ZonedDateTime zdt = instant.atZone( z ) ; // Same simultaneous moment, same point on the timeline. 

Vea este código en vivo en IdeOne.com .

Observe la diferencia de ocho horas, ya que la zona horaria de Asia/Singapore actualmente tiene un desplazamiento desde UTC de +08: 00. Mismo momento, diferente hora del reloj de pared.

instant.toString (): 2017-01-23T12: 34: 56.123456789Z

zdt.toString (): 2017-01-23T20: 34: 56.123456789 + 08: 00 [Asia / Singapur]

Convertir

Evite la clase heredada java.util.Date . Pero si debes hacerlo, puedes convertir. Mire los nuevos métodos agregados a las clases antiguas.

 java.util.Date date = Date.fromInstant( instant ) ; 

… yendo por el otro lado …

 Instant instant = myJavaUtilDate.toInstant() ; 

Solo fecha

Solo para fecha, use LocalDate .

 LocalDate ld = zdt.toLocalDate() ; 

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 .

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

  • Java SE 8 , Java SE 9 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
    • El proyecto ThreeTenABP adapta ThreeTen-Backport (mencionado anteriormente) específicamente para Android.
    • 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 .