Strefy czasowe: OffsetDateTime czy LocalDateTime? A może ZonedDateTime?

Strefy czasowe: OffsetDateTime czy LocalDateTime? A może ZonedDateTime?
aliszja
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:30
1

Mam w robocie typowego CRUDa w Springu (minuta ciszy dla mnie), w Javie 11. CRUD robi ekspansję za ocean, więc trzeba zaimplementować wybór strefy czasowej użytkownika i operować poprawnie na datach i czasach. Problemów z datami było już sporo przy jednej strefie czasowej, a operacje na nich to smutek rozpacz i cierpienie, bo wszystkie daty są trzymane w OffsetDateTime, a z frontendu przychodzi czas lokalny. Jest to ogarnięte jakimś dzikim voodoo w stylu "odejmij godzinę, dodaj 3 minuty, podskocz na lewej nodze a jak zrobisz to ładnie, to zadziała".

No i rozgorzała w teamie dyskusja, jak to powinno być zrobione, czy w encjach powinno być OffsetDateTime, czy nie, czy konwertować OffsetDateTime z encji na LocalDateTime, czy nie. Nagle trzeba użyć Javy, więc wiadomo, pod górkę. Ludzie mają jakieś tam opinie, ale średnio im ufam, bo widziałam już w ich wykonaniu return null;, operacje na pieniądzach na Double i 20 dependencies w jednym Service, a także inne trololo typowe dla programistów Springa, którzy lubią żyć na krawędzi.

Moje pytanie brzmi: Jak to powinno być zrobione, żeby było dobrze - tak by the book? Jaki typ w encjach? Jaki typ w service? Co zwracać w DTOsach frontowi? Mamy scheduler, który na przykład wysyła przypomnienia użytkownikom, jeśli tego dnia upływa termin zrobienia czegoś, jak to ogarnąć przy różnych strefach czasowych?

Mój koncept jest taki: w bazie wszystko w UTC, w service operujemy na LocalDateTime z UTC, a na koniec zwracamy frontowi LocalDateTime ze strefą użytkownika. Ale czy to wymaga, żeby migrować encje na LocalDateTime? Albo może wręcz przeciwnie, niech nas ręka boska broni?

edytowany 2x, ostatnio: aliszja
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:7 minut
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0

A czemu z frontu przychodzi data z timezonem a nie timestamp?

aliszja
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:30
0

@TomRiddle: Szczerze, to nie wiem, tak zastałam. Ale załóżmy, że przychodziłby timestamp, bo tak jest poprawniej. Przerzucamy timestamp na LocalDateTime z UTC, robimy jakieś operacje, checki, cokolwiek, zapisujemy do bazy. Pytanie wciąż istnieje: Jaki powinien być typ w encji zapisywanej do bazy? OffsetDateTime? LocalDateTime? Jaki powinien być typ kolumny w bazie? (MySQL)

Albo w drugą stronę: czytamy z bazy, robimy jakieś operacje w service, zwracamy timestamp frontendowi. Żeby te operacje móc łatwo wykonywać, zakładam że używalibyśmy LocalDateTime - w której warstwie konwertujemy tę datę? Już na etapie encji, czy może w service?

edytowany 1x, ostatnio: aliszja
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:7 minut
  • Lokalizacja:Laska, z Polski
  • Postów:10056
2
aliszja napisał(a):

Jaki powinien być typ w encji zapisywanej do bazy? OffsetDateTime? LocalDateTime? Jaki powinien być typ kolumny w bazie? (MySQL)

Moim zdaniem typ kolumny w MySql to powinien być TIMESTAMP.

Czytaj: https://dev.mysql.com/doc/refman/8.0/en/datetime.html

Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:dzień
  • Postów:1873
6

Użyj Instant. LocalDateTime nie ma informacji o strefie czasowej, OffsetDateTime ma stały offset bez uwzględnienia np. zmiany czasu. Najwiecej info posiada ZonedDateTime, natomiast zakładam, że Ciebie interesuje punkt w czasie.


”Engineering is easy. People are hard.” Bill Coughran
ZI
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 3 lata
  • Postów:208
0

Tak ogólnie biorąc to trochę zależy od tego co aplikacja robi. Jeśli chcesz np schedulować spotkania na kilka miesięcy w przód, i te spotkania mają być o 7 rano każdego dnia, to OffsetDateTime się nie nadaje bo zje ci zmianę czasu. LocalDateTime też niezbyt - możesz myśleć że sobie policzysz z góry te czasy spotkań na pół roku do przodu, a tutaj jakiś kraj przestanie uwzględniać zmianę czasu, i jesteś w dupie (vs podmianka tzdata). Dodatkowo wyobraź sobie że ktoś jest właścicielem standupów danego zespołu ale pojechał sobie na konferencję do stanów i przestawił preferencje strefy czasowej - czy teraz spotkanie powinno być o 10:30 rano czasu w Polsce, czy czasu tej osoby która założyła spotkanie?

W twoim przypadku trzeba się zastanowić - czy ważna w tym schedulerze jest konkretna lokalna godzina (np musisz wziąć urlop na żądanie przed 9:00) czy nie (masz 72 godziny na zasubmitowanie rozwiązania jakiegoś quizu)?

edytowany 2x, ostatnio: Zing
W0
  • Rejestracja:ponad 12 lat
  • Ostatnio:około godziny
  • Postów:3543
0

Po stronie BE to powinno być zapisane w jakimś jednolitym formacie, może być nawet czas z polskiej strefy - byleby było spójnie. Chociaż faktycznie strefa Z będzie do tego najlepsza. Do tego (jeśli potrzeba) strefa użytkownika powinna lecieć w headerze/w sesji/w bazie danych (zależnie od konfiguracji) jeśl zajdzie taka potrzeba.

Największy problem jest taki, żeby zapewnić, że front będzie się trzymał konwencji - tj. żeby przysyłał informacje na temat strefy w jakimś jednym formacie.

aliszja
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:30
0

@Zing: Zdecydowanie konkretna godzina. Założenie jest takie: user wybiera w swoich ustawieniach, w jakiej strefie czasowej operuje. Potem, powiedzmy, że składa zamówienie i ustala datę i czas ważności tego zamówienia. Osoby, które na to zamówienie odpowiadają, mają poustawiane swoje strefy czasowe. Ważne jest, żeby pokazać im datę i czas upływu ważności zamówienia w ich strefie czasowej, i wysłać przypomnienie mailowo. Teraz przypomnienie przychodzi po prostu o 9:00 rano dnia, kiedy ten deadline upływa. Zakładam, że trzeba będzie podejść do tego bardziej na zasadzie "przypomnij 12h przed deadlinem", tylko co jeśli to będzie środek nocy, a klienci życzą sobie dostawać maile z przypomnieniami rano?

@wartek01: Wybrana strefa użytkownika będzie zapisana w bazie, bo użytkownicy chcą ją sobie ustawiać z panelu opcji użytkownika.

Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:dzień
  • Postów:1873
6

Tylko daty i tak bym przechowywał spójnie w UTC, a potem konwertował na potrzeby widoków.


”Engineering is easy. People are hard.” Bill Coughran
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:7 minut
  • Lokalizacja:Laska, z Polski
  • Postów:10056
2

@aliszja: Trzymaj czas w bazie w UTC i posługuj się nim wszędzie w aplikacji, i dopiero przy wyświetlaniu userowi daty wyświetl ją z wykorzystaniem strefy czasowej.

Charles_Ray
Przecież napisałem to post wcześniej wtf
aliszja
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:30
0

Dzięki za wszystkie Wasze podpowiedzi. Wnioski mam takie: Między LocalDateTime a OffsetDateTime jest znacząca różnica - taka, że OffsetDateTime reprezentuje punkt w czasie, a LocalDateTime po prostu reprezentuje datę. To był ważny kawałek informacji, którego mi było brak. Jeśli porównujemy ze sobą daty w różnych strefach czasowych, to tak naprawdę powinniśmy porównywać dwa punkty w czasie. Żeby stwierdzić, czy te punkty w czasie są zbieżne/większe/mniejsze, byłoby dobrze sprowadzić je do jednej strefy czasowej (np. UTC). ZonedDateTime byłby najbardziej "prawilny", bo ogarnia czas letni/zimowy i ma najwięcej informacji.

Większość problemów w tej aplikacji wynika z tego, że konwertując z LocalDateTime na OffsetDateTime i z powrotem, tracimy informację o tym, w jakim punkcie w czasie (momencie) się znajdujemy. Rozwiązaniem tego problemu byłoby upewnienie się, że w komunikacji z frontendem operujemy na momentach, a frontend interpretuje ten moment i wyświetla go użytkownikowi w jego strefie czasowej i w drugą stronę - frontend wysyła backendowi punkt w czasie.

Musimy też zadbać o to, żeby w bazie też zapisywać jakiś timestamp, a nie reprezentację daty.

edytowany 3x, ostatnio: aliszja
IV
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 2 miesiące
  • Postów:30
0

Jeśli łączysz się do bazy za pomocą sterownika JDBC to proponuję do zajrzenia do specyfikacji JDBC 4.2 załącznik B.
Jest tam napisane, że występują następujące mapowania:

Kopiuj
SQL Timestamp with timezone <-> JAVA OffsetDateTime
SQL Timestamp without timezone <-> JAVA LocalDateTime

Teraz musisz zobaczyć w dokumentacji sterownika JDBC do MySQL jak on te typy ze specyfikacji implementuje.

Używanie OffsetDateTime na backendzie oraz do komunikacji z backendem moim zdaniem jest najlepszym rozwiązaniem.

superdurszlak
  • Rejestracja:prawie 7 lat
  • Ostatnio:3 dni
  • Lokalizacja:Kraków
  • Postów:1999
0
Charles_Ray napisał(a):

Tylko daty i tak bym przechowywał spójnie w UTC, a potem konwertował na potrzeby widoków.

TIMESTAMP w MySQL może trzymać dane zarówno ze strefą, jak i bez, co mu podasz to przyjmie (przynajmniej od wersji 8 - nie wiem jak w starszych). Żeby było śmieszniej, według docsów MySQL uczynnie przechowuje wszystkie timestampy w UTC, ale przy wyciąganiu może to skonwertować do innej strefy, zależnie od tego co siedzi w zmiennej systemowej, więc trzeba o tym pamiętać :D

Jeszcze pytanie, co się wydarzy przy mapowaniu do/z bazy w razie, gdyby ta strefa UTC nie była explicite zawarta w samym timestamp. Wydaje mi się, że kiedyś naciąłem się na takich konwersjach i efekt końcowy był taki, że do tego co wypluł MySQL serwer beztrosko doklejał swoją systemową strefę i do widzenia :D Więc ja bym asekuracyjnie, ze względu na brak zaufania do tego czego używam zrobił tak:

  • Trzymał w bazie timestampy w UTC, ale explicite narzucił informację że to jest UTC. W Postgres jest to o tyle proste, że masz osobny typ kolumny TIMESTAMP i TIMESTAMP WITH TIME ZONE, a w MySQL najwyraźniej stosuje się logikę rozmytą...
  • Zdefiniował ten sysvar dla MySQL na UTC, żeby nie przyszło mu do głowy uczynnie robić niechcianych konwersji
  • Po stronie serwera również pilnował, żeby za strefę czasową przyjmował UTC a nie systemową (albo zmienił systemową). Możesz wstrzykiwać sobie java.time.Clock tam, gdzie majstrujesz coś z czasem. Przy okazji, mogąc wstrzykiwać zamiast polegać na domyślnym po którego sięgnie sobie jakiś Instant.now() lub inny ancymon łatwiej będzie testować jakieś dziwne przypadki w stylu a co jak user ma inną strefę, serwer ma inną strefę, a baza ma inną?

edytowany 1x, ostatnio: superdurszlak
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)