Jak podróżujemy to zdarza się, że zmieniamy strefy czasowe. Mają one wpływ na nasz zewnętrzny jak i wewnętrzny zegar, nie wspominając o wszystkich kalendarzach z których korzystaliśmy albo korzystaliśmy ŹLE.
Jeżeli jesteśmy na prawo od Greenwich to czas dodajemy, zaś na lewo odejmujemy. Proste prawda? Oczywiście, robią się komplikacje jak duże państwa albo państwa przez które przechodzi granica strefy czasowej. Pewnie jakbym tutaj poprosił Jona Skeeta to by on opowiedział jeszcze paręnaście anegdot związanych z czasem, a niektóre z nich są super zabawne – część z nich na pewno możecie usłyszeć na prezentacji jaką dał na DevDay 2013.
Ale nie o tym, choć z czasem powiązane. Do jednej z aplikacji z 2012 roku dostaliśmy ostatnio zgłoszenie o niezgodności czasu w bazie z tym co zostało zaznaczone przez użytkownika na stronie. Jako dobrzy obywatele od lat zapisujemy wszystko w UTC (dlaczego w UTC, wiele powodów – swoich przemyśleń nigdy nie zapisałem, ale zrobił to Maciej na devStyle, więc zachęcam do czytania), dzięki czemu raczej problemów nie mieliśmy z datami. Problem więc był dość dziwny. Do tego też zauważony dopiero teraz… po 4 latach – mhhh, nie ważne ;)
Przy czytaniu kodu po stronie klienta nie znaleźliśmy żadnego błędu, wszystko wyglądało świetnie. Ale patrzyliśmy na kod i za nic w świecie nie byliśmy wstanie odkryć co jest nie tak. Komponent był dziwny, prawda, przechowywał czas wewnętrznie w UTC i wykorzystywał offset z obiektu po to by potem przywracać czas do wersji lokalnej/przeglądarki. Czyli wszystko to co miał wewnętrznie miało datę i godzinę w UTC mimo iż na przykład pisało GMT+0200 (Central European Daylight Time)
. Sam komponent wiedział jak z tym ma działać. Do tego zapis UTC nie był dostępny zewnątrz, tylko wewnętrznie. Dlaczego tak zrobili twórcy komponentu? Nie wiem. Nie wnikam w coś co miało miejsce 4 lata temu, nie ma sensu tutaj.
By zapisywać datę i godzinę w formie UTC, komponent na różne sposoby tworzył sobie datę:
var today = new Date(); var utc01 = new Date(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate(), today.getUTCHours(), today.getUTCMinutes(), today.getUTCSeconds()); var utc02 = new Date(today.valueOf() - today.getTimezoneOffset() * 60000); var utc03 = new Date(today.getTime() - today.getTimezoneOffset() * 60000);
Przy pobraniu zaś zwracał datę dodając getTimezoneOffset()
.
Czytając ten kod, widzicie gdzieś błąd?
Mi osobiście długo to zajęło – za długo.
today.getTime() > 1472825301705 today.getTime() - today.getTimezoneOffset() > 1472825301825 1472825301705 - 1472825301825 > -120 today.getTimezoneOffset() > -120
Widzie do czego zmierzam? W JavaScript inaczej niż na przyład w C# czy Java:
// C# TimeZoneInfo.Local.GetUtcOffset(DateTime.Now); > 02:00:00 // Java (wybaczcie za mój kod) Date date = new Date(); TimeZone london = TimeZone.getTimeZone("Europe/London"); Date > Fri Sep 02 14:24:15 GMT 2016 london.getOffset(date.getTime()) > 3600000 // = 1h
getTimezoneOffset
jest… odwrotny do tego co powinno być. Czyli dla nas, Polaków to jest -120
, czyli -2h
zamiast +2h
. Czyli wszystkie operacje, wykonywane przez komponent były na odwrót, i za każdym razem jak się ustawiło godzinę tym bardziej się wszystko psuło. Problem był ciężki do rozwiązania w naszym przypadku gdyż biblioteka ta była silnie uzależniona od tych różnych operacji – w szczególności iż powinno to działać zarówno dla nas Polaków jak i Amerykanów. W ostateczności po prostu usunęliśmy wszelki kombinacje zmiany daty na UTC.
Tak jak po stronie serwera trzymanie tego w bazie danych w formie UTC ma ręce i nogi. Tak tworzenie komponentu webowego, który wyświetla datę lokalnie a trzyma ją w UTC dla mnie osobiście mija się z celem. W szczególności iż trzeba się męczyć z takimi PROBLEM jak getTimezoneOffet()
który działa tak samo jak JavaScript – po swojemu ;)
Daty często potrafią być złośliwe :) Przy okazji chciałbym poruszyć temat przekazywania daty serwer -> klient. Jeśli się przypatrzeć historii ASP.NET MVC (od 3 do 5) i JSON.NET to według mnie narósł niezły bałagan. JSON nie ma oficjalnie czegoś takiego jak data (silnie typowane pole), ale przez lata ludzie próbowali (nadal?) to hackować. Wystarczy rzucić okiem na:
http://www.newtonsoft.com/json/help/html/datesinjson.htm (3 formaty serializacji!)
Ja aktualnie stosuję domyślą serializację, czyli jako string w formacie ISO8601. Mowa tutaj o zwracaniu JSON-a z akcji MVC. Następnie na kliencie, parsuje to (jest to upierdliwe) na Date i od tego momentu trzymam się silnie typowanej daty (komponenty, store itp.). Od użytkownika też od razu parsuje na obiekt Date. Jakie Wy macie podejście?
Wracając do tematu bloga, nie wyrobiłem sobie jeszcze zdania. Z tego co widzę, w ostatnim projekcie miałem taką samą zasadę jak Ty, czyli:
– serwer -> klient (utc z bazy na lokalną datę)
– klient (lokalna data)
– klient -> serwer (lokalna data)
– serwer -> baza (lokalna na utc przed zapisem)
Ręczne bawienie się datą/czasem często źle się kończy :) Jak coś robię na kliencie to najczęściej używam biblioteki moment.js (parsowanie, wyświetlanie, przesuwanie).
http://momentjs.com/
Opisany problem jest jednym z wielu ‘ficzerow’ Date object w JavaScript: http://jj09.net/javascript-date-a-bad-part/ ;)
Comments are closed.