Mam witrynę internetową, która jest hostowana w innej strefie czasowej niż użytkownicy korzystający z aplikacji. Oprócz tego użytkownicy mogą mieć określoną strefę czasową. Zastanawiałem się, jak podchodzą do tego inni użytkownicy SO i aplikacje? Najbardziej oczywistą częścią jest to, że w DB data / czas są przechowywane w UTC. Na serwerze wszystkie daty / godziny powinny być obsługiwane w czasie UTC. Widzę jednak trzy problemy, które próbuję rozwiązać:
Pobieranie aktualnego czasu w UTC (łatwe rozwiązanie za pomocą
DateTime.UtcNow
).Pobieranie daty / godzin z bazy danych i wyświetlanie ich użytkownikowi. Potencjalnie jest wiele wezwań do drukowania dat w różnych widokach. Myślałem o jakiejś warstwie między widokiem a kontrolerami, która mogłaby rozwiązać ten problem. Lub mając włączoną niestandardową metodę rozszerzenia
DateTime
(patrz poniżej). Główną wadą jest to, że w każdym miejscu korzystania z daty i godziny w widoku należy wywołać metodę rozszerzenia!To również utrudniłoby używanie czegoś takiego jak
JsonResult
. Nie można już było łatwo zadzwonićJson(myEnumerable)
, musiałoby byćJson(myEnumerable.Select(transformAllDates))
. Może AutoMapper mógłby pomóc w tej sytuacji?Pobieranie danych od użytkownika (lokalnie do UTC). Na przykład POST przesłanie formularza z datą wymagałoby konwersji daty na UTC wcześniej. Pierwszą rzeczą, która przychodzi na myśl, jest stworzenie niestandardowego
ModelBinder
.
Oto rozszerzenia, których użyłem w widokach:
public static class DateTimeExtensions
{
public static DateTime UtcToLocal(this DateTime source,
TimeZoneInfo localTimeZone)
{
return TimeZoneInfo.ConvertTimeFromUtc(source, localTimeZone);
}
public static DateTime LocalToUtc(this DateTime source,
TimeZoneInfo localTimeZone)
{
source = DateTime.SpecifyKind(source, DateTimeKind.Unspecified);
return TimeZoneInfo.ConvertTimeToUtc(source, localTimeZone);
}
}
Myślę, że zajmowanie się strefami czasowymi byłoby tak powszechne, biorąc pod uwagę, że wiele aplikacji jest teraz opartych na chmurze, gdzie lokalny czas serwera może być znacznie inny niż oczekiwana strefa czasowa.
Czy zostało to wcześniej elegancko rozwiązane? Czy jest coś, czego mi brakuje? Bardzo cenione są pomysły i przemyślenia.
EDYCJA: Aby usunąć zamieszanie, pomyślałem, że dodaj więcej szczegółów. Kwestia teraz nie ma jak do przechowywania UTC razy w db, to więcej o procesie dzieje z UTC-> Lokalne> szczeblu lokalnym i UTC. Jak wskazuje @Max Zerbini, oczywiście mądrze jest umieścić kod UTC-> lokalny w widoku, ale czy DateTimeExtensions
naprawdę używa się odpowiedzi? Czy podczas uzyskiwania danych wejściowych od użytkownika sensowne jest akceptowanie dat jako czasu lokalnego użytkownika (ponieważ tego właśnie używałby JS), a następnie używanie ModelBinder
do przekształcenia na UTC? Strefa czasowa użytkownika jest przechowywana w bazie danych i można ją łatwo odzyskać.
źródło
ModelBinder
.Odpowiedzi:
Nie chodzi o to, że jest to zalecenie, bardziej rozpowszechnianie paradygmatu, ale najbardziej agresywny sposób obsługi informacji o strefie czasowej w aplikacji internetowej (który nie dotyczy wyłącznie ASP.NET MVC), jaki widziałem, był następujący:
Wszystkie daty na serwerze to UTC. Oznacza użyciu, tak jak powiedział
DateTime.UtcNow
.Staraj się jak najmniej ufać klientowi, który przekazuje daty na serwer. Na przykład, jeśli potrzebujesz „teraz”, nie twórz daty na kliencie, a następnie przekazuj ją do serwera. Utwórz datę w GET i przekaż ją do ViewModel lub POST do
DateTime.UtcNow
.Jak dotąd dość standardowa taryfa, ale tutaj robi się „interesująco”.
Jeśli musisz zaakceptować datę od klienta, użyj javascript, aby upewnić się, że dane, które wysyłasz na serwer, są w czasie UTC. Klient wie, w jakiej strefie czasowej się znajduje, więc może z rozsądną dokładnością przeliczyć czasy na UTC.
Podczas renderowania widoków używali
<time>
elementu HTML5 , nigdy nie renderowali dat dat bezpośrednio w ViewModel. Został zaimplementowany jakoHtmlHelper
rozszerzenie, coś w rodzajuHtml.Time(Model.when)
. To by się renderowało<time datetime='[utctime]' data-date-format='[datetimeformat]'></time>
.Następnie użyliby javascript do przetłumaczenia czasu UTC na czas lokalny klienta. Skrypt znalazłby wszystkie
<time>
elementy i użyłbydate-format
właściwości data do sformatowania daty i zapełnienia zawartości elementu.W ten sposób nigdy nie musieli śledzić, przechowywać ani zarządzać strefą czasową klientów. Serwer nie przejmował się strefą czasową klienta, ani nie musiał wykonywać żadnych tłumaczeń stref czasowych. Po prostu wypluł UTC i pozwolił klientowi przekształcić go w coś rozsądnego. Co jest łatwe z poziomu przeglądarki, ponieważ wie, w jakiej strefie czasowej się znajduje. Jeśli klient zmieni swoją strefę czasową, aplikacja internetowa zaktualizuje się automatycznie. Jedyne, co zapisali, to łańcuch formatu daty i godziny dla ustawień regionalnych użytkownika.
Nie mówię, że to było najlepsze podejście, ale było to inne, którego wcześniej nie widziałem. Może wyciągniesz z niego kilka interesujących pomysłów.
źródło
<time>
Pomysł jest całkiem fajny. Jedyną wadą jest to, że muszę odpytać cały DOM pod kątem tych elementów, co sama zmiana dat nie jest przyjemna (a co z wyłączonym JS?).Po kilku informacjach zwrotnych, oto moje ostateczne rozwiązanie, które moim zdaniem jest czyste i proste i obejmuje kwestie związane z czasem letnim.
1 - Konwersję wykonujemy na poziomie modelu. Tak więc w klasie Model piszemy:
2 - W globalnym pomocniku tworzymy naszą niestandardową funkcję „ToLocalTime”:
3 - Możemy to jeszcze bardziej ulepszyć, zapisując identyfikator strefy czasowej w każdym profilu użytkownika, dzięki czemu możemy pobierać dane z klasy użytkownika zamiast używać stałej wartości „China Standard Time”:
4 - Tutaj możemy uzyskać listę stref czasowych, które użytkownik może wybrać z rozwijanego menu:
Tak więc teraz o 9:25 w Chinach, strona internetowa hostowana w USA, data zapisana w bazie danych UTC, oto wynik końcowy:
EDYTOWAĆ
Dziękuję Mattowi Johnsonowi za wskazanie słabych części oryginalnego rozwiązania i przepraszam za usunięcie oryginalnego posta, ale mam problemy z uzyskaniem odpowiedniego formatu wyświetlania kodu ... Okazało się, że edytor ma problemy z mieszaniem "punktorów" z "kodem wstępnym", więc ja usunąłem byki i było dobrze.
źródło
W sekcji wydarzeń na sf4answers użytkownicy wprowadzają adres wydarzenia, a także datę rozpoczęcia i opcjonalną datę zakończenia. Te czasy są tłumaczone na
datetimeoffset
serwer SQL, który uwzględnia przesunięcie względem czasu UTC.To jest ten sam problem, przed którym stoisz (chociaż podchodzisz do niego inaczej, ponieważ używasz
DateTime.UtcNow
); masz lokalizację i musisz przetłumaczyć czas z jednej strefy czasowej na drugą.Zrobiłem dwie główne rzeczy, które mi pomogły. Po pierwsze , zawsze używaj
DateTimeOffset
struktury . Uwzględnia potrącenie z UTC i jeśli możesz uzyskać te informacje od swojego klienta, ułatwi Ci to życie.Po drugie, wykonując tłumaczenia, zakładając, że znasz lokalizację / strefę czasową, w której znajduje się klient, możesz użyć publicznej bazy danych stref czasowych, aby przetłumaczyć czas z UTC na inną strefę czasową (lub triangulować, jeśli chcesz, między dwiema strefy czasowe). Wspaniałą rzeczą w bazie danych tz (czasami nazywanej bazą danych Olson ) jest to, że uwzględnia ona zmiany stref czasowych w historii; uzyskanie przesunięcia jest funkcją daty, na którą chcesz je uzyskać (wystarczy spojrzeć na ustawę o polityce energetycznej z 2005 r., która zmieniła daty wejścia w życie czasu letniego w USA ).
Mając bazę danych w ręku, możesz użyć interfejsu API ZoneInfo (baza danych tz / baza danych Olson) .NET . Zwróć uwagę, że nie ma dystrybucji binarnej, musisz pobrać najnowszą wersję i samodzielnie ją skompilować.
W chwili pisania tego tekstu analizuje on obecnie wszystkie pliki w najnowszej dystrybucji danych (faktycznie uruchomiłem go z plikiem ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz 25 września, 2011; w marcu 2017 r. Można go było dostać za pośrednictwem https://iana.org/time-zones lub z ftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz ).
Tak więc w odpowiedziach sf4, po uzyskaniu adresu, jest on geokodowany w kombinacji szerokość / długość geograficzna, a następnie wysyłany do usługi internetowej innej firmy w celu uzyskania strefy czasowej, która odpowiada wpisowi w bazie danych tz. Stamtąd czasy rozpoczęcia i zakończenia są konwertowane na
DateTimeOffset
wystąpienia z odpowiednim przesunięciem czasu UTC, a następnie zapisywane w bazie danych.Jeśli chodzi o radzenie sobie z tym w SO i witrynach internetowych, zależy to od odbiorców i tego, co próbujesz wyświetlić. Jeśli zauważysz, większość serwisów społecznościowych (i SO oraz sekcja wydarzeń w sf4answers) wyświetla zdarzenia w czasie względnym lub, jeśli używana jest wartość bezwzględna, zwykle jest to UTC.
Jeśli jednak Twoi odbiorcy oczekują czasu lokalnego, użycie
DateTimeOffset
wraz z metodą rozszerzenia, która pobiera strefę czasową do konwersji, byłoby w porządku; typ danych SQLdatetimeoffset
przetłumaczyłby się na .NET,DateTimeOffset
który można następnie uzyskać uniwersalny czas stosowaniaGetUniversalTime
metody . Stamtąd po prostu używasz metodZoneInfo
klasy do konwersji z UTC na czas lokalny (będziesz musiał trochę popracować, aby wprowadzić go do aDateTimeOffset
, ale jest to dość proste).Gdzie dokonać transformacji? To koszt, który będziesz musiał gdzieś zapłacić , i nie ma „najlepszego” sposobu. Wybrałbym jednak widok, z przesunięciem strefy czasowej jako częścią modelu widoku prezentowanego w widoku. W ten sposób, jeśli zmienią się wymagania dotyczące widoku, nie musisz zmieniać modelu widoku, aby uwzględnić zmianę. Twoja
JsonResult
po prostu zawierać model z i offset.IEnumerable<T>
Po stronie wejściowej, używając segregatora modelowego? Powiedziałbym, że nie ma mowy. Nie możesz zagwarantować, że wszystkie daty (teraz lub w przyszłości) będą musiały zostać przekształcone w ten sposób, powinno to być jawną funkcją twojego kontrolera, aby wykonać tę akcję. Ponownie, jeśli wymagania ulegną zmianie, nie musisz dostosowywać jednego lub wielu
ModelBinder
wystąpień, aby dostosować logikę biznesową; i jest to logika biznesowa, co oznacza, że powinna znajdować się w kontrolerze.źródło
datetimeoffset
w SQL Server iDateTimeOffset
.NET tutaj, naprawdę bardzo upraszczają one sprawę), które moim zdaniem .NET nie obsługuje odpowiednio w ogóle z powodów przedstawionych powyżej. Jeśli masz datę w Nowym Jorku, która została wprowadzona w 2003 r., A następnie chcesz ją przetłumaczyć na datę w Los Angeles w 2011 r., W takim przypadku .NET ciężko zawodzi.DateTimeOffset
, bardzo to złagodzisz, IMO).To tylko moja opinia, uważam, że aplikacja MVC powinna dobrze oddzielać problem prezentacji danych od zarządzania modelem danych. Baza danych może przechowywać dane w czasie lokalnego serwera, ale obowiązkiem warstwy prezentacji jest renderowanie daty i godziny przy użyciu lokalnej strefy czasowej użytkownika. Wydaje mi się, że to ten sam problem, co I18N i format liczb dla różnych krajów. W Twoim przypadku aplikacja powinna wykryć
Culture
strefę czasową użytkownika i zmienić Widok, pokazując inny tekst, liczbę i datę, ale przechowywane dane mogą mieć ten sam format.źródło
DateTimeExtensions
.W przypadku danych wyjściowych utwórz szablon wyświetlania / edytora, taki jak ten
Możesz powiązać je na podstawie atrybutów w modelu, jeśli chcesz, aby tylko niektóre modele korzystały z tych szablonów.
Zobacz tutaj i tutaj, aby uzyskać więcej informacji na temat tworzenia niestandardowych szablonów edytorów.
Alternatywnie, ponieważ chcesz, aby działał zarówno dla wejścia, jak i wyjścia, sugerowałbym rozszerzenie kontroli lub nawet utworzenie własnej. W ten sposób możesz przechwycić zarówno dane wejściowe, jak i wyjściowe i przekonwertować tekst / wartość w razie potrzeby.
Mam nadzieję, że ten link popchnie cię we właściwym kierunku, jeśli chcesz iść tą ścieżką.
Tak czy inaczej, jeśli chcesz eleganckiego rozwiązania, będzie to trochę pracy. Z drugiej strony, kiedy już to zrobisz, możesz zachować to w swojej bibliotece kodu do wykorzystania w przyszłości!
źródło
DisplayFor
iEditorFor
będzie działać za każdym razem. +1Jest to prawdopodobnie młot kowalski do złamania orzecha, ale można wprowadzić warstwę między warstwą interfejsu użytkownika a warstwą biznesową, która w sposób przejrzysty konwertuje czasy danych na czas lokalny na zwracanych grafach obiektów oraz na czas UTC na wejściowych parametrach daty i godziny.
Wyobrażam sobie, że można to osiągnąć za pomocą PostSharp lub jakiejś inwersji kontenera sterującego.
Osobiście wybrałbym po prostu jawną konwersję dat w interfejsie użytkownika ...
źródło