Czas w Javie ⏱️. Dobrze używasz?

Mądre zarządzanie czasem nie jest łatwe – w dużej mierze dotyczy się to życia prywatnego ale również ma swoje odniesienie w technologii. Każda operacja, która wplata czas w swoją domenę jest warta zastanowienia – jak, po co, dlaczego i dla kogo to ma służyć? Świat jest duży, istnieją różne strefy czasowe, przesilenia jesienne/wiosenne. To wszystko wpływa na czas i programy, które ten czas implementują. Raz błędnie zaimplementowana logika może ponieść za sobą olbrzymie konsekwencje.


Cześć 🙂

Nie będzie to artykuł, który poprowadzi Cię szczegółowo po dokumentacji API. Chciałbym Ci przedstawić główne idee dotyczące czasu oraz abyś zrozumiał jak używać czasu. Dziwnie to brzmi jak używać czasu, ale taka jest koncepcja. Wiele osób nie zastanawia się nad tym, walą LocalDateTime.now() i wszystko gra bo u mnie działa. Świadomi programiści tak nie robią. Na początku penetrują API, później zastanawiają się jakie mogą być konsekwencje ich wyborów a następnie podejmują decyzje. Najlepiej takie decyzje, które mogą być odwracalne.

W dzisiejszym artykule:


API Dawniej i Dziś

Bez zbędnego gadania – API do zarządzania datą przed Javą 8 było słabe – klasy były mutowalne i niebezpieczne dla środowisk wielowątkowych (DateFormat).  Jako, że API zostało zaprojektowane już dawno temu (java.util.Date – JDK 1 / java.util.Calendar – JDK 1.1) i było w użyciu przez wiele lat to jego wady zostały zauważone i podkreślane przez innych. Dzięki temu powstawały rozwiązania poza JDK, które ułatwiały pracę z czasem. Najpopularniejszą biblioteką jaka powstała jest Joda-Time (jeżeli utrzymujesz projekt posiadający wersję Javy niższą niż 8 i potrzebujesz zarządzać czasem, rozważ użycie tej biblioteki).

Tak było kiedyś. Architekci Javy widząc problem i inspirując się API Joda-Time doszli do wniosku, że aktualne rozwiązanie trzeba zaorać. I tak oto powstało nowe API, znajdujące się w JDK w pakiecie java.time. Uwagi projektowe zamieszczone w dokumentacji najlepiej opisują czym kierowali się twórcy projektując nowe API. Z notatek technicznych można wskazać 3 główne cechy nowego API:

  1. Niemutowalność – to była zmora dawnego API.
  2. Domain Driven Design – tworzone API ma być oparte na zachowaniach domeny – szczególnie istotne jest odróżnienie przypadków biznesowych dla Daty i Czasu.
  3. Separacja chronologii – umożliwienie pracy z kalendarzami innymi niż standardowe, np. japoński czy tajski.

Pomimo, że pakiet java.time, może być początkowo dosyć przerażający biorąc pod uwagę ilość klas jakie posiada, to nawet dla osoby z niedużym doświadczeniem nie będzie to ciężkie do ogarnięcia podstawowych przypadków. Podstawowe przypadki to jest jakieś 90% albo i więcej czasu naszej pracy (to już zależy jak trafisz). Do uzupełnienia pozostałych 10% będziesz musiał poczytać dokumentację, doczytać źródła w Internecie i popróbować samemu – nie jest to ciężkie, ale wymaga pracy.

Warto też dodać, że musisz rozumieć czym jest czas. Ale ten punkt powinieneś zrozumieć czytając kolejną sekcję.

A teraz krótki cytat z dokumentacji API:

This can seem like a lot of classes, but most applications can begin with just five date/time types.

  • Instant – a timestamp
  • LocalDate – a date without a time, or any reference to an offset or time-zone
  • LocalTime – a time without a date, or any reference to an offset or time-zone
  • LocalDateTime – combines date and time, but still without any offset or time-zone
  • ZonedDateTime – a „full” date-time with time-zone and resolved offset from UTC/Greenwich

Instant is the closest equivalent class to java.util.DateZonedDateTime is the closest equivalent class to java.util.GregorianCalendar.


Moment a data wydarzenia

Długo miałem problem żeby zrozumieć jaka jest różnica między momentem wydarzenia a datą wydarzenia. Mój mózg nie chciał tego zwizualizować. Jeżeli też masz problem ze zrozumieniem różnicy pomiędzy tym zagadnieniem, to proszę Cię o skupienie się przez najbliższą minutę.

Wyobraź sobie, że są Letnie Igrzyska Olimpijskie. Ludzie z całego świata walczą o jakieś tam tytuły w jednym konkretnym miejscu – akurat najbliższe będą w Tokyo. Inni ludzie – również z całego świata – będą te Igrzyska oglądać w różnych miejscach na świecie. Niech program Igrzysk trwa codziennie, pomiędzy 11 rano a 23 wieczorem czasu lokalnego (Tokyo).

Jestem Polakiem i chcę wiedzieć o której mam włączyć telewizor żeby móc to obejrzeć. Patrzę w reklamy w TV (tak nawiasem, nie oglądam TV) i mówią mi, że program Igrzysk trwa między 4 rano (dla niektórych to jeszcze noc) a 16. I właśnie tym jest moment. Konkretny czas wydarzenia w danym momencie na całej kuli ziemskiej (albo i we wszechświecie).

Kiedy stosować? Jeżeli Twoja aplikacja ma obsługiwać ludzi z całego świata (albo nawet z kilku stref czasowych) i chcesz żeby jakieś wydarzenie (cokolwiek co ma jakąś datę czy czas) było w danym momencie dostępne dla wszystkich o tej samej porze to użyj klas wspierających moment.

 

A co z datą wydarzenia? Wydaje mi się, że tutaj nie ma aż tak wielkich problemów ze zrozumieniem. Data wydarzenia jest po prostu konkretnym punktem na osi czasu, np. urodziny babci 23 czerwca, start Igrzysk o godzinie 11, piłka z kolegami w piątek o 20. I teraz pytanie – czy jeżeli urodziny babci są 23 czerwca a ja jestem w Tokyo i przypomniałem sobie o nich o godzinie 20 to zdążę jeszcze złożyć życzenia? No i tu znów wchodzi w grę moment.

To czy będziesz wspierał strefę czasową czy nie, zależy od specyfikacji Twojej aplikacji. Warto jednak pamiętać, że nawet jeżeli nie decydujesz się ich wspierać to możesz wpaść w problemy. Większość państw ma coś takiego jak czas letni i czas zimowy, gdzie godzina na zegarku przeskakuje o godzinę w przód lub w tył. Jeżeli chcesz dowiedzieć się więcej na temat problemów w jakie możesz wpaść stosując wyłącznie klasy nie wspierające momentu, obejrzyj to krótkie wideo.

Apropo stref czasowych i dlaczego sam nie powinieneś podejmować się ich implementacji. Polecam świetny 10-minutowy filmik przy którym można się nawet uśmiechnąć The Problem with Time & Timezones – Computerphile.


Dobre zalecenia

Niech ta sekcja będzie częścią wspólnych doświadczeń – jeżeli masz jakąś radę, coś, co widzisz, że jest problematyczne przy zarządzaniu czasem to daj o tym znać w komentarzu. Ja z chęcią zedytuję post i dodam Twoją radę.

Jeden. Modeluj domenę przy użyciu klas LocalDate, LocalTime i LocalDateTime.

Cytat z dokumentacji

Where possible, applications should use LocalDateLocalTime and LocalDateTime to better model the domain. For example, a birthday should be stored in a code LocalDate. Bear in mind that any use of a time-zone, such as ‚Europe/Paris’, adds considerable complexity to a calculation. Many applications can be written only using LocalDateLocalTime and Instant, with the time-zone added at the user interface (UI) layer.

Dwa. Data i czas powinny być wymieniane/przechowywane w formacie UTC. Źródła [1][2]

(Uwaga! Patrz również punkt 4)

Choć na pierwszy rzut oka można sobie pomyśleć, że gryzie się to z punktem pierwszym bo zaraz zaraz… będę używać np. LocalDateTime, który jest czasem lokalnym i jednocześnie czasu UTC? Tak 🙂

Trzy. Nie polegaj na strefie czasowej serwera!

Unikaj czegoś takiego:

Poleganie na domyślnej strefie czasowej może zaskoczyć – pamiętaj, że strefa czasowa serwera jest poza Twoją kontrolą jako programisty. Co jeżeli Twój program będzie hostowany na 2 lub 3 serwerach – każdy w innej strefie czasowej.

Możesz sobie powiedzieć, nie no – wiem, że jest tylko jedna instancja serwera i to w moim mieście. Ok… niech będzie. Ale co jeżeli tą instancją zarządza jakiś admin, któremu nagle przyjdzie do głowy, żeby zmienić strefę czasową na serwerze bo taki miał kapryś tego dnia. (Ignore Server Time Zone)

Cztery. UTC nie jest lekiem na wszystko!

Użytkownik Cepewka w komentarzach do tego posta słusznie zauważył że przechowywanie czasu jako UTC nie jest złotym środkiem. Istnieją sytuacje kiedy czas, przechowywany w taki sposób może nie być odpowiedni dla naszego przypadku biznesowego. Nie będę poruszać tych kwestii w tym poście ale chcę żeby każdy czytający był świadomy.

Zachęcam do przeczytania komentarzy użytkownika Cepewka (dzięki za poruszenie tej kwestii!). A jeżeli potrzebujesz więcej informacji to odsyłam do kilku linków:


Podsumowanie

Zarządzanie czasem wydaje się łatwe ale nie jest proste. Lepiej spędź chwilę na rozmowach z klientem i podrąż temat jaki jest oczekiwany rezultat. Często okaże się, że plany długoterminowe biznesu są większe niż jest to przedstawiane na początku. Twórz takie API, które będzie łatwo przenaszalne pomiędzy różne serwery i na które strefa czasowa serwera nie wpływa. Mówi się, że czas leczy rany, w tym przypadku niekoniecznie 😅. Może się okazać, że z czasem przyjdą zmiany a my będziemy zbierali żniwo naszej wcześniejszej implementacji. Życzę, żeby tak nie było.

Źródła:


Za tydzień

Porozmawiamy o architekturze warstwowej. Jest to architektura która pozwala na szybkie dostarczanie? A może zmora przez którą nie myślimy o tym co dostarczamy?

0 0 vote
Article Rating
Subscribe
Powiadom o
guest
5 komentarzy
najnowszy
najstarszy oceniany
Inline Feedbacks
View all comments
Cepewka
Cepewka
19 dni temu

Warto pamiętać, że trzymanie czasu jako UTC nie jest złotym środkiem. Jeżeli trzymamy instant jakiegoś wydarzenia w przyszłości (na przykład start konferencji za 2 lata), a w międzyczasie kraj zmieni strefę czasową lub reguły DST, to czas UTC się rozjedzie (najprawdopodobniej o +- jedną godzinę). Tak naprawdę trzeba trzymać czas UTC i wersję tzdb/cldr LUB trzymać trzymać czas w formie niezależnej od jakichkolwiek dat (na przykład string „9:00 rano czasu lokalnego”).

Cepewka
Cepewka
19 dni temu
Reply to  Bartosz Dąbek

Bierzesz czas PL na 2022.07.25, dostajesz coś na kształt UTC + 2 (czy jaka tam strefa czasowa jest). Godzinę 9:00 UTC+2 traktujesz jako 7:00 w czasie UTC, czyli w bazie zapisujesz, że wydarzenie jest o 7 rano w czasie UTC. Teraz w 2021 Polska zmienia strefę czasową letnią na UTC+1 i robi się problem, bo przy wyświetleniu użytkownikowi wyciągniesz z bazy danych godzinę 7:00, dodasz godzinę (bo strefa czasowa w Polsce w lecie po zmianach to UTC+1) i w efekcie dostajesz 8:00. Powinieneś zapisać czas UTC wraz z wersją tzdb/cldr i albo aktualizować przy zmianach bazy albo przy wyświetlaniu odpowiednio… Czytaj więcej »

Cepewka
Cepewka
18 dni temu
Reply to  Bartosz Dąbek

Problem jest tak naprawdę o wiele szerszy i łatwo się pomylić. Wyobraźmy sobie, że kupuję bilet lotniczy na grudzień tego roku na godzinę 8:00 w niedzielę. Ale za pół roku będę miał inne przesunięcie względem UTC (nie będę tu nawet wspominał o różnicach między strefą czasową a przesunięciem względem UTC itp, to jest bardzo duży temat). Jak teraz dodaję zdarzenie do kalendarza? Jeżeli kalendarz jest „głupi”, to przy przeglądaniu grudnia weźmie czas UTC, przeliczy względem obecnego czasu DST i pokaże mi, że wylot jest o 9:00, co wprawdzie nie jest „błędem” (bo za kilka miesięcy samo się naprawi), ale użytkownik… Czytaj więcej »