¿Por qué restar estas dos veces (en 1927) da un resultado extraño?

Si ejecuto el siguiente progtwig, que analiza dos cadenas de fechas haciendo referencia a las horas de diferencia de 1 segundo y las compara:

public static void main(String[] args) throws ParseException { SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String str3 = "1927-12-31 23:54:07"; String str4 = "1927-12-31 23:54:08"; Date sDt3 = sf.parse(str3); Date sDt4 = sf.parse(str4); long ld3 = sDt3.getTime() /1000; long ld4 = sDt4.getTime() /1000; System.out.println(ld4-ld3); } 

El resultado es:

353

¿Por qué ld4-ld3 no es 1 (como esperaría de la diferencia de un segundo en los tiempos), sino 353 ?

Si cambio las fechas a veces 1 segundo después:

 String str3 = "1927-12-31 23:54:08"; String str4 = "1927-12-31 23:54:09"; 

Entonces ld4-ld3 será 1 .


Versión de Java:

 java version "1.6.0_22" Java(TM) SE Runtime Environment (build 1.6.0_22-b04) Dynamic Code Evolution Client VM (build 0.2-b02-internal, 19.0-b04-internal, mixed mode) Timezone(`TimeZone.getDefault()`): sun.util.calendar.ZoneInfo[id="Asia/Shanghai", offset=28800000,dstSavings=0, useDaylight=false, transitions=19, lastRule=null] Locale(Locale.getDefault()): zh_CN 

Es un cambio de zona horaria el 31 de diciembre en Shanghai.

Consulte esta página para obtener detalles de 1927 en Shanghai. Básicamente, a la medianoche de finales de 1927, los relojes retrocedieron 5 minutos y 52 segundos. Así que “1927-12-31 23:54:08” en realidad sucedió dos veces, y parece que Java lo está analizando como el posible instante posterior para esa fecha / hora local, de ahí la diferencia.

Solo otro episodio en el mundo de las zonas horarias, a menudo extraño y maravilloso.

EDITAR: ¡ Deja de presionar! La historia cambia …

La pregunta original ya no mostraría el mismo comportamiento, si se reconstruye con la versión 2013a de TZDB . En 2013a, el resultado sería 358 segundos, con un tiempo de transición de 23:54:03 en lugar de 23:54:08.

Solo me di cuenta de esto porque estoy recostackndo preguntas como esta en Noda Time, en forma de pruebas unitarias … La prueba ahora ha cambiado, pero solo muestra, ni siquiera los datos históricos están seguros.

EDITAR: La historia ha cambiado nuevamente …

En TZDB 2014f, el momento del cambio se ha movido a 1900-12-31, y ahora es un mero cambio de 343 segundos (por lo que el tiempo entre t y t+1 es 344 segundos, si ves lo que quiero decir).

EDITAR: para responder a una pregunta sobre una transición en 1900 … parece que la implementación de la zona horaria Java trata todas las zonas horarias como simplemente estar en su hora estándar para cualquier instante antes del inicio de las 1900 UTC:

 import java.util.TimeZone; public class Test { public static void main(String[] args) throws Exception { long startOf1900Utc = -2208988800000L; for (String id : TimeZone.getAvailableIDs()) { TimeZone zone = TimeZone.getTimeZone(id); if (zone.getRawOffset() != zone.getOffset(startOf1900Utc - 1)) { System.out.println(id); } } } } 

El código anterior no produce salida en mi máquina con Windows. Por lo tanto, cualquier zona horaria que tenga un desplazamiento distinto al estándar a comienzos de 1900 lo contará como una transición. TZDB en sí tiene algunos datos que se remontan a épocas anteriores, y no confía en ninguna idea de un tiempo estándar “fijo” (que es lo que getRawOffset supone que es un concepto válido), por lo que otras bibliotecas no necesitan introducir esta transición artificial.

Has encontrado una discontinuidad horaria local :

Cuando el horario estándar local estaba a punto de llegar el domingo 1 de enero de 1928, 00:00:00 los relojes retrocedieron 0:05:52 horas hasta el sábado 31 de diciembre de 1927, a las 23:54:08 hora local estándar.

Esto no es particularmente extraño y ha sucedido prácticamente en todas partes en un momento u otro ya que las zonas horarias se cambiaron o cambiaron debido a acciones políticas o administrativas.

La moraleja de esta extrañeza es:

  • Use fechas y horas en UTC siempre que sea posible.
  • Si no puede mostrar una fecha u hora en UTC, siempre indique la zona horaria.
  • Si no puede requerir una fecha / hora de entrada en UTC, solicite una zona horaria explícitamente indicada.

Al incrementar el tiempo, debe volver a convertir a UTC y luego sumr o restar. Use la hora local solo para mostrar.

De esta manera podrá caminar por cualquier período donde las horas o los minutos suceden dos veces.

Si convirtió a UTC, agregue cada segundo y convierta a la hora local para mostrar. Pasarías a las 11:54:08 p.m. LMT – 11:59:59 p.m. LMT y luego a las 11:54:08 p.m. CST – 11:59:59 p.m. CST.

En lugar de convertir cada fecha, usa el siguiente código

 long difference = (sDt4.getTime() - sDt3.getTime()) / 1000; System.out.println(difference); 

Y mira el resultado es:

 1 

Lamento decir eso, pero la discontinuidad de tiempo se ha movido un poco en

JDK 6 hace dos años, y en JDK 7 recientemente en la actualización 25 .

Lección para aprender: evite los tiempos no UTC a toda costa, excepto, tal vez, para mostrarlos.

Como lo explicaron otros, hay una discontinuidad temporal allí. Hay dos posibles desfases de zona horaria para 1927-12-31 23:54:08 en Asia/Shanghai , pero solo una compensación para 1927-12-31 23:54:07 . Entonces, dependiendo de la compensación que se use, hay una diferencia de un segundo o una diferencia de 5 minutos y 53 segundos.

Este ligero desplazamiento de compensaciones, en lugar del ahorro habitual de una hora (horario de verano) al que estamos acostumbrados, oscurece un poco el problema.

Tenga en cuenta que la actualización 2013a de la base de datos de la zona horaria movió esta discontinuidad unos segundos antes, pero el efecto aún sería observable.

El nuevo paquete java.time en Java 8 permite ver esto más claramente y proporciona herramientas para manejarlo. Dado:

 DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder(); dtfb.append(DateTimeFormatter.ISO_LOCAL_DATE); dtfb.appendLiteral(' '); dtfb.append(DateTimeFormatter.ISO_LOCAL_TIME); DateTimeFormatter dtf = dtfb.toFormatter(); ZoneId shanghai = ZoneId.of("Asia/Shanghai"); String str3 = "1927-12-31 23:54:07"; String str4 = "1927-12-31 23:54:08"; ZonedDateTime zdt3 = LocalDateTime.parse(str3, dtf).atZone(shanghai); ZonedDateTime zdt4 = LocalDateTime.parse(str4, dtf).atZone(shanghai); Duration durationAtEarlierOffset = Duration.between(zdt3.withEarlierOffsetAtOverlap(), zdt4.withEarlierOffsetAtOverlap()); Duration durationAtLaterOffset = Duration.between(zdt3.withLaterOffsetAtOverlap(), zdt4.withLaterOffsetAtOverlap()); 

Entonces durationAtEarlierOffset durará un segundo, mientras que durationAtLaterOffset durará cinco minutos y 53 segundos.

Además, estos dos desplazamientos son los mismos:

 // Both have offsets +08:05:52 ZoneOffset zo3Earlier = zdt3.withEarlierOffsetAtOverlap().getOffset(); ZoneOffset zo3Later = zdt3.withLaterOffsetAtOverlap().getOffset(); 

Pero estos dos son diferentes:

 // +08:05:52 ZoneOffset zo4Earlier = zdt4.withEarlierOffsetAtOverlap().getOffset(); // +08:00 ZoneOffset zo4Later = zdt4.withLaterOffsetAtOverlap().getOffset(); 

Puede ver el mismo problema comparando 1927-12-31 23:59:59 con 1928-01-01 00:00:00 , sin embargo, en este caso, es el desplazamiento anterior el que produce la divergencia más larga, y es el fecha anterior que tiene dos compensaciones posibles.

Otra forma de abordar esto es verificar si hay una transición en curso. Podemos hacer esto así:

 // Null ZoneOffsetTransition zot3 = shanghai.getRules().getTransition(ld3.toLocalDateTime); // An overlap transition ZoneOffsetTransition zot4 = shanghai.getRules().getTransition(ld3.toLocalDateTime); 

Puede verificar si la transición es una superposición, en cuyo caso hay más de un desplazamiento válido para esa fecha / hora, o un espacio, en cuyo caso esa fecha / hora no es válida para esa identificación de zona, utilizando isOverlap() y métodos isGap() en zot4 .

Espero que esto ayude a las personas a manejar este tipo de problemas una vez que Java 8 esté ampliamente disponible, o para aquellos que usan Java 7 y adoptan el backport JSR 310.

En mi humilde opinión, la localización omnipresente e implícita en Java es su mayor defecto de diseño. Puede estar destinado a interfaces de usuario, pero, francamente, quién realmente usa Java para las interfaces de usuario de hoy excepto por algunos IDEs donde básicamente se puede ignorar la localización porque los progtwigdores no son exactamente el público objective para ello. Puede solucionarlo (especialmente en servidores Linux) de la siguiente manera:

  • exportar LC_ALL = C TZ = UTC
  • configura el reloj de tu sistema en UTC
  • nunca utilice implementaciones localizadas a menos que sea absolutamente necesario (es decir, solo para visualización)

A los miembros de Java Community Process les recomiendo:

  • hacer que los métodos localizados no sean los predeterminados, pero requieren que el usuario solicite explícitamente la localización.
  • use UTF-8 / UTC como el valor predeterminado fijo en su lugar porque es simplemente el valor predeterminado hoy. No hay ninguna razón para hacer otra cosa, excepto si desea producir hilos como este.

Quiero decir, vamos, ¿las variables estáticas globales no son un patrón anti-OO? Nada más es esos incumplimientos omnipresentes dados por algunas variables de entorno rudimentarias …….