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

Si ejecuto el siguiente programa, que analiza dos cadenas de fechas que hacen referencia a tiempos de 1 segundo de diferencia y los 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é es ld4-ld3no, 1 (como esperaría de la diferencia de un segundo en los tiempos), pero 353?

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

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

Entonces ld4-ld3 se mostrará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

preguntado el 27 de julio de 11 a las 05:07

Esto puede deberse a un problema de configuración regional. -

La respuesta real es siempre, siempre usar segundos desde una época para el registro, como la época de Unix, con representación entera de 64 bits (firmada, si desea permitir sellos antes de la época). Cualquier sistema de tiempo del mundo real tiene un comportamiento no lineal y no monótono, como las horas bisiestos o el horario de verano. -

Un gran video sobre este tipo de cosas: youtube.com/watch?v=-5wpm-gesOY -

Y otro del mismo tipo, @ ThorbjørnRavnAndersen: youtube.com/watch?v=Uqjg8Kk1HXo (Segundos bisiestos). (Este es del propio canal de YouTube de Tom Scott, no de Computerphile).

@Phil H "segundos desde la época" (es decir, tiempo Unix) tampoco es lineal, en el sentido de que los segundos POSIX no son segundos SI y varían en duración -

10 Respuestas

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

Ver esta página para detalles de 1927 en Shanghai. Básicamente, a la medianoche de finales de 1927, los relojes retrocedieron 5 minutos y 52 segundos. Entonces, "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 a menudo extraño y maravilloso de las zonas horarias.

EDIT: ¡Deja de presionar! Cambios en el historial ...

La pregunta original ya no demostrarí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 me di cuenta de esto porque estoy recopilando preguntas como esta en Noda Time, en forma de pruebas unitarias... La prueba ahora se ha cambiado, pero simplemente demuestra que ni siquiera los datos históricos son seguros.

EDIT: La historia ha cambiado de nuevo ...

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

EDIT: Para responder una pregunta sobre una transición en 1900 ... parece que la implementación de la zona horaria de Java trata todos las las zonas horarias simplemente están en su hora estándar para cualquier instante antes del comienzo 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 diferente al estándar a principios de 1900 lo contará como una transición. TZDB en sí tiene algunos datos que se remontan antes que eso, y no se basa en ninguna idea de un tiempo estándar "fijo" (que es lo que getRawOffset asume que es un concepto válido) por lo que otras bibliotecas no necesitan introducir esta transición artificial.

Respondido 07 Feb 19, 09:02

Te has encontrado con un discontinuidad de la hora local:

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

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

Respondido 27 Jul 11, 09:07

Ocurre dos veces al año en cualquier lugar que observe el horario de verano. - uckelman

La moraleja de esta extrañeza es:

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

Respondido 28 Jul 11, 12:07

Ninguno de estos puntos afectaría este resultado (cae directamente debajo del tercer punto) y, además, este es un tiempo varias décadas antes de que se definiera UTC y, por lo tanto, no se puede expresar de manera significativa en UTC. - dag agren

Si bien su comentario es técnicamente correcto (¡el mejor tipo de correcto!), La hora media de Greenwich existe desde hace mucho tiempo antes de la invención del acrónimo UTC. - Señor lister

Al incrementar el tiempo, debe volver a convertir a UTC y luego sumar o restar. Utilice la hora local solo para la visualización.

De esta manera, podrá recorrer cualquier período en el que las horas o los minutos sucedan dos veces.

Si convirtió a UTC, agregue cada segundo y conviértalo a la hora local para su visualización. Pasarías por las 11:54:08 pm LMT - 11:59:59 pm LMT y luego 11:54:08 pm CST - 11:59:59 pm CST.

Respondido el 27 de diciembre de 11 a las 20:12

En lugar de convertir cada fecha, puede utilizar el siguiente código:

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

Y luego mira que el resultado es:

1

Respondido el 24 de diciembre de 19 a las 15:12

Lamento decirlo, pero la discontinuidad del tiempo se ha movido un poco en

JDK6 hace dos años, y en JDK7 recientemente en actualización 25.

Lección para aprender: evite las horas que no sean UTC a toda costa, excepto tal vez para la visualización.

Respondido el 17 de diciembre de 18 a las 00:12

Como explicaron otros, hay una discontinuidad de tiempo allí. Hay dos posibles desplazamientos de zona horaria para 1927-12-31 23:54:08 at Asia/Shanghai, pero solo un desplazamiento para 1927-12-31 23:54:07. Entonces, dependiendo de qué compensación se use, hay una diferencia de un segundo o una diferencia de 5 minutos y 53 segundos.

Este ligero cambio de compensaciones, en lugar del horario de verano habitual al que estamos acostumbrados, oscurece un poco el problema.

Tenga en cuenta que la actualización de 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 paquete en Java 8 permite que el uso vea esto más claramente y proporcione 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, estas dos compensaciones son las mismas:

// 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 anterior la que tiene dos posibles desplazamientos.

Otra forma de abordar esto es comprobar si se está produciendo una transición. 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 donde hay más de un desplazamiento válido para esa fecha / hora o un intervalo donde esa fecha / hora no es válida para esa identificación 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.

Respondido el 24 de diciembre de 19 a las 00:12

IMHO el omnipresente, implícitamente la localización 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 hoy en día, excepto para algunos IDE en los que básicamente puede ignorar la localización porque los programadores no son exactamente el público objetivo? Puede solucionarlo (especialmente en servidores Linux) de la siguiente manera:

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

Al Proceso de la comunidad Java miembros recomiendo:

  • hacen métodos localizados, no los predeterminados, pero requieren que el usuario solicite explícitamente la localización.
  • utilizan el 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 son esos omnipresentes valores predeterminados dados por algunas variables de entorno rudimentarias .......

Respondido 20 Oct 20, 04:10

Como dijeron otros, es un cambio de hora en 1927 en Shanghai.

Era 23:54:07 en Shanghai, en la hora estándar local, pero luego de 5 minutos y 52 segundos, pasó al día siguiente a las 00:00:00, y luego la hora estándar local cambió de nuevo a 23:54:08. Entonces, es por eso que la diferencia entre los dos tiempos es de 343 segundos, no de 1 segundo, como hubiera esperado.

El tiempo también puede estropearse en otros lugares como Estados Unidos. Estados Unidos tiene el horario de verano. Cuando comienza el horario de verano, el tiempo avanza 1 hora. Pero después de un tiempo, el horario de verano finaliza y retrocede 1 hora hasta la zona horaria estándar. Entonces, a veces, cuando se comparan los tiempos en los EE. UU., La diferencia es de aproximadamente 3600 segundos, no 1 segundo.

Pero hay algo diferente en estos cambios de dos tiempos. El último cambia continuamente y el primero fue solo un cambio. No volvió a cambiar ni volvió a cambiar en la misma cantidad.

Es mejor usar UTC a menos que sea necesario usar una hora que no sea UTC, como en la pantalla.

respondido 15 nov., 20:01

Para evitar ese problema, al incrementar el tiempo, debe volver a convertir a UTC y luego sumar o restar.

De esta manera, podrá recorrer cualquier período en el que las horas o los minutos sucedan dos veces.

Si convirtió a UTC, agregue cada segundo y conviértalo a la hora local para mostrarlo. Pasaría por 11:54:08 pm LMT - 11:59:59 pm LMT y luego 11:54:08 pm CST - 11:59:59 pm CST.

Respondido el 22 de diciembre de 20 a las 12:12

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.