with - java timezone




Perché sottrarre queste due volte(nel 1927) dando uno strano risultato? (6)

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

È 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.


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.


IMHO la pervasiva, implicita localizzazione in Java è il suo difetto di progettazione più grande. Può essere inteso per le interfacce utente, ma francamente, chi usa realmente Java per le interfacce utente oggi eccetto per alcuni IDE in cui si può sostanzialmente ignorare la localizzazione perché i programmatori non sono esattamente il target di riferimento. Puoi sistemarlo (specialmente sui server Linux):

  • esporta LC_ALL = C TZ = UTC
  • imposta il tuo orologio di sistema su UTC
  • non utilizzare mai implementazioni localizzate se non assolutamente necessario (ad es. solo per la visualizzazione)

Ai membri del processo della comunità Java consiglio:

  • rendere i metodi localizzati non predefiniti, ma richiedere all'utente di richiedere esplicitamente la localizzazione.
  • utilizzare UTF-8 / UTC come predefinito FIXED, perché è semplicemente l'impostazione predefinita oggi. Non c'è motivo di fare qualcos'altro, tranne se vuoi produrre discussioni come questa.

Voglio dire, dai, non sono variabili statiche globali un modello anti-OO? Nient'altro è quei default pervasivi dati da alcune variabili di ambiente rudimentali .......


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

Mi dispiace dirlo, ma la discontinuità nel tempo si è spostata un po '

JDK 6 due anni fa, e in JDK 7 solo recentemente nell'aggiornamento 25 .

Lezione per imparare: evita i tempi non UTC a tutti i costi, tranne, forse, per la visualizzazione.


Quando si incrementa il tempo, è necessario riconvertire in UTC e quindi aggiungere o sottrarre. Utilizza l'ora locale solo per la visualizzazione.

In questo modo sarai in grado di attraversare tutti i periodi in cui le ore o i minuti si verificano due volte.

Se hai convertito in UTC, aggiungi ogni secondo e converti in ora locale per la visualizzazione. Dovresti passare alle 11:54:08 pm LMT - 11:59:59 pm LMT e poi 11:54:08 CST - 11:59:59 pm CST.





timezone