Pytanie Dlaczego odejmowanie tych dwóch razy (w 1927) daje dziwny wynik?


Jeśli uruchomię następujący program, który analizuje dwa ciągi dat odnoszące się do odległości 1 sekundy od siebie i porównuje je:

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

Wynik to:

353

Dlaczego jest ld4-ld3 nie 1 (jak można oczekiwać od jednej sekundy różnicy w czasie), ale 353?

Jeśli zmienię daty na 1 sekundę później:

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

Następnie ld4-ld3 będzie 1.


Wersja 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

6025
2017-07-27 08:15


pochodzenie


Czy naprawdę natknąłeś się na tę dokładną sytuację w prawdziwym scenariuszu, czy też pytanie to miało być jedynie zagadką - po prostu dla zabawy? - Costi Ciudatu
@Costi Ciudatu: FWIW, z łatwością mógłbym sobie wyobrazić to powstanie w wyniku zmniejszenia większego błędu - tj. "Dlaczego te dwie daty różnią się od siebie o rok, a nie o rok?" - Brooks Moses
Prawdziwa odpowiedź brzmi: zawsze, zawsze używaj sekund od epoki do logowania, tak jak w przypadku uniksowej epoki, z 64-bitową reprezentacją całkowitą (podpisaną, jeśli chcesz zezwolić na znaczki przed epoką). Każdy system czasu rzeczywistego ma pewne nieliniowe, niemonotoniczne zachowanie, takie jak godziny przestępne lub oszczędności w świetle dziennym. - Phil H
pierwotnie opublikowany jako Oracle Bug ID 7070044 23 lipca 11 11 - Arno
Jako @Costi Ciudatu, naprawdę zastanawiam się nad tym, który raport o błędzie zrobił ten OP. Jestem również całkiem pewny, z wyjątkiem bycia zagadką, nie jest to przydatne do 99.9 (dodać dużo 9 cyfr) procent użytkowników. Dostaje medal za reputację "gra". - Menelaos Bakopoulos


Odpowiedzi:


Jest to zmiana strefy czasowej w dniu 31 grudnia w Szanghaju.

Widzieć ta strona po szczegóły z 1927 r. w Szanghaju. Zasadniczo o północy pod koniec 1927 roku zegary wróciły 5 minut i 52 sekundy. Tak więc "1927-12-31 23:54:08" faktycznie zdarzyło się dwa razy i wygląda na to, że Java analizuje to jako później możliwe natychmiastowe dla tej lokalnej daty / czasu - stąd różnica.

Kolejny odcinek w często dziwnym i cudownym świecie stref czasowych.

EDYTOWAĆ: Zatrzymaj prasę! Historia się zmienia ...

Pierwotne pytanie nie będzie już wykazywać tego samego zachowania, jeśli zostanie przebudowane w wersji 2013a TZDB. W 2013a wynik wyniesie 358 sekund, a czas przejścia 23:54:03 zamiast 23:54:08.

Zauważyłem to tylko dlatego, że zbieram takie pytania w czasie Noda, w formie testy jednostkowe... Test został zmieniony, ale po prostu się pokazuje - nawet historyczne dane nie są bezpieczne.

EDYTOWAĆ: Historia znów się zmieniła ...

W TZDB 2014f czas zmiany przesunął się do 1900-12-31, a teraz wynosi zaledwie 343 sekundy (więc czas pomiędzy t i t+1 to 344 sekundy, jeśli widzisz co mam na myśli).

EDYTOWAĆ: Aby odpowiedzieć na pytanie dotyczące przejścia w 1900 roku ... wygląda na to, że implementacja strefy czasowej Java traktuje wszystko strefy czasowe po prostu będąc w ich standardowym czasie przez dowolną chwilę przed początkiem 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);
            }
        }
    }
}

Powyższy kod nie generuje żadnych danych wyjściowych na moim komputerze z systemem Windows. Tak więc każda strefa czasowa, która ma jakiekolwiek przesunięcie inne niż standardowe na początku 1900, zlicza to jako przejście. Sam TZDB ma pewne dane cofające się wcześniej i nie opiera się na żadnej idei "ustalonego" czasu standardowego (co jest getRawOffset zakłada się, że jest to ważna koncepcja), więc inne biblioteki nie muszą wprowadzać tego sztucznego przejścia.


9729
2017-07-27 08:31



@Gareth: Nie, ale sprawdzanie szczegółów przejścia do strefy czasowej w Szanghaju w tym okresie było moim pierwszym portem zawinięcia. Ostatnio pracowałem nad przejściami w strefie czasowej dla Noda Time, więc możliwość niejednoznaczności jest w zasadzie na czele moich myśli ... - Jon Skeet
@Johannes: Uważam, że w celu uczynienia go bardziej globalnie normalną strefą czasową wynikły offset to UTC + 8. Paris zrobił to samo w 1911 na przykład: timeanddate.com/worldclock/clockchange.html?n=195&year=1911 - Jon Skeet
@Charles: wtedy podróżnicy wiedzieli, że czas lokalny jest inny wszędzie (ponieważ był). Dodatkowo zegarki były mechaniczne i szybko dryfowały, więc ludzie, których używaliśmy, dostosowywali je zgodnie z lokalną wieżą zegarkową co kilka dni, nawet jeśli nie podróżowali. Jak więc ustawiono zegary wieżowe (które również dryfowały)? Najłatwiej, ustawiając je na 12:00, kiedy słońce osiągnęło szczyt dzienny ... który różnił się w każdym miejscu nie na tej samej długości geograficznej. Było to normą niemal wszędzie, dopóki rozkłady jazdy na kolei nie wymagały jakiejś standaryzacji. - Michael Borgwardt
Ale potem: jak na Ziemi ta wiedza przetrwała wieki, tak że prawie sto lat temu została zaimplementowana w oprogramowaniu? W 2011 r. Każdy, kto wspomniał o dziwacznych cechach czasowych inżynierowi niebędącemu programistom, był traktowany jak nerd. (I tak naprawdę, ludzie oczekują, że całe oprogramowanie je zredukuje, i nie obchodzi ich, czy to jest niejednoznaczne, kiedy mówią "południe", my inżynier oprogramowania powinien sobie z tym poradzić). Ale żeby wyobrazić sobie kogoś z Shangai, w grudniu 1927 roku, myśląc, że istotne byłoby zanotowanie czegoś takiego, i że jakoś ta informacja nigdy nie została utracona, usunięta, cokolwiek ... umysł został zdmuchnięty. - phtrivier
@EdS. W rzeczywistości zajęło mu to tylko 15 minut, a to dlatego, że pokazuje 16-minutową różnicę wynikającą z niewielkiej rozbieżności strefy czasowej w dniu 27 lipca 2011 r., Spowodowanej przez niesamowity Jon Skeet. - JessieArr


Napotkaliście nieciągłość czasu lokalnego:

Gdy lokalny czas standardowy miał dotrzeć do niedzieli, 1 stycznia 1928 r.,   00:00:00 zegary zostały odwrócone 0:05:52 godziny do soboty, 31.   W grudniu 1927, 23:54:08 zamiast lokalnego czasu lokalnego

Nie jest to szczególnie dziwne i zdarzyło się prawie wszędzie w tym samym czasie, gdy strefy czasowe zostały zmienione lub zmienione z powodu działań politycznych lub administracyjnych.


1463
2017-07-27 08:38



@Jason: Jeśli chodzi o czytanie na dobranoc, sugerowałbym (teraz) bazę danych stref czasowych IANA (wcześniej administrowana przez uroczego Olsona, jak sądzę), który byłby świetnym źródłem informacji: iana.org/time-zones. O ile mi wiadomo, większość światów otwartego oprogramowania (tak wspomniane biblioteki) używają tego jako głównego źródła danych strefy czasowej. - Sune Rasmussen


Morał tej dziwności to:

  • W miarę możliwości używaj dat i godzin w UTC.
  • Jeśli nie możesz wyświetlić daty ani czasu w UTC, zawsze wskaż strefę czasową.
  • Jeśli nie możesz wymagać wprowadzenia daty / czasu w UTC, wymagana jest wyraźna wskazana strefa czasowa.

580
2017-07-28 11:50



Konwersja / przechowywanie w UTC naprawdę nie pomogłoby w opisywanym problemie, ponieważ napotkasz na nieciągłość konwersji na UTC. - unpythonic
@ Mark Mann: jeśli twój program używa wszędzie UTC wewnętrznie wszędzie, konwersja do / z lokalnej strefy czasowej tylko w interfejsie użytkownika, nie opieka o takich nieciągłościach. - Raedwald
@Raedwald: Pewnie, że tak - Jaki jest czas UTC dla 1927-12-31 23:54:08? (Pomijając, na razie, że UTC nawet nie istniało w 1927 r.). W trochę punkt ten czas i data wchodzą do twojego systemu i musisz zdecydować, co z nim zrobić. Powiedzenie użytkownikowi, że musi wprowadzić czas w UTC, po prostu przenosi problem na użytkownika, nie eliminuje go. - Nick Bastin
Czuję się usatysfakcjonowany ilością działań w tym wątku, pracując nad refinansowaniem daty / czasu dużej aplikacji przez prawie rok. Jeśli robisz coś takiego jak kalendarze, nie możesz "zwyczajnie" przechowywać UTC, ponieważ definicje stref czasowych, w których może być renderowana, będą się zmieniać z czasem. Przechowujemy "czas intencji użytkownika" - czas lokalny użytkownika i jego strefę czasową - oraz czas UTC do wyszukiwania i sortowania, a za każdym razem, gdy baza danych IANA jest aktualizowana, przeliczamy wszystkie czasy UTC. - taiganaut


Przy zwiększaniu czasu powinieneś przekonwertować z powrotem na UTC, a następnie dodać lub odjąć. Użyj czasu lokalnego tylko do wyświetlenia.

W ten sposób będziesz mógł przechodzić przez wszystkie okresy, w których godziny lub minuty zdarzają się dwa razy.

Po przekonwertowaniu na UTC dodaj każdą sekundę i zamień na czas lokalny w celu wyświetlenia. Przejdziesz o 11:54:08 po południu. LMT - 11:59:59 pm LMT, a następnie 11:54:08 po południu CST - 11:59:59 pm CST.


320
2017-07-30 16:55





Zamiast konwertować każdą datę, używasz następującego kodu

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

Zobacz rezultat:

1

265
2018-05-16 05:31



Obawiam się, że tak nie jest. Możesz wypróbować mój kod w swoim systemie, będzie on wyprowadzany 1, ponieważ mamy różne lokalizacje. - Freewind
To prawda, ponieważ nie określiłeś ustawień regionalnych w danych wejściowych analizatora składni. To zły styl kodowania i ogromna wada projektowa w Javie - jego nieodłączna lokalizacja. Osobiście umieszczam "TZ = UTC LC_ALL = C" wszędzie, gdzie używam języka Java, aby tego uniknąć. Ponadto należy unikać każdej zlokalizowanej wersji implementacji, chyba że użytkownik bezpośrednio nawiązuje kontakt z użytkownikiem i wyraźnie go chce. NIE PODLEGAJ ŻADNYM obliczeniom, w tym lokalizacjom, zawsze używaj stref czasowych Locale.ROOT i UTC, chyba że jest to absolutnie konieczne. - user1050755


Przykro mi to mówić, ale czas nieciągłości nieco się przesunął

JDK 6 dwa lata temu i w JDK 7 niedawno w aktualizacja 25.

Lekcja do nauczenia: unikaj czasów nie-UTC za wszelką cenę, z wyjątkiem, być może, do wyświetlania.


188
2018-02-17 22:44



To jest niepoprawne. Nieciągłość nie jest błędem - po prostu nowsza wersja TZDB ma nieco inne dane. Na przykład na moim komputerze z językiem Java 8, jeśli zmienisz kod bardzo delikatnie, aby użyć "1927-12-31 23:54:02" i "1927-12-31 23:54:03", nadal będziesz widzieć nieciągłość - ale z 358 sekund, zamiast 353. Jeszcze nowsze wersje TZDB mają jeszcze inną różnicę - zobacz moją odpowiedź dla szczegółów. Nie ma tutaj żadnego prawdziwego błędu, tylko decyzja dotycząca projektowania, w jaki sposób analizowane są niejednoznaczne wartości tekstowe daty / czasu. - Jon Skeet
Prawdziwym problemem jest to, że programiści nie rozumieją, że konwersja pomiędzy czasem lokalnym i uniwersalnym (w obu kierunkach) nie jest i nie może być w 100% niezawodna. W przypadku starych sygnatur czasowych dane, które mamy na temat czasu lokalnego, są w najlepszym razie niestabilne. W przypadku przyszłych znaczników czasowych działania polityczne mogą zmienić czas uniwersalny, z jakim dany czas lokalny jest mapowany. W przypadku bieżących i ostatnich przeszłych znaczników czasu można mieć problem, że proces aktualizacji bazy danych tz i wprowadzanie zmian może przebiegać wolniej niż harmonogram implementacji przepisów. - plugwash


Jak wyjaśniono przez innych, istnieje tam nieciągłość czasowa. Istnieją dwie możliwe korekty strefy czasowej dla 1927-12-31 23:54:08 w Asia/Shanghai, ale tylko jeden offset dla 1927-12-31 23:54:07. Tak więc, w zależności od zastosowanego przesunięcia, występuje różnica jednej sekundy lub różnica 5 minut i 53 sekund.

To niewielkie przesunięcie przesunięć, zamiast zwykłych godzinnych oszczędności czasu letniego (czasu letniego), do których jesteśmy przyzwyczajeni, nieco zaciemnia problem.

Zauważ, że aktualizacja bazy danych strefy czasowej 2013a przesunęła tę nieciągłość kilka sekund wcześniej, ale efekt byłby nadal zauważalny.

Nowa java.time pakiet na Javę 8, użyjmy tego bardziej wyraźnie i dostarczmy narzędzia do jego obsługi. Dany:

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

Następnie durationAtEarlierOffset będzie jedną sekundę, natomiast durationAtLaterOffset będzie pięć minut i 53 sekundy.

Te dwie korekcje są takie same:

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

Ale te dwa są różne:

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

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

Możesz porównać ten sam problem 1927-12-31 23:59:59 z 1928-01-01 00:00:00w tym przypadku jednak wcześniejsza korekcja powoduje dłuższą rozbieżność, a wcześniejsza data ma dwie możliwe korekty.

Innym sposobem podejścia jest sprawdzenie, czy trwa przejście. Możemy to zrobić tak:

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

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

Możesz sprawdzić, czy przejście jest nakładaniem się - w takim przypadku istnieje więcej niż jedno poprawne przesunięcie dla tej daty / czasu - lub przerwa - w którym to przypadku ta data / godzina nie jest ważna dla tego identyfikatora strefy - za pomocą isOverlap() i isGap() metody na zot4.

Mam nadzieję, że pomoże to ludziom rozwiązać ten problem, gdy Java 8 stanie się powszechnie dostępna, lub tym, którzy używają Java 7, która adoptuje backport JSR 310.


168
2018-01-03 14:43



Cześć Daniel, Uruchomiłem twój kawałek kodu, ale to nie daje wyjścia zgodnie z oczekiwaniami. podobnie jak durationAtEarlierOffset i durationAtLaterOffset oba są tylko 1 sekunda, a także zot3 i zot4 oba mają wartość null. Po prostu skopiowałem i uruchomiłem ten kod na moim komputerze. czy jest coś, co należy tutaj zrobić. Daj mi znać, jeśli chcesz zobaczyć fragment kodu. Oto kod tutorialspoint.com/... czy możesz mi powiedzieć, co tu się dzieje. - vineeshchauhan
@vineeshchauhan To zależy od wersji Javy, ponieważ zmieniło się to w tzdata, a różne wersje pakietu JDK zawierają różne wersje tzdata. Na mojej własnej zainstalowanej Javie są czasy 1900-12-31 23:54:16 i 1900-12-31 23:54:17, ale to nie działa w witrynie, którą udostępniasz, więc używają innej wersji Java niż ja. - Daniel C. Sobral


IMHO wszechobecne, domniemany lokalizacja w Javie jest jej największą wadą konstrukcyjną. Może to być przeznaczone dla interfejsów użytkownika, ale szczerze mówiąc, kto tak naprawdę używa Javy dla interfejsów użytkownika dzisiaj, z wyjątkiem niektórych środowisk IDE, w których można zasadniczo zignorować lokalizację, ponieważ programiści nie są dokładnie jej odbiorcami. Możesz to naprawić (szczególnie na serwerach Linux) przez:

  • export LC_ALL = C TZ = UTC
  • ustaw swój zegar systemowy na UTC
  • nigdy nie używaj zlokalizowanych implementacji, chyba że jest to absolutnie konieczne (np. tylko do wyświetlania)

Do Proces społeczności Java członkowie polecam:

  • uczynić zlokalizowane metody nie domyślnymi, ale wymagają od użytkownika wyraźnego zażądania lokalizacji.
  • użyj UTF-8 / UTC jako NAPRAWIONY domyślnie zamiast tego, to po prostu dzisiaj jest domyślne. Nie ma powodu, aby robić coś innego, chyba że chcesz tworzyć takie wątki.

Chodzi mi o to, przestańcie, czy globalne zmienne statyczne nie są wzorcem anty-OO? Nic innego nie jest wszechobecne domyślne dane przez niektóre podstawowe zmienne środowiskowe .......


137
2017-11-26 15:58