example - zonetime java




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

Si ejecuto el siguiente programa, que analiza dos cadenas de fecha que hacen referencia a intervalos 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);
}

La salida es:

353

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

Si cambio las fechas a tiempos 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

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 a través de los períodos donde las horas o los minutos pasan dos veces.

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


Como explicaron otros, hay una discontinuidad temporal allí. Hay dos posibles compensaciones 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 cambio de compensaciones, en lugar del habitual horario de verano (horario de verano) al que estamos acostumbrados, oculta un poco el problema.

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

El nuevo paquete java.time en Java 8 le 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());

Luego durationAtEarlierOffset será de un segundo, mientras durationAtLaterOffset será de 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();

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 posibles compensaciones.

Otra forma de abordar esto es verificar si hay una transición en marcha. 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 intervalo, en cuyo caso esa fecha / hora no es válida para esa identificación de zona, utilizando isOverlap() y los métodos isGap() en zot4 .

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


En mi opinión, la localización generalizada e implícita en Java es su principal defecto de diseño. Puede estar destinado a interfaces de usuario, pero francamente, que realmente utiliza Java para las interfaces de usuario hoy en día, excepto en algunos IDE donde básicamente puede ignorar la localización porque los programadores no son exactamente el público objetivo. Puedes arreglarlo (especialmente en servidores Linux) por:

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

A los miembros del Proceso de la Comunidad Java les recomiendo:

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

Quiero decir, vamos, ¿no son las variables estáticas globales un patrón anti-OO? Nada más son los valores predeterminados generalizados dados por algunas variables de entorno rudimentarias .......


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

Vea esta página para detalles de 1927 en Shanghai. Básicamente, a medianoche de fines 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 último momento posible para esa fecha / hora local, de ahí la diferencia.

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

EDITAR: dejar de presionar! La historia cambia ...

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 TZDB ... La prueba ahora se ha cambiado, pero simplemente muestra 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 solo un cambio de 343 segundos (por lo que el tiempo entre t y t+1 es de 344 segundos, si ve lo que quiero decir).

EDITAR: Para responder una pregunta sobre una transición en 1900 ... parece que la implementación de la zona horaria de Java trata a todas las zonas horarias como si estuvieran 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 al comienzo de 1900 contará eso como una transición. La propia TZDB tiene algunos datos anteriores a eso, y no se basa en ninguna idea de un tiempo estándar "fijo" (que es lo que getRawOffset asume como un concepto válido), por lo que otras bibliotecas no necesitan introducir esta transición artificial.


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, siempre indique la zona horaria.
  • Si no puede requerir una fecha / hora de entrada en UTC, requiera una zona horaria explícitamente indicada.

Lamento decir eso, pero la discontinuidad del 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 la visualización.







timezone