Jak elegancko radzić sobie ze strefami czasowymi

Mam stronę internetową, która jest hostowana w innej strefie czasowej niż użytkownicy korzystający z aplikacji. Ponadto użytkownicy mogą mieć określoną strefę czasową. Zastanawiałem się, jak podchodzą do tego inni użytkownicy i aplikacje SO? Najbardziej oczywistą częścią jest to, że w DB data / godzina są przechowywane w UTC. Kiedy na serwerze, wszystkie daty/godziny powinny być rozpatrywane w UTC. Jednak widzę trzy problemy, które staram się przezwyciężyć:

  1. Uzyskanie aktualnego czasu w UTC (łatwo rozwiązane z DateTime.UtcNow).

  2. Pobranie daty/czasu z bazy danych i wyświetlenie ich użytkownikowi. Istnieje potencjalnie wiele wywołań do drukowania dat w różnych widokach. Myślałem o jakiejś warstwie pomiędzy widokiem a kontrolerami, która mogłaby rozwiązać ten problem. Lub posiadające niestandardową metodę rozszerzenia na DateTime (patrz poniżej). Główną stroną w dół jest to, że w każdej lokalizacji używającej datetime w widoku, metoda rozszerzenia musi być wywołana!

    To Dodaj również trudności w używaniu czegoś takiego jak JsonResult. Nie można już Łatwo zadzwonić Json(myEnumerable), musiałoby to być Json(myEnumerable.Select(transformAllDates)). Może AutoMapper mógłby pomóc w tej sytuacji?

  3. Pobieranie danych wejściowych od użytkownika (lokalne do UTC). Na przykład opublikowanie formularza z datą wymagałoby konwersji daty NA UTC przed. Pierwszą rzeczą, która przychodzi na myśl, jest stworzenie niestandardowego ModelBinder.

Oto rozszerzenia, które myślałem o użyciu 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);
    }
}

I would myślę, że radzenie sobie z strefami czasowymi byłoby tak powszechne, biorąc pod uwagę wiele aplikacji jest teraz opartych na chmurze, gdzie czas lokalny serwera może być znacznie inny niż oczekiwana Strefa czasowa.

Czy to już zostało rozwiązane? Czy coś mi umyka? Pomysły i myśli są bardzo cenione.

EDIT: aby wyjaśnić pewne zamieszanie, pomyślałem, że dodam więcej szczegółów. Obecnie problemem nie jest Jak przechowywać czasy UTC w db, to bardziej o procesie przechodzenia z UTC - > Local i Local- > UTC. Jak zaznacza @Max Zerbini, oczywiście mądrze jest umieścić w widoku Kod UTC->lokalny, ale czy użycie DateTimeExtensions naprawdę jest odpowiedzią? Czy podczas uzyskiwania danych wejściowych od użytkownika ma sens akceptowanie dat jako czasu lokalnego użytkownika (ponieważ tego używa JS), a następnie użycie ModelBinder do przekształcenia NA UTC? Strefa czasowa użytkownika jest przechowywana w DB i jest łatwo pobierana.

Author: TheCloudlessSky, 2011-09-28

6 answers

Nie to, że jest to rekomendacja, jej bardziej dzielenie się paradygmatem, ale najbardziej agresywny sposób, jaki widziałem w obsłudze informacji strefy czasowej w aplikacji internetowej (która nie jest wyłączna dla ASP.NET MVC) był następujący:

  • Wszystkie daty na serwerze są UTC. To oznacza używanie, jak powiedziałeś, DateTime.UtcNow.

  • Staraj się nie ufać klientowi przekazującemu daty do serwera w jak najmniejszym stopniu. Na przykład, jeśli potrzebujesz "teraz", nie twórz daty na kliencie a następnie przekazać go do serwera. Albo utwórz datę w GET i przekaż ją do ViewModel lub POST do DateTime.UtcNow.

Jak na razie dość standardowa taryfa, ale tutaj robi się "ciekawie".

  • Jeśli musisz zaakceptować datę od klienta, użyj javascript, aby upewnić się, że dane, które publikujesz na serwerze, są w UTC. Klient wie, w jakiej strefie czasowej się znajduje, dzięki czemu może z rozsądną dokładnością przeliczyć czasy na UTC.

  • Podczas renderowania widoki używały elementu HTML5 <time>, nigdy nie renderowały datetimes bezpośrednio w modelu widoku. Zostało zaimplementowane jako rozszerzenie HtmlHelper, coś w rodzaju Html.Time(Model.when). Renderuje <time datetime='[utctime]' data-date-format='[datetimeformat]'></time>.

    Wtedy użyliby javascript, aby przetłumaczyć czas UTC na czas lokalny klienta. Skrypt znajdzie wszystkie elementy <time> i użyje właściwości date-format data do sformatowania daty i wypełnienia zawartości element.

W ten sposób nigdy nie musieli śledzić, przechowywać ani zarządzać strefą czasową klientów. Serwer nie dbał o to, w jakiej strefie czasowej znajduje się klient, ani nie musiał wykonywać żadnych tłumaczeń stref czasowych. Po prostu wypluł UTC i pozwolił klientowi przekształcić to w coś rozsądnego. Co jest łatwe z przeglądarki, ponieważ wie, w jakiej strefie czasowej jest. Jeśli klient zmieni swoją strefę czasową, aplikacja internetowa automatycznie się zaktualizuje. Jedyne co oni przechowywany był łańcuch formatu datetime dla ustawień regionalnych użytkownika.

Nie mówię, że to było najlepsze podejście, ale było inne, którego wcześniej nie widziałem. Może uda Ci się zebrać kilka ciekawych pomysłów.

 97
Author: James Holmes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-14 09:40:48

Po kilku sprzężeniach zwrotnych, oto moje ostateczne rozwiązanie, które moim zdaniem jest czyste i proste i obejmuje problemy z czasem letnim.

1-zajmujemy się konwersją na poziomie modelu. Tak więc w klasie modelowej piszemy:

    public class Quote
    {
        ...
        public DateTime DateCreated
        {
            get { return CRM.Global.ToLocalTime(_DateCreated); }
            set { _DateCreated = value.ToUniversalTime(); }
        }
        private DateTime _DateCreated { get; set; }
        ...
    }

2-w globalnym helperze wykonujemy naszą niestandardową funkcję "ToLocalTime":

    public static DateTime ToLocalTime(DateTime utcDate)
    {
        var localTimeZoneId = "China Standard Time";
        var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(localTimeZoneId);
        var localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localTimeZone);
        return localTime;
    }

3 - możemy to jeszcze poprawić, zapisując identyfikator strefy czasowej w każdym profilu użytkownika, abyśmy mogli pobrać z klasy użytkownika zamiast używać stałej " China Standard Czas": {]}

public class Contact
{
    ...
    public string TimeZone { get; set; }
    ...
}

4 - Tutaj możemy uzyskać listę strefy czasowej do wyświetlenia użytkownikowi, aby wybrać z dropdownbox:

public class ListHelper
{
    public IEnumerable<SelectListItem> GetTimeZoneList()
    {
        var list = from tz in TimeZoneInfo.GetSystemTimeZones()
                   select new SelectListItem { Value = tz.Id, Text = tz.DisplayName };

        return list;
    }
}

Więc, teraz o 9:25 AM w Chinach, strona hostowana w USA, Data zapisana w bazie UTC w bazie danych, Oto ostateczny wynik:

5/9/2013 6:25:58 PM (Server - in USA) 
5/10/2013 1:25:58 AM (Database - Converted UTC)
5/10/2013 9:25:58 AM (Local - in China)

EDIT

Podziękowania dla Matt Johnson za wskazanie słabych części oryginalnego rozwiązania i przepraszam za usunięcie oryginalnego postu, ale mam problemy z poprawnym formatem wyświetlania kodu... okazało się, że redaktor ma problemy mieszanie "bullets" z "pre code", więc usunąłem Bulle i było ok.

 10
Author: Nestor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:46:59

W sekcji zdarzenia na sf4answers , użytkownicy wprowadzają adres zdarzenia, a także datę rozpoczęcia i opcjonalną datę zakończenia. Czasy te tłumaczone są na datetimeoffset W Sql server, który odpowiada offsetowi od UTC.

Jest to ten sam problem, z którym się borykasz (chociaż podchodzisz do Niego inaczej, ponieważ używasz DateTime.UtcNow); masz lokalizację i musisz przetłumaczyć czas z jednej strefy czasowej na kolejny.

Są dwie główne rzeczy, które zrobiłem, które działały dla mnie. Po pierwsze, użyj DateTimeOffset Struktura, zawsze. Odpowiada za przesunięcie z UTC i jeśli możesz uzyskać te informacje od swojego klienta, ułatwi Ci to życie.

Po drugie, podczas wykonywania tłumaczeń, zakładając, że znasz lokalizację / strefę czasową, w której znajduje się klient, możesz użyć public info time zone database , aby przetłumaczyć czas z UTC do innej strefy czasowej (lub triangulować, jeśli chcesz will, między dwiema strefami czasowymi). Wspaniałą rzeczą w bazie danych TZ (czasami określanej jako Olson database) jest to, że uwzględnia zmiany w strefach czasowych w całej historii; uzyskanie przesunięcia jest funkcją daty, na którą chcesz uzyskać przesunięcie (wystarczy spojrzeć na Energy Policy Act of 2005, która zmieniła daty, kiedy czas letni wchodzi w życie w USA ).

Mając bazę danych w ręku, możesz użyć ZoneInfo (tz database / Olson database). NET API . Zauważ, że nie ma dystrybucji binarnej, musisz pobrać najnowszą wersję i skompilować ją samodzielnie.

W momencie pisania tego tekstu, obecnie parsuje wszystkie pliki w najnowszej dystrybucji danych (w rzeczywistości uruchomiłem go z ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz Plik z dnia 25 września 2011; w marcu 2017, dostaniesz go za pośrednictwem https://iana.org/time-zones lub od ftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz).

Więc na sf4answers, po uzyskaniu adresu, jest on geokodowany na kombinację szerokości i długości geograficznej, a następnie wysyłany do zewnętrznego serwisu internetowego, aby uzyskać strefę czasową, która odpowiada wpisowi w bazie danych tz. Następnie czasy rozpoczęcia i zakończenia są konwertowane na instancje DateTimeOffset z odpowiednim przesunięciem UTC, a następnie przechowywane w bazie danych.

Jeśli chodzi o radzenie sobie z tym NA SO I stronach internetowych, to zależy od publiczność i to, co próbujesz wyświetlić. Jeśli zauważysz, że większość witryn społecznościowych (a więc i sekcja wydarzenia na sf4answers) wyświetla zdarzenia w względnym czasie lub, jeśli używana jest wartość bezwzględna, zwykle jest to UTC.

Jeśli jednak odbiorcy oczekują czasu lokalnego, użycie DateTimeOffset wraz z metodą rozszerzenia, która zajmuje strefę czasową do konwersji, byłoby w porządku; typ danych SQL datetimeoffset przełoży się na. Net DateTimeOffset, który można następnie uzyskać Czas uniwersalny dla strefy czasowej. korzystanie z GetUniversalTime metoda . Stamtąd po prostu używasz metod klasy ZoneInfo do konwersji z czasu UTC na czas lokalny (musisz trochę popracować, aby przekształcić go w DateTimeOffset, ale jest to wystarczająco proste).

Gdzie dokonać transformacji? To jest koszt, który będziesz musiał zapłacić gdzieś, i nie ma "najlepszego" sposobu. Wybrałbym jednak widok, z przesunięciem strefy czasowej jako częścią modelu widoku przedstawionego w widoku. W ten sposób, jeśli wymagania dla Zmień widok, nie musisz zmieniać modelu widoku, aby dostosować się do zmiany. Twój JsonResult zawierałaby po prostu model z IEnumerable<T> i offset.

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, powinna to być wyraźna funkcja kontrolera, aby wykonać tę akcję. Ponownie, jeśli wymagania Zmień, nie musisz poprawiać jednego lub wielu ModelBinder instancje, aby dostosować swoją logikę biznesową; i to jest logika biznesowa, co oznacza, że powinna być w kontrolerze.

 8
Author: casperOne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-07 15:44:41

To tylko moja opinia, myślę, że aplikacja MVC powinna oddzielić problem prezentacji danych od zarządzania modelami danych. Baza danych może przechowywać dane w lokalnym czasie serwera, ale obowiązkiem warstwy prezentacji jest renderowanie datetime przy użyciu lokalnej strefy czasowej użytkownika. Wydaje mi się, że jest to ten sam problem co I18N i format liczb dla różnych krajów. W Twoim przypadku aplikacja powinna wykryć Culture i strefę czasową użytkownika i zmienić widok wyświetlający inny tekst, liczbę i datę prezentacji, ale przechowywane dane mogą mieć ten sam format.

 5
Author: Max Zerbini,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-28 15:29:37

Dla wyjścia, Utwórz szablon display / editor w ten sposób

@inherits System.Web.Mvc.WebViewPage<System.DateTime>
@Html.Label(Model.ToLocalTime().ToLongTimeString()))

Możesz powiązać je na podstawie atrybutów modelu, jeśli chcesz, aby tylko niektóre modele używały tych szablonów.

Zobacz tutaj i tutaj aby uzyskać więcej szczegółów na temat tworzenia niestandardowych szablonów edytora.

Alternatywnie, ponieważ chcesz, aby działał zarówno na wejściu, jak i na wyjściu, sugerowałbym rozszerzenie kontroli lub nawet utworzenie własnej. W ten sposób można przechwycić zarówno wejścia, jak i wyjścia i Konwertuj 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, to będzie trochę pracy. Z drugiej strony, gdy już to zrobisz, możesz go zachować w swojej bibliotece kodów do wykorzystania w przyszłości!

 2
Author: dnatoli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-07 05:38:14

Jest to prawdopodobnie młot do złamania nakrętki, ale można wprowadzić warstwę między interfejsem i warstwami biznesowymi, która transparentnie konwertuje daty na czas lokalny na zwracanych wykresach obiektów i na UTC na wejściowych parametrach datetime.

Wyobrażam sobie, że można to osiągnąć za pomocą PostSharp lub jakiejś inwersji kontenera sterującego.

Osobiście wybrałbym jawną konwersję danych w interfejsie użytkownika...

 0
Author: geoffreys,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-04 23:25:51