java timezone - Perché sottrarre queste due volte (nel 1927) dando uno strano risultato?




4 Answers

È un cambio di fuso orario il 31 dicembre a Shanghai.

Vedi questa pagina per i dettagli del 1927 a Shanghai. Fondamentalmente a mezzanotte alla fine del 1927, gli orologi tornarono indietro di 5 minuti e 52 secondi. Quindi "1927-12-31 23:54:08" in realtà è successo due volte, e sembra che Java la stia analizzando come l'istante più tardi possibile per quella data / ora locale, quindi la differenza.

Solo un altro episodio nel mondo spesso strano e meraviglioso dei fusi orari.

MODIFICA: basta premere! La storia cambia ...

La domanda originale non avrebbe più dimostrato lo stesso comportamento, se ricostruita con la versione 2013a di TZDB . Nel 2013a, il risultato sarebbe 358 secondi, con un tempo di transizione di 23:54:03 anziché di 23:54:08.

L'ho notato solo perché sto raccogliendo domande come questa in Noda Time, sotto forma di TZDB ... Il test ora è stato modificato, ma va solo a dimostrare - nemmeno i dati storici sono al sicuro.

EDIT: la storia è cambiata di nuovo ...

Nel TZDB 2014f, il tempo del cambiamento è passato a 1900-12-31, e ora è un semplice cambiamento di 343 secondi (quindi il tempo tra t e t+1 è 344 secondi, se vedi cosa intendo).

EDIT: per rispondere a una domanda intorno a una transizione a 1900 ... sembra che l'implementazione del fuso orario Java consideri tutti i fusi orari semplicemente nel loro tempo standard per qualsiasi istante prima dell'inizio di UTC 1900:

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);
            }
        }
    }
}

Il codice sopra non produce output sulla mia macchina Windows. Quindi qualsiasi fuso orario che abbia un offset diverso da quello standard all'inizio del 1900, verrà considerato come una transizione. Lo stesso TZDB ha alcuni dati che risalgono a prima, e non si basa su alcuna idea di un tempo standard "fisso" (che è quello che getRawOffset assume come concetto valido), quindi altre librerie non hanno bisogno di introdurre questa transizione artificiale.

convert date

Se eseguo il seguente programma, che analizza due stringhe di data che fanno riferimento a tempi di 1 secondo e li confronta:

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);
}

L'output è:

353

Perché ld4-ld3 non è 1 (come mi aspetterei dalla differenza di un secondo nei tempi), ma 353 ?

Se cambio le date a volte 1 secondo dopo:

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

Quindi ld4-ld3 sarà 1 .

Versione 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



La morale di questa stranezza è:

  • Utilizza le date e le ore in UTC ovunque sia possibile.
  • Se non è possibile visualizzare una data o un'ora in UTC, indicare sempre il fuso orario.
  • Se non è possibile richiedere una data / ora di input in UTC, è necessario un fuso orario esplicitamente indicato.



Invece di convertire ogni data, si utilizza il seguente codice

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

E vedi il risultato è:

1



Come spiegato da altri, c'è una discontinuità nel tempo lì. Ci sono due possibili offset del fuso orario per 1927-12-31 23:54:08 in Asia/Shanghai , ma solo uno per 1927-12-31 23:54:07 . Quindi, a seconda dell'offset utilizzato, c'è una differenza di un secondo o una differenza di 5 minuti e 53 secondi.

Questo leggero spostamento di offset, anziché la solita ora legale di un'ora (periodo estivo) a cui siamo abituati, oscura un po 'il problema.

Si noti che l'aggiornamento 2013a del database del fuso orario ha spostato questa discontinuità pochi secondi prima, ma l'effetto sarebbe ancora osservabile.

Il nuovo pacchetto java.time su Java 8 consente di vederlo più chiaramente e fornisce strumenti per gestirlo. Dato:

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());

Quindi durationAtEarlierOffset sarà un secondo, mentre durationAtLaterOffset sarà di cinque minuti e 53 secondi.

Inoltre, questi due offset sono gli stessi:

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

Ma questi due sono diversi:

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

// +08:00
ZoneOffset zo4Later = zdt4.withLaterOffsetAtOverlap().getOffset();

Puoi vedere lo stesso problema confrontando 1927-12-31 23:59:59 con 1928-01-01 00:00:00 , anche se, in questo caso, è lo 1928-01-01 00:00:00 precedente che produce la divergenza più lunga, ed è il data precedente che ha due possibili offset.

Un altro modo per avvicinarsi a questo è controllare se c'è una transizione in corso. Possiamo farlo in questo modo:

// Null
ZoneOffsetTransition zot3 = shanghai.getRules().getTransition(ld3.toLocalDateTime);

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

Puoi verificare se la transizione è una sovrapposizione - nel qual caso c'è più di un offset valido per quella data / ora - o un divario - nel qual caso quella data / ora non è valida per isOverlap() della zona - usando isOverlap() e metodi isGap() su zot4 .

Spero che questo aiuti le persone a gestire questo tipo di problema una volta che Java 8 diventa ampiamente disponibile, oa coloro che utilizzano Java 7 che adotta il backport JSR 310.




Related

java date timezone