W ubiegłym tygodniu w implementowanym systemie zajmowałem się datą i czasem. I tak mnie tknęło… dlaczego w ogromnej większości systemów, które przyszło mi oglądać, datę zapisuje się w sposób następujący?
1: article.PublishedTime = DateTime.Now
Niby co jest z tym złego? Właściwie… to zależy od kontekstu i rodzaju tworzonego systemu. Korzystając z powyższej konstrukcji trzeba zdawać sobie sprawę z tego, że tak zapisany czas jest ZALEŻNY od maszyny, na której operacja ma miejsce. Wartość ta odczytana na innym komputerze może najzwyczajniej w świecie nie mieć sensu.
Jednym z bardzo irytujących przykładów z życia wziętych jest chociażby silnik BlogEngine, na którym działa mój blog. Data utworzenia czy modyfikacji zarówno postów jak i komentarzy jest zapamiętywana właśnie w taki sposób. Serwer hostujący stronę znajduje się w USA (i należy do najnajlepsiejszej firmy OrcsWeb). Jak wiadomo w USA jest inna godzina niż tutaj. Co więc muszę zrobić? Metodą prób i błędów (bo i w USA są różne strefy czasowe) ustawić w konfiguracji BE ile godzin trzeba dodać bądź odjąć od zapisanej wartości podczas wyświetlania treści tak, aby miało to sens dla gościa z Polski. Aktualnie wartość ta wynosi 6, więc podczas gdy ja publikuję posta o 6:50 rano, do docelowej bazy zapisywana jest godzina 00:50. Przy wyświetlaniu posta te 6 godzin jest ponownie dodawane i niby wszystko jest cacy… dopóki nie będę chciał "zmigrować się" gdzieś indziej. Przy przenosinach na polski serwer musiałbym przeorać wszystkie daty zapisane na amerykańskim serwerze i pododawać do nich te durne 6 godzin.
Jeśli robię pewną czynność w systemie, a lądująca w bazie data jej wykonania będzie inna serwerach w różnych krajach… to coś jest nie tak. Przecież nie jestem Ojcem Pio, nie mam daru bilokacji (do tego czasoprzestrzennej)!
To jest po prostu GŁUPIE!
Co prawda jeszcze głupsze wyjście to całkowite zignorowanie tej kwestii i absolutne uniemożliwienie dostosowania wyświetlanej daty.
Ale ileż bardziej praktyczne byłoby zapisanie każdej daty jako UTC – Coordinated Universal Time, poprzez pobranie wartości DateTime.UtcNow! Mając czas absolutny, całkowicie niezależny od lokalizacji, można rozwiązać podobny problem na kilka sposobów.
Jeden z nich to zawarcie w konfiguracji informacji o docelowej strefie czasowej czytelnika podczas WYŚWIETLANIA? Konwersja z czasu UTC na określoną strefę czasową zawsze da ten sam wynik. Mój post powinien mieć dla mnie sens niezależnie od tego gdzie stoi serwer i jaka jest na nim aktualna godzina!
Sposób inny, jeszcze bardziej "globalny", to przesyłanie do przeglądarki daty w UTC i jej konwersja na strefę czasową komputera, na którym jest ona uruchomiona – w Javascript. Wtedy każdy zobaczy taki czas, jaki jest dla niego odpowiedni.
O ile w mniejszych/lokalnych systemach DateTime.Now sprawdzi się znakomicie i nawet nie przyjdzie nikomu do głowy zastanawiać się nad tym. A to niedobrze, bo decyzję taką należy podjąć świadomie. Przy poważniejszych rozwiązaniach warto zwrócić uwagę na to, czy na pewno aktualne ustawienia serwera powinny być dla aplikacji święte? Czy na pewno nie potrzebujemy danych interpretowalnych jednoznacznie w każdy warunkach? Wreszcie: czy zapisując wartość "TERAZ" na pewno z pełną odpowiedzialnością decydujemy się na kod z poniższego obrazka?
Na koniec, dla zainteresowanych tematem, "wyjściowy" link do MSDN: Time Zone Overview.
zobacz o ktorej zostal dodan trackback z maniaka :D System gdzies nie dodaje tych durnych godzin
@klm_:
heh no wlasnie… przynajmniej jest dowód że nie łżę:)
Date nalezy zawsze przechowywac w UTC i kalkulowac roznice do obecnej strefy czasowej na danym komputerze. Nigdy nie wiadomo gdzie trafi aplikacja/system, a to naprawde ulatwia pozniej wiele spraw :)
Nieco OT, ale być może dla kogoś to będzie przydatne – z obsługą UTC jest problem przy deserializacji. W firmie zrobiliśmy webservice WCF, który otrzymuje PESEL i datę urodzenia klienta, oraz sprawdza czy jedno z drugim się zgadza. W testach robionych ręcznie użyliśmy formatu: 1969-03-17T00:00:00.000+01:00 i wszystko pięknie działało. Niestety, okazało się że klient serwisu (java) podawał 1969-03-16T23:00:00.000Z, a to było odczytywane jako 16 marca (zamiast 17) przez .NET, co skutkowało komunikatem "niepoprawny PESEL".
Obejście: dla każdej daty trzeba robić coś takiego:
if (date.Kind == DateTimeKind.Utc) date = date.ToLocalTime();
Oprócz ewidentnego błędu widać, że przydałoby się wprowadzenie do .NET typu Date oprócz DateTime, żeby już się nie mordować ze strefami czasowymi gdy chodzi o przekazanie samej daty.