Zabawa z datami to odwieczny problem programistów. Nie inaczej ma to miejsce u mnie. Ostatnio musiałem się z tym zmierzyć w ramach swojego projektu Parking Domain. Problem dotyczył klasy ZonedDateTime
oraz Instant
, a dokładniej utworzenia Instant
z określoną godziną. Jak wiemy Instant.now()
reprezentuje punkt w czasie określony przez liczbę nanosekund liczoną od 1970-01-01T00:00:00Z. Z tego zapisu najbardziej istotna jest literka Z na końcu tej daty. Oznacza ona nic innego jak Zulu time. Korzystając z API klasy Instant
poruszamy się w strefie czasowej UTC±00:00 tak naprawdę o tym nie wiedząc.
Jeśli uruchomimy Instant.now()
w Polsce o godzinie 13:11 16 czerwca 2024 to otrzymamy - 2024-06-13T11:11:30.218825200Z. Jest on przedstawiony w strefie UTC uwzględniając przesunięcie.
Utrudnieniem staje się fakt, że na klasie Instant
nie ma możliwości zmiany np. godziny. Musimy zrobić pewne obejście wykorzystując do tego ZonedDateTime
.
1
2
3
4
5
6
ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault())
.withHour(12)
.withMinute(0)
.withSecond(0)
.withNano(0)
.toInstant();
Będąc w Polsce spodziewamy się dostać w wyniku godzinę 12 czasu Europe/Warsaw zmienioną na godzinę 10 czasu UTC. I tak się właśnie dzieje - 2024-06-13T10:00:00Z.
Załóżmy teraz, że jesteśmy w strefie UTC i chcemy wykonać tą samą operację.
1
2
3
4
5
6
ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("UTC"))
.withHour(12)
.withMinute(0)
.withSecond(0)
.withNano(0)
.toInstant();
Bez niespodzianek, otrzymaliśmy 2024-06-13T12:00:00Z. Czyli dwie godziny później niż to miało miejsce w poprzednim przypadku, ponieważ ustawiliśmy godzinę będąc już w strefie czasowej Zulu. Jednak co jeśli chcielibyśmy w tym drugim przypadku przeskoczyć do strefy czasowej CET? Musimy skorzystać z metody withZoneSameInstant
.
1
2
3
4
5
6
7
ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("UTC"))
.withHour(12)
.withMinute(0)
.withSecond(0)
.withNano(0)
.withZoneSameInstant(ZoneId.systemDefault())
.toInstant();
Znowu otrzymaliśmy 2024-06-13T12:00:00Z. Dlaczego? Ponieważ linijka withZoneSameInstant(ZoneId.systemDefault())
zmieniła nam ZonedDateTime
wykorzystując czas środkowoeuropejski, ale później skorzystaliśmy z metody toInstant
, która ponownie zmieniła nam nasz czas uwzględniając strefę Zulu. Łatwo się w tym pogubić, ja właśnie tak miałem.
Nie ułatwia nam jeszcze kwestia tego, że do dyspozycji mamy również metodę withZoneSameLocal
, która zmienia strefę zachowując czas.
1
2
3
4
5
6
7
ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("UTC"))
.withHour(12)
.withMinute(0)
.withSecond(0)
.withNano(0)
.withZoneSameLocal(ZoneId.systemDefault())
.toInstant();
Dla takiego przypadku dostajemy oczywiście 2024-06-13T10:00:00Z. W ten sposób uzmysłowiłem sobie jak ważne jest przekazywanie odpowiedniej daty w samej aplikacji oraz pomiędzy systemami. Warto w odpowiedni sposób taką datę przekonwertować do czasu Greenwich i informować o tym klienty naszej aplikacji, aby nie było żadnych nieporozumień.