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


Si ejecuto el siguiente programa, 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 que es ld4-ld3 no 1 (como esperaría de la diferencia de un segundo en los tiempos), pero 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 estarán 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

6015
2017-07-27 08:15


origen


Respuestas:


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

Ver esta página para los 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 luego posible instante 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 reconstruyera 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 noté esto porque estoy recopilando preguntas como esta en Noda Time, en forma de pruebas unitarias... La prueba ahora ha sido modificada, pero solo muestra, ni siquiera los datos históricos están seguros.

EDITAR: La historia ha cambiado otra vez ...

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 simplemente están 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 más temprano que eso, y no se basa 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.


9719
2017-07-27 08:31



Has encontrado un discontinuidad de tiempo 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.   Diciembre de 1927, 23:54:08 hora local estándar en su lugar

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.


1462
2017-07-27 08:38



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.

579
2017-07-28 11:50



Al incrementar el tiempo, debe volver a convertir a UTC y luego sumar 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 por 11:54:08 p.m. LMT - 11:59:59 p.m. LMT y luego 11:54:08 p.m. CST - 11:59:59 p.m. CST.


319
2017-07-30 16:55



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

264
2018-05-16 05:31



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

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

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


187
2018-02-17 22:44



Como lo explicaron otros, hay una discontinuidad temporal allí. Hay dos posibles compensaciones de zona horaria para 1927-12-31 23:54:08 a Asia/Shanghai, pero solo una compensación por 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 java.time El paquete en Java 8 permite verlo de forma más clara 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 será un segundo, mientras durationAtLaterOffset serán 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();

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

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 un solapamiento, 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 ese ID de zona, utilizando el isOverlap() y isGap() métodos 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.


167
2018-01-03 14:43



En mi humilde opinión, el generalizado implícito la localización en Java es su defecto de diseño más grande. 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 programadores no son exactamente el público objetivo 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)

Al Proceso comunitario de Java miembros que recomiendo:

  • hacer que los métodos localizados no sean los predeterminados, pero requieren que el usuario solicite explícitamente la localización.
  • usa UTF-8 / UTC como el FIJO predeterminado en su lugar porque es simplemente el 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 .......


137
2017-11-26 15:58