Przetwarzanie ciągu z datą i godziną w określonym momencie (Java nazywa to „ Instant
”) jest dość skomplikowane. Java radzi sobie z tym w kilku iteracjach. Najnowszy, java.time
i java.time.chrono
obejmuje prawie wszystkie potrzeby (z wyjątkiem Dylatacji Czasu :)).
Jednak ta złożoność wprowadza wiele zamieszania.
Kluczem do zrozumienia parsowania daty jest:
Dlaczego Java ma tak wiele sposobów na parsowanie daty
- Istnieje kilka systemów pomiaru czasu. Na przykład historyczne kalendarze japońskie pochodzą z przedziałów czasowych panowania danego cesarza lub dynastii. Następnie jest np. Znacznik czasu UNIX. Na szczęście cały świat (biznesowy) wykorzystał to samo.
- Historycznie systemy były przełączane z / do, z różnych powodów . Np. Od kalendarza juliańskiego do kalendarza gregoriańskiego w 1582 r. Tak więc „zachodnie” daty muszą być wcześniej potraktowane inaczej.
- I oczywiście zmiana nie nastąpiła od razu. Ponieważ kalendarz pochodził z centrali niektórych religii, a inne części Europy wierzyły w inne diety, na przykład Niemcy zmieniły się dopiero w 1700 roku.
... i dlaczego jest LocalDateTime
, ZonedDateTime
i in. takie skomplikowane
Istnieją strefy czasowe . Strefa czasowa jest w zasadzie „paskiem” * [1] powierzchni Ziemi, którego władze przestrzegają tych samych zasad co do tego, kiedy ma przesunięcie czasowe. Obejmuje to zasady dotyczące czasu letniego.
Strefy czasowe zmieniają się w czasie dla różnych obszarów, głównie na podstawie tego, kto kogo zwycięża. Z czasem zasady jednej strefy czasowej również się zmieniają .
Istnieją przesunięcia czasowe. Nie jest to to samo, co strefy czasowe, ponieważ strefa czasowa może być np. „Praga”, ale ma przesunięcie czasu letniego i zimowego.
Jeśli otrzymasz znacznik czasu ze strefą czasową, przesunięcie może się różnić w zależności od tego, w której części roku się znajduje. Podczas godziny przestępnej znacznik czasu może oznaczać 2 różne czasy, więc bez dodatkowych informacji nie można go wiarygodnie nawrócony.
Uwaga: Przez znacznik czasu rozumiem „ciąg znaków zawierający datę i / lub godzinę, opcjonalnie ze strefą czasową i / lub przesunięciem czasu”.
Kilka stref czasowych może współdzielić to samo przesunięcie czasowe dla niektórych okresów. Na przykład strefa czasowa GMT / UTC jest taka sama jak strefa czasowa „Londyn”, gdy nie obowiązuje przesunięcie czasu letniego.
Aby było to nieco bardziej skomplikowane (ale nie jest to zbyt ważne w przypadku użycia):
- Naukowcy obserwują dynamikę Ziemi, która zmienia się w czasie; na tej podstawie dodają sekundy pod koniec poszczególnych lat. (
2040-12-31 24:00:00
Może to być prawidłowa data i godzina). Wymaga to regularnych aktualizacji metadanych używanych przez systemy do prawidłowej konwersji daty. Np. W systemie Linux otrzymujesz regularne aktualizacje pakietów Java, w tym te nowe dane.
Aktualizacje nie zawsze zachowują poprzednie zachowanie zarówno dla historycznych, jak i przyszłych znaczników czasu. Może się więc zdarzyć, że parsowanie dwóch znaczników czasu wokół zmiany strefy czasowej w porównaniu z nimi może dać różne wyniki, gdy uruchomione zostaną różne wersje oprogramowania. Dotyczy to również porównywania między strefą czasową dotkniętą chorobą a inną strefą czasową.
Jeśli spowoduje to błąd w oprogramowaniu, rozważ użycie znacznika czasu, który nie ma tak skomplikowanych reguł, jak na przykład znacznik czasu UNIX .
Z powodu 7 w przypadku przyszłych dat nie możemy przekonwertować dat dokładnie. Na przykład bieżące parsowanie 8524-02-17 12:00:00
może być wyłączone o kilka sekund od przyszłego parsowania.
Interfejsy API JDK do tego ewoluowały wraz ze współczesnymi potrzebami
- Wczesne wydania Java miały
java.util.Date
nieco naiwne podejście, zakładając, że jest tylko rok, miesiąc, dzień i godzina. To szybko nie wystarczyło.
- Również potrzeby baz danych były różne, więc wprowadzono dość wcześnie
java.sql.Date
, z własnymi ograniczeniami.
- Ponieważ żaden z nich nie obejmował dobrze różnych kalendarzy i stref czasowych, wprowadzono
Calendar
API.
- To wciąż nie obejmowało złożoności stref czasowych. A jednak połączenie powyższych API było naprawdę trudnym zadaniem. Gdy programiści Java zaczęli pracować nad globalnymi aplikacjami internetowymi, biblioteki, które były ukierunkowane na większość przypadków użycia, takie jak JodaTime, szybko zyskały popularność. JodaTime był de facto standardem przez około dekadę.
- Ale JDK nie zintegrował się z JodaTime, więc praca z nim była nieco kłopotliwa. Tak więc, po bardzo długiej dyskusji na temat podejścia do sprawy, JSR-310 został stworzony głównie w oparciu o JodaTime .
Jak sobie z tym poradzić w Javie java.time
Określ, do jakiego typu parsować znacznik czasu
Kiedy konsumujesz ciąg znacznika czasu, musisz wiedzieć, jakie informacje zawiera. To jest kluczowy punkt. Jeśli nie uda ci się tego zrobić, możesz uzyskać tajemnicze wyjątki, takie jak „Nie można utworzyć błyskawicznego” lub „Brak przesunięcia strefy” lub „nieznany identyfikator strefy” itp.
Czy zawiera datę i godzinę?
Czy ma przesunięcie czasowe?
Przesunięcie czasu jest +hh:mm
częścią. Czasami +00:00
może być zastąpiony przez Z
„czas Zulu”, UTC
jako koordynowany czas uniwersalny lub GMT
jako czas uniwersalny Greenwich. Ustawiają one także strefę czasową.
Dla tych znaczników czasu używasz OffsetDateTime
.
Czy ma strefę czasową?
Dla tych znaczników czasu używasz ZonedDateTime
.
Strefa jest określona przez
- nazwa („Praga”, „czas pacyficzny standardowy”, „PST”) lub
- „identyfikator strefy” („America / Los_Angeles”, „Europe / London”), reprezentowany przez java.time.ZoneId .
Lista stref czasowych jest tworzona przez „bazę danych TZ” , wspieraną przez ICAAN.
Zgodnie z ZoneId
javadoc, identyfikator strefy można również jakoś określić jako Z
i offset. Nie jestem pewien, jak to mapuje do prawdziwych stref. Jeśli znacznik czasu, który ma tylko TZ, wpada w godzinę przestępną zmiany przesunięcia czasowego, to jest on niejednoznaczny, a interpretacja jest przedmiotem ResolverStyle
, patrz poniżej.
Jeśli nie ma żadnego , wówczas brakujący kontekst jest przyjmowany lub zaniedbywany. I konsument musi zdecydować. Dlatego musi zostać przeanalizowany jako LocalDateTime
i przekonwertowany OffsetDateTime
przez dodanie brakujących informacji:
- Możesz założyć , że jest to czas UTC. Dodaj przesunięcie UTC 0 godzin.
- Możesz założyć , że jest to czas miejsca, w którym następuje konwersja. Przekształć go, dodając strefę czasową systemu.
- Możesz zaniedbać i po prostu używać go takim, jaki jest. Jest to przydatne np. Do porównania lub odjęcia dwa razy (patrz
Duration
) lub gdy nie wiesz i nie ma to tak naprawdę znaczenia (np. Rozkład jazdy autobusów lokalnych).
Informacje o niepełnym wymiarze godzin
- Na podstawie tego, co zawiera znacznik czasu, można przyjąć
LocalDate
, LocalTime
, OffsetTime
, MonthDay
, Year
, lub YearMonth
z niego.
Jeśli masz pełne informacje, możesz uzyskać java.time.Instant
. Jest to również wewnętrznie wykorzystywane do konwersji między OffsetDateTime
i ZonedDateTime
.
Dowiedz się, jak to przeanalizować
Istnieje obszerna dokumentacja, w DateTimeFormatter
której można parsować ciąg znacznika czasu i formatować go.
The wstępnie utworzone DateTimeFormatter
s powinien obejmować więcejmniej wszystkie standardowe formaty datownik. Na przykład ISO_INSTANT
można parsować 2011-12-03T10:15:30.123457Z
.
Jeśli masz jakiś specjalny format, możesz utworzyć własny DateTimeFormatter (który jest również analizatorem składni).
private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
.toFormatter();
Polecam zajrzeć do kodu źródłowego DateTimeFormatter
i zainspirować się, jak zbudować taki przy użyciu DateTimeFormatterBuilder
. Podczas gdy tam jesteś, sprawdź również, ResolverStyle
który kontroluje, czy analizator składni jest LENIENT, SMART czy STRICT dla formatów i niejednoznacznych informacji.
TemporalAccessor
Teraz częstym błędem jest wchodzenie w złożoność TemporalAccessor
. Wynika to ze sposobu, w jaki programiści byli przyzwyczajeni do pracy SimpleDateFormatter.parse(String)
. Racja, DateTimeFormatter.parse("...")
daje ciTemporalAccessor
.
// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");
Ale dzięki wiedzy z poprzedniej sekcji możesz wygodnie parsować wybrany typ:
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);
Tak naprawdę nie potrzebujesz DateTimeFormatter
ani jednego, ani drugiego. Typy, które chcesz przeanalizować, mają parse(String)
metody.
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");
Jeżeli chodzi o TemporalAccessor
, możesz go użyć, jeśli masz niejasne pojęcie o tym, jakie informacje znajdują się w ciągu i chcesz zdecydować w czasie wykonywania.
Mam nadzieję, że rzuciłem trochę zrozumienia na twoją duszę :)
Uwaga: Istnieje backport java.time
do Java 6 i 7: ThreeTen-Backport . Na Androida ma ThreeTenABP .
[1] Nie tylko to, że nie są paskami, ale są też dziwne skrajności. Na przykład niektóre sąsiednie wyspy Pacyfiku mają strefy czasowe +14: 00 i -11: 00. Oznacza to, że będąc na jednej wyspie, jest 1 maja 3 po południu, na innej wyspie jeszcze nie jest jeszcze 30 kwietnia 12 po południu (jeśli poprawnie policzyłem :))
ZonedDateTime
raczej niżLocalDateTime
. Nazwa jest sprzeczna z intuicją;Local
oznacza dowolną lokalizację w ogóle zamiast strefę określonym czasie. Jako takiLocalDateTime
obiekt nie jest powiązany z linią czasu. Aby mieć znaczenie, aby uzyskać określony moment na linii czasu, musisz zastosować strefę czasową.