viento libre
Si ejecuto el siguiente programa, que analiza dos cadenas de fecha que hacen referencia a tiempos separados por 1 segundo 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);
}
La salida es:
353
Por que es ld4-ld3
no 1
(como era de esperar de la diferencia de un segundo en los tiempos), pero 353
?
Si cambio las fechas a horas 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 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
jon skeet
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 más tarde posible instante 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.
Si se reconstruye con la versión 2013a de TZDB, La pregunta original ya no demostraría exactamente el mismo comportamiento. 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 se ha cambiado, pero solo muestra que ni siquiera los datos históricos son seguros.
En TZDB 2014fla hora 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
son 344 segundos, si entiendes a lo que me refiero).
Para responder una pregunta sobre una transición a las 1900… parece que la implementación de la zona horaria de Java trata todo zonas horarias como simplemente estar 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 ningún resultado en mi máquina con Windows. Por lo tanto, cualquier zona horaria que tenga un desplazamiento que no sea el estándar a principios de 1900 contará como una transición. TZDB en sí tiene algunos datos que se remontan a antes de 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.
Te has encontrado con un discontinuidad de la hora local:
Cuando la hora estándar local estaba a punto de llegar el domingo, 1 de enero de 1928, 00:00:00, los relojes se retrocedieron 0:05:52 horas hasta el sábado, 31 de diciembre de 1927, 23:54:08, hora estándar local.
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.
-
Ocurre dos veces al año en cualquier lugar que observe el horario de verano.
– uckelman
27 de noviembre de 2020 a las 22:57
-
Este no es horario de verano, creo. Son solo 10 minutos atrás, y solo una vez. Al mismo tiempo, los cambios relacionados con el horario de verano pueden ocurrir dos veces al año… o 4 veces al año (debido al Ramadán). o incluso una vez al año en algunas configuraciones. No hay regla ahí 🙂
– iwat0qs
27 de marzo de 2022 a las 10:29
-
Por lo general, no sucede así con el horario de verano en estos días, porque los cambios de reloj se realizan a la 1 a. m. o a las 2 a.
-Henry Brice
10 abr a las 17:18
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.
-
Ninguno de estos puntos afectaría este resultado (se incluye directamente en el 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 Ågren
2 de noviembre de 2020 a las 16:11
patricio
Al incrementar el tiempo, debe volver a convertir a UTC y luego sumar o restar. Use la hora local solo para la visualización.
De esta manera, podrá caminar a través de cualquier período en el que las horas o los minutos sucedan dos veces.
Si convirtió a UTC, agregue cada segundo y convierta a la hora local para mostrar. Pasarías a las 11:54:08 pm LMT – 23:59:59 LMT y luego 23:54:08 CST – 23:59:59 CST.
Rajshri
En lugar de convertir cada fecha, puede usar el siguiente código:
long difference = (sDt4.getTime() - sDt3.getTime()) / 1000;
System.out.println(difference);
Y luego ver que el resultado es:
1
davnicwil
Lamento decirlo, pero la discontinuidad del tiempo se ha movido un poco
JDK6 hace dos años y en JDK 7 recientemente en actualizar 25.
Lección para aprender: evite las horas no UTC a toda costa, excepto tal vez para la visualización.
nuevo QOpenGLWidget
Como explicaron otros, hay una discontinuidad de tiempo allí. Hay dos posibles compensaciones de zona horaria para 1927-12-31 23:54:08
en Asia/Shanghai
pero sólo una compensación para 1927-12-31 23:54:07
. Entonces, según el desplazamiento 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 de una hora al que estamos acostumbrados, oscurece un poco el problema.
Tenga en cuenta que la actualización de 2013a de la base de datos de zonas horarias movió esta discontinuidad unos segundos antes, pero el efecto aún sería observable.
El nuevo java.time
El paquete 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
será un segundo, mientras durationAtLaterOffset
serán cinco minutos y 53 segundos.
Además, estos dos desplazamientos son iguales:
// 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:00
aunque, en este caso, es el desplazamiento anterior el que produce la divergencia más larga, y es la fecha anterior la que tiene dos desplazamientos posibles.
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 una brecha donde esa fecha/hora no es válida para esa identificación de zona, usando 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.
La respuesta real es siempre, siempre usar segundos desde una época para el registro, como la época de Unix, con representación de enteros de 64 bits (firmados, si desea permitir sellos antes de la época). Cualquier sistema de tiempo del mundo real tiene un comportamiento no lineal, no monótono, como las horas bisiestas o el horario de verano.
– Phil H.
12 de julio de 2012 a las 8:34
Un gran video sobre este tipo de cosas: youtube.com/watch?v=-5wpm-gesOY
– Thorbjorn Ravn Andersen
14/10/2014 a las 10:39
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).
– TRIG
3 de julio de 2015 a las 16:23
@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 longitud
– Recuerda a Mónica
13 de septiembre de 2020 a las 23:57
Los segundos de POSIX pueden variar en longitud, pero el paso a paso también es una opción de implementación permisible y común. Es decir, la diferencia entre las marcas de tiempo separadas por un segundo puede ser cero cuando se agrega un segundo intercalar, o negativa si las marcas de tiempo están separadas por menos de un segundo. Entonces, el tiempo de Unix tiene un comportamiento no monótono como otros sistemas de tiempo del mundo real, y no es una panacea.
– erickson
4 mayo 2022 a las 20:22