W każdym systemie informatycznym funkcjonują mechanizmy obsługujące datę i czas. Zwykle bardzo ważne jest zapisywanie informacji o
momencie wystąpienia czynności, zdarzenia lub innego elementu rzeczywistości opisywanej przez system.
W Javie, przed erą Javy 8 istniał dosyć skromny zestaw rozwiązań do zarządzania takimi danymi.
Data przed Javą 8
W skrócie wyglądało to tak, że istniała klasa o nazwie
Date, która
wyznaczała datę i czas oraz klasa
Calendar, której rolą było dostarczenie metod
do manipulacji datą i czasem. Tak więc, jeśli chcieliśmy uzyskać bieżącą datę, po prostu tworzyliśmy obiekt klasy
Date.
Dodatkowo mogliśmy użyć formatera (klasa
SimpleDateFormat), tak aby przedstawić takie dane w odpowiadającym nam formacie:
Wynik wykonania kodu:
Pobranie bieżącej daty i czasu można było wykonać również za pomocą klasy
Calendar, ale
klasa ta była używana głównie do modyfikacji daty/czasu. Mogliśmy dowolnie ustawiać dni, miesiące, lata, godziny itp.
Kilka możliwości obrazuje poniższy przykład:
Wynik wykonania kodu:
* Na uwagę zasługuje fakt, że numeracja miesięcy zaczyna się od zera, więc cyfra 6 oznacza lipiec.
Appa Notka. Uparcie piszemy o datach sprzed Javy 8 w czasie przeszłym, co może nieco
zaskakiwać, skoro wspomniane tutaj klasy nadal są dostępne w Javie. Wynika to z tego, że praktycznie w każdym nowym systemie używany jest
już zestaw klas i interfejsów z Javy 8. Pakiet rozwiązań dotyczący daty i czasu został w tej wersji znacząco rozszerzony, a także wyeliminowano
wiele dotychczas występujących problemów.
Problemy z datami przed Javą 8
Można zadawać sobie pytanie dlaczego w Javie 8 wprowadzono całkiem nowy pakiet klas i interfejsów do zarządzania datą i czasem.
Odpowiedź sprowadza się do przedstawienia kilku kluczowych problemów poprzedniego modelu:
- Ubogie i nienajlepiej zaprojektowane API, co udowodnimy w tym rozdziale, omawiając alternatywy z wersji Java 8.
- Klasa Date jest mutable, czyli można ją łatwo modyfikować, co w przypadku dat może być niebezpieczne.
- Klasa SimpleDateFormat nie jest synchronizowana, co generuje problemy w systemach wielowątkowych.
- Mizernie zaimplementowane zarządzanie strefami czasowymi. Sama klasa Date nie pozwala na podanie strefy czasowej.
Data w Java 8 - Wstęp
No to zaczynamy. Najpierw podstawy, czyli zapoznanie się z listą tematów, które rozwijamy w dalszej części tego rozdziału.
Zarządzanie datą i czasem zostało umieszczone w nowym pakiecie o nazwie
java.time. To tam znajdują się nowe klasy, interfejsy
i inne byty odpowiedzialne za prawidłową obsługę dat.
W ramach nowości w Javie 8 pojawiają się zupełnie nowe pojęcia:
- Instant, czyli punkt w czasie
- LocalDate - data w lokalnej (systemowej) albo zdefiniowanej przez nas strefie czasowej (bez przechowywania jej w obiekcie)
- LocalTime - czas w lokalnej (systemowej) albo zdefiniowanej przez nas strefie czasowej (bez przechowywania jej w obiekcie)
- LocalDateTime - data i czas w lokalnej (systemowej) albo zdefiniowanej przez nas strefie czasowej (bez przechowywania jej w obiekcie)
- ZonedDateTime - data i czas w lokalnej (systemowej) albo zdefiniowanej przez nas strefie czasowej (przechowywana w obiekcie)
- Duration - określenie czasu trwania w zakresie od nanosekund do liczby dni
- Period - określenie czasu trwania w zakresie od dni do lat
- TemporalAdjusters - dostosowanie daty do potrzeb za pomocą predefiniowanych metod
- Nowe formatery dat - nowe sposoby formatowania daty i czasu
Tyle tytułem wstępu. Teraz wyjaśnimy wszystko po kolei, bazując na konkretnych przykładach.
Data w Java 8 - Instant vs Date
Klasa
Instant reprezentuje konkretny punkt na linii czasu, a jej precyzja sięga nanosekund.
Instant jest
immutable, więc podczas próby modyfikacji obiektu tego typu uzyskujemy referencję do nowego obiektu, a
obiekt modyfikowany pozostaje bez zmian. To duży plus, ponieważ nie musimy się martwić, że spotkamy się z problemami takimi jak w przypadku
użycia klasy
Date. Podobnie będzie z
LocalTime,
LocalDate,
LocalDateTime
i
ZonedDateTime. One też są
immutable.
Zróbmy teraz małe porównanie wyjaśniające, co oznacza to pojęcie.
Stwórzmy klasę
DateAndInstantProvider i przeanalizujmy różnicę miedzy
immutable Instant
a
mutable Date:
W kodzie głównym programu (poniżej) tworzymy obiekt klasy
DateAndInstantProvider, w którym automatycznie
inicjowane są pola
date i
instant (powyżej). Dalej pobieramy obie wartości z obiektu,
a dokładniej
uzyskujemy referencje do tych obiektów. Teraz przechodzimy do kluczowej części programu. W niej drukujemy daty
przed i
po
zmianach obiektów. Po zmianie pobieramy jeszcze raz referencje bezpośrednio z
dateAndInstantProvider.
Okazuje się, że data typ
Date zmieniła się, podczas gdy data typu
Instant
pozostała taka sama. Dowodzi do, że
Instant jest
immutable, w przeciwieństwie do
Date, która jest
mutable.
Po modyfikacji obiektu typu
Instant otrzymaliśmy nowy obiekt tego typu (nazwaliśmy go
newInstant)
i tam znajduje się nasza nowa data.
Wynik wykonania kodu:
Appa Notka. Użycie instanta spowoduje, że data będzie podana w strefie czasowej UTC (to oznacza "Z" na końcu wydruku daty), więc
ta klasa nie powinna być używana do zapisywania momentu wystąpienia wydarzeń, czy też poznawania bieżącej godziny.
Traktujemy ją czysto technicznie, stosując na przykład do odmierzania czasu trwania określonych operacji.
Po prostu, dzięki klasie Instant poznajesz dwa punkty w czasie i możesz wyznaczać różnice między nimi.
W tej materii dowiesz się więcej, gdy przejdziemy do omawiania klasy Duration, która wspiera klasę Instant
w jej działaniach.
Data w Java 8 - Użycie Instant
Tak jak zaznaczyliśmy w poprzednim paragrafie, klasa
Instant pozwala nam na określenie konkretnego momentu w czasie.
Pytanie, jakie jeszcze możliwości dostarcza nam ta klasa? Czy możemy na przykład obiekt tej klasy stworzyć z obiektu klasy
Date albo wczytać datę podając stringa?
Odpowiedzi na te pytania znajdują się w komentarzach poniższego przykładu:
Wynik wykonania kodu:
Można do tego jeszcze dodać metody aktualizujące (
plus,
minus) sekundy, millisekundy i nanosekundy, ale tym zajmiemy się dopiero za chwilę.
Po prostu, aby udać się w tym kierunku, potrzebujesz jeszcze trochę wiedzy z innych zagadnień.
Zwróć uwagę, że klasa
Instant co prawda wyznacza punkt w czasie, ale nie służy do określania dokładnej daty i godziny, pasującej do tego, co widzisz w systemie operacyjnym.
Będzie ona zgodna z Twoim czasem systemowym tylko, jeśli przebywasz w strefie
UTC,
ale wtedy i tak nie powinieneś(-naś) jej zapisywać w aplikacji webowej w celu obsługi logiki biznesowej.
Uzależnienie poprawnego zapisu daty i czasu od jednej strefy czasowej jest złym podejściem.
W dalszej części rozdziału poznasz klasę, która nadaje się do tego o wiele lepiej (dla niecierpliwych:
LocalDateTime)
Po wyjaśnieniu tematu instantów ruszamy dalej, tym razem w kierunku obsługi daty zgodnej z
Twoimi ustawieniami systemowymi (a więc i Twoją strefą czasową).
Data w Java 8 - LocalDate
Ten paragraf należałoby rozpocząć od stwierdzenia, że jeśli zależy Ci na
obsłudze samych dat (bez czasu) z prostym definiowaniem stref czasowych to "jesteś w domu". Do tego właśnie służy klasa
LocalDate.
Nie zapamiętuje ona informacji o strefie czasowej, ale podaje ją w strefie czasowej systemu operacyjnego (na którym uruchamiamy kod),
bądź strefie podanej w postaci parametru ZoneId.
Co to znaczy, że
nie zapamiętuje? To oznacza tyle, że po stworzeniu daty w określonej strefie czasu, przy późniejszym jej użyciu nie wiadomo, w jakiej strefie ona powstała.
Innymi słowy, jeżeli stworzysz obiekt klasy
LocalDate w strefie na przykład
UTC,
to w kolejnej linii kodu nie wyciągniesz z tego obiektu informacji, w jakiej strefie została utworzona data.
Ważne jest, aby zapamiętać, że
LocalDate przechowuje informację tylko o samej dacie, bez czasu.
Wynik wykonania kodu:
Data w Java 8 - LocalTime
Było o dacie, to teraz przyszła kolej na klasę reprezentującą czas, a więc
LocalTime.
Reguły dotyczące lokalizacji w strefie czasowej są dokładnie takie same jak w przypadku
LocalDate.
Nie będziemy ich powtarzać, ponieważ już je znasz. Zapamiętaj, że klasa
LocalTime
określa tylko i wyłącznie czas. Nie zawiera informacji o dacie.
Wynik wykonania kodu:
Data w Java 8 - LocalDateTime
Wreszcie przyszedł czas na danie główne. Klasa
LocalDateTime przechowuje informacje zarówno
o dacie, jak i o czasie. Reguły poznane w przypadku
LocalDate także tutaj
znajdują swoje zastosowanie. W dalszym ciągu data i czas określone przez obiekt tej klasy są zgodne ze strefą czasową
systemu, na którym uruchamiasz kod, bądź strefą, którą podasz w postaci parametru stosownej metody.
W przypadku protych aplikacji z nieskomplikowaną logiką dotyczącą zmian stref czasowych klasa
LocalDateTime jest w zupełności wystarczająca.
Natomiast jeśli się okaże, że potrzebujesz czegoś więcej, wtedy warto rozważyć użycie klasy
ZonedDateTime.
Wynik wykonania kodu:
Data w Java 8 - Strefy czasowe
Zanim przejdziemy do klasy
ZonedDateTime, wyjaśnimy jeszcze, skąd bierzemy informacje
o strefach czasowych w Javie. Wszystkie strefy pochodzą z bazy informacyjnej IANA, znajdującej się pod adresem
https://www.iana.org/time-zones.
W kodzie Javy listę stref wyciągamy za pomocą metody
getAvailableZoneIds z klasy
ZoneId.
Możemy też stworzyć obiekt tej klasy, który będzie reprezentował konkretną strefę. Taka umiejętność przyda się już w kolejnym paragrafie.
Wynik wykonania kodu:
Data w Java 8 - ZonedDateTime
Najbogatszą klasą obsługującą datę i czas w Javie 8 jest
ZonedDateTime.
Oferuje ona zbliżoną funkcjonalność co
LocalDateTime z tą różnicą, że
przechowuje jeszcze informację o strefie czasowej, w której dana data i czas zostały pobrane.
Klasa
ZonedDateTime daje nam wiele możliwości tworzenia daty i czasu
wraz ze strefami czasowymi. Uruchomienie metody
now
zwraca informację o dacie i czasie z systemu, ale tym razem zawiera
doklejoną informację o przesunięciu czasu oraz nazwie strefy czasowej
(obrazek "Wynik wykonania kodu" pod snippetem kodu).
Wykonanie kolejnej metody z podaniem strefy czasowej
zwraca bieżącą godzinę w tamtej strefie, czyli naszą godzinę przesuniętą o
ZoneOffset.
Przesunięcie wynosi
9 godzin w stosunku do UTC, a więc 7 godzin w porównaniu do naszej strefy czasu (my obecnie w czasie letnim jesteśmy przesunięci o 2 godziny względem
UTC).
Dalej wykonujemy operację odwrotną, tyle że z wykorzystaniem metody
withZoneSameInstant.
Data i godzina wracają do pierwotnej postaci.
Zanim przejdziemy do kolejnej operacji na strefach, ustawmy jeszcze zupełnie nową datę, tym razem złożoną z poszczególnych parametrów,
takich jak rok, dzień, miesiąc itd. W tym celu wykorzystamy metodę
of.
Kolejną opcją jest stworzenie obiektu klasy
ZonedDateTime w danej strefie (
LocalDateTime.now()) i
doklejenie innej strefy
bez zmiany daty i czasu. Ponownie z użyciem metody
of. W tak stworzonym obiekcie będziemy mieli naszą datę,
ale offset i strefa będą wyglądać, jakby data pochodziła z innej strefy (w tym przypadku
Asia/Tokyo).
W ten sposób możemy manipulować strefami czasowymi w dowolny sposób. Na jasnozielono zaznaczyliśmy bieżącą datę w strefie amerykańskiej,
z doklejoną strefą azjatycką.
Wynik wykonania kodu:
Appa Notka. Niech Cię nie zdziwi, że daty, które mają być identyczne, różnią się milisekundami.
Wynika to z tego, że dla kolejnych przykładów na nowo pobieramy datę i czas za pomocą LocalDateTime.now(...) i podobnych metod, więc różnica milisekund pojawia się ze względu na delikatnie różny czas
wykonania się tych linii kodu. Nie ma to znaczenia w kontekście tłumaczonych zagadnień.
Data w Java 8 - ZonedDateTime - dodatek
Na koniec rozdziału zerknij jeszcze na inne przykłady wykorzystania klasy
ZonedDateTime.
Najpierw różnymi metodami tworzymy dwie daty (
zonedDateTime i
startDateTime), które będą nam potrzebne nieco dalej.
Następnie przekształcamy stringa na obiekt z datą. Charakterystyczne w tym stringu jest
to, że uwzględniamy w nim
offset (przesunięcie) oraz
nazwę strefy czasowej.
Dalej wykorzystujemy metodę
isBefore, której zadaniem jest sprawdzenie, czy data skonwertowana ze stringa
jest przed bieżącą datą pobraną z zegara systemowego.
Ostatnie metody dokumentują, że tak jak było w przypadku
LocalDateTime, tak samo i w przypadku
ZonedDateTime
możemy w łatwy sposób pobrać z obiektu daty poszczególne składowe czasu.
Wynik wykonania kodu:
W rozdziale zajęliśmy się typami obiektów reprezentujących datę/czas w Javie 8. Pokazaliśmy też różnicę w stosunku do wcześniejszych wersji Javy.
Na szczególne wyróżnienie zasługuje fakt udostępnienia odrębnych klas specjalizujących się w konkretnych zagadnieniach.
Na tym etapie widać już, że mamy sporo nowych rozwiązań, a przecież wiele jeszcze przed nami. W kolejnym rozdziale zobaczysz, jak można
wykorzystać potencjał klas
Duration,
Period oraz
TemporalAdjusters.
Nauczysz się także formatować daty za pomocą nowej klasy o nazwie
DateTimeFormatter.
Autor: Jarek Klimas
Data: 03 stycznia 2024
Labele: Backend, Podstawowy, Java
Czy informacje, które otrzymałeś, były pomocne?
Jeśli tak, zapraszam Cię do podarowania mi kawy.
Masz pytanie dotyczące prezentowanego materiału?
Coś jest dla Ciebie niejasne i Twoje wątpliwości przeszkadzają Ci w pełnym zrozumieniu treści?
Napisz do nas maila, a my chętnie znajdziemy odpowiednie rozwiązanie.
Najciekawsze pytania wraz z odpowiedziami będziemy publikować pod rozdziałem.
Nie czekaj. Naucz się programować jeszcze lepiej.