Zaman dilimleri ile zarif bir şekilde nasıl baş edilir


140

Uygulamayı kullanan kullanıcılardan farklı bir saat diliminde barındırılan bir web sitem var. Buna ek olarak, kullanıcılar belirli bir saat dilimine sahip olabilirler. Diğer SO kullanıcıları ve uygulamalarının buna nasıl yaklaştığını merak ediyordum? En belirgin kısım, DB içinde tarih / saatlerin UTC'de depolanmasıdır. Sunucudayken, tüm tarih / saatler UTC olarak ele alınmalıdır. Ancak üstesinden gelmeye çalıştığım üç sorun görüyorum:

  1. UTC'de şimdiki zamanı almak (ile kolayca çözülür DateTime.UtcNow).

  2. Veritabanından tarih / saatlerin alınması ve kullanıcıya gösterilmesi. Farklı görünümlerde tarihleri ​​yazdırmak için potansiyel olarak çok sayıda çağrı vardır. Bu sorunu çözebilecek görüş ve kontrolörler arasında bir katman düşünüyordum. Veya üzerinde özel bir uzantı yöntemi kullanmak DateTime(aşağıya bakın). En önemli tarafı, görünümde tarih saatinin kullanıldığı her yerde, uzantı yönteminin çağrılması gerektiğidir!

    Bu, gibi bir şeyi kullanmakta zorluk ekler JsonResult. Artık kolayca arayamazsınız Json(myEnumerable), olması gerekirdi Json(myEnumerable.Select(transformAllDates)). Belki AutoMapper bu durumda yardımcı olabilir?

  3. Kullanıcıdan girdi alma (Yerelden UTC'ye). Örneğin, bir formun tarihle birlikte gönderilmesi için tarihin önceden UTC'ye dönüştürülmesi gerekir. Akla ilk gelen şey bir gelenek yaratmaktır ModelBinder.

İşte görünümlerde kullanmayı düşündüğüm uzantılar:

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);
    }
}

Zaman dilimleri ile uğraşmanın, sunucunun yerel saatinin beklenen saat diliminden çok farklı olabileceği birçok uygulama artık bulut tabanlı olduğu düşünülürse, bu kadar yaygın bir şey olacağını düşünürdüm.

Bu daha önce zarif bir şekilde çözüldü mü? Kaçırdığım bir şey var mı? Fikirler ve düşünceler çok takdir edilmektedir.

EDIT: Biraz karışıklık gidermek için biraz daha ayrıntı eklemek düşündüm. Sorun şu anda UTC saatlerini db'de nasıl saklayacağınız değil , daha çok UTC-> Yerel ve Yerel-> UTC'den gitme süreci hakkında. @Max Zerbini'nin belirttiği gibi, UTC-> Yerel kodu görünüme koymak akıllıca bir şey, ama DateTimeExtensionsgerçekten cevabı kullanıyor mu? Kullanıcıdan girdi alırken, tarihleri ​​kullanıcının yerel saati olarak kabul etmek mantıklı olur (JS'nin kullanacağı şey budur) ve sonra ModelBinderUTC'ye dönüştürmek için a kullanır mı? Kullanıcının saat dilimi DB'de saklanır ve kolayca alınır.


3
Önce bu mükemmel yazı ile bir okuma olabilir ... Yaz saati ve Timezone en iyi uygulamaları
dodgy_coder

@dodgy_coder - Bu her zaman zaman dilimleri için harika bir kaynak bağlantısı olmuştur. Ancak, gerçekten hiçbir sorunumu çözmez (özellikle MVC ile ilgili). Yine de teşekkürler.
TheCloudlessSky


Hangi çözümü seçtiğinizi merak ediyorum. Benzer bir karara kendim bakıyorum. İyi soru.
Sean

@Sean - Şimdiye kadar çözüm zarif değildi (bu yüzden henüz bir cevap kabul etmedim). Tarihlerin / saatlerin yazdırılması ve manuel olarak a ModelBinder.
TheCloudlessSky

Yanıtlar:


106

Bu bir öneri değil, bir paradigmanın daha fazla paylaşılması değil, ancak bir web uygulamasında (ASP.NET MVC'ye özel olmayan) saat dilimi bilgilerini ele almanın en agresif yolu şuydu:

  • Sunucudaki tüm tarih saatleri UTC'dir. Dediğin gibi kullanmak demek DateTime.UtcNow.

  • Mümkün olduğunca az sunucuya istemci geçiş tarihlerini güvenmeye çalışın. Örneğin, "şimdi" ye ihtiyacınız varsa, istemcide bir tarih oluşturmayın ve ardından sunucuya iletin. GET'inizde bir tarih oluşturun ve bunu ViewModel'e veya POST do'ya geçirin DateTime.UtcNow.

Şimdiye kadar, oldukça standart ücret, ama burası şeyler 'ilginç' olsun.

  • İstemciden bir tarih kabul etmeniz gerekiyorsa, sunucuya gönderdiğiniz verilerin UTC'de olduğundan emin olmak için javascript kullanın. Müşteri hangi saat diliminde olduğunu bilir, böylece makul doğrulukta zamanları UTC'ye dönüştürebilir.

  • Görünümleri oluştururken HTML5 <time>öğesini kullanıyorlardı, hiçbir zaman doğrudan doğrudan ViewModel'de oluşturulmayacaklardı. Bir HtmlHelperuzantı gibi uygulandı Html.Time(Model.when). Render eder<time datetime='[utctime]' data-date-format='[datetimeformat]'></time> .

    Ardından UTC saatini istemcilerin yerel saatine çevirmek için javascript kullanırlar. Komut dosyası tüm <time>öğeleri bulur date-formatve tarihi biçimlendirmek ve öğenin içeriğini doldurmak için data özelliğini kullanır.

Bu şekilde, hiçbir zaman bir müşteri saat dilimini takip etmek, saklamak veya yönetmek zorunda kalmadılar. Sunucu, istemcinin hangi saat diliminde olduğunu umursamadı ya da saat dilimi çevirileri yapmak zorunda değildi. Sadece UTC tükürmek ve istemci bunu makul bir şeye dönüştürmek izin. Hangi tarayıcı hangi saat dilimi içinde olduğunu bilir, çünkü istemci saat dilimini değiştirirse, web uygulaması otomatik olarak kendini güncelleyecektir. Depoladıkları tek şey, kullanıcının yerel ayarı için datetime biçim dizesidir.

Bunun en iyi yaklaşım olduğunu söylemiyorum, ama daha önce görmediğim farklı bir yaklaşımdı. Belki bazı ilginç fikirleri öğreneceksiniz.


Yanıtınız için teşekkürler. Kullanıcıdan tarih girdisi alırken (örneğin bir randevu planlama), bu girdinin geçerli saat diliminde olduğu varsayılmıyor mu? ModelBinder'ın eyleme girmeden önce bu dönüşümü yapması hakkında ne düşünüyorsunuz (@casperOne'daki yorumlarıma bakın. Ayrıca, <time>fikir oldukça havalı. Tek dezavantajı, bu öğeler için tüm DOM'yi sorgulamam gerektiği anlamına geliyor. sadece tarihleri ​​dönüştürmek için hoş değil (JS devre dışı ne olacak?) Tekrar teşekkürler!
TheCloudlessSky

Genellikle, evet varsayım yerel bir saat diliminde olacağı yönündedir. Ancak bir kullanıcıdan bir zaman topladıklarında, sunucuya gönderilmeden önce javascript kullanılarak UTC'ye dönüştürüldüğünden emin olmak için bir noktaya değindiler. Çözümleri çok JS ağırdı ve JS kapalıyken çok iyi bir şekilde bozulmadı. Bunun etrafında zeki olup olmadığını görmek için yeterince düşünmedim. Ama dediğim gibi, belki size bazı fikirler verir.
J. Holmes

2
O zamandan beri yerel tarihlere ihtiyaç duyan başka bir proje üzerinde çalıştım ve kullandığım yöntem buydu. Tarih / saat biçimlendirmesi yapmak için moment.js kullanıyorum ... çok tatlı. Teşekkürler!
TheCloudlessSky

Uygulamanın app.config / web.cofig dosyasında bir Uygulama kapsamı saat dilimi tanımlamanın ve tüm DateTime.Now değerlerini DateTime.UtcNow'a dönüştürmesini sağlamanın basit bir yolu yok mu? (Şirketteki programcıların hala DateTime'ı kullanacağı durumlardan kaçınmak istiyorum.Şimdi yanlışlıkla)
Uri Abramson

3
Görebildiğim bu yaklaşımın bir dezavantajı, başka şeyler için zaman kullandığınızda, pdf'ler, e-postalar vb. Aksi takdirde, çok temiz bir çözüm
shenku

15

Birkaç geri bildirimden sonra, temiz ve basit olduğunu ve gün ışığından yararlanma sorunlarını kapsadığını düşündüğüm son çözümüm.

1 - Dönüşümü model düzeyinde ele alıyoruz. Bu nedenle, Model sınıfında şunları yazıyoruz:

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

2 - Küresel bir yardımcıda "ToLocalTime" özel fonksiyonumuzu yapıyoruz:

    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 - Her kullanıcı profilinde saat dilimi kimliğini kaydederek bunu daha da geliştirebiliriz, böylece sabit "Çin Standart Saati" kullanmak yerine kullanıcı sınıfından alabiliriz:

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

4 - Burada, bir açılır kutudan seçim yapmak için kullanıcıya gösterilecek saat dilimi listesini alabiliriz:

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;
    }
}

Yani, şimdi 9:25 Çin'de, ABD'de barındırılan web sitesi, tarih veritabanında UTC'de kaydedildi, sonuç şu:

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)

DÜZENLE

Orijinal çözümün zayıf kısımlarını işaret ettiği için Matt Johnson'a teşekkürler ve orijinal yazıyı sildiğiniz için üzgünüm, ancak doğru kod görüntüleme formatını alma konusunda sorun yaşadı ... editörün "mermi" ile "ön kod" karıştırma sorunları var, bu yüzden ben boğalar kaldırıldı ve o was ok.


Bazılarının n-katmanlı mimarisi yoksa veya Web kütüphaneleri paylaşılıyorsa uygulanabilir gibi görünüyor. Saat dilimi kimliğini veri katmanıyla nasıl paylaşabiliriz.
Vikash Kumar

9

Gelen olayları bölümünde üzerinde sf4answers , kullanıcılar bir olayın yanı sıra başlangıç tarihi ve isteğe bağlı bir bitiş tarihi için bir adres girin. Bu zamanlar, datetimeoffsetUTC'den ofseti oluşturan bir SQL sunucusuna çevrilir .

Bu, karşılaştığınız aynı problemdir (farklı bir yaklaşım alsanız da, kullandığınızdan DateTime.UtcNow); bir konumunuz var ve bir saati bir saat diliminden diğerine çevirmeniz gerekiyor.

Benim için işe yarayan iki ana şey var. İlk olarak, her zaman DateTimeOffsetyapıyı kullanın . UTC'den ofseti açıklar ve bu bilgileri istemcinizden alabiliyorsanız, hayatınızı biraz daha kolay hale getirir.

İkinci olarak, çevirileri gerçekleştirirken, istemcinin bulunduğu konumu / saat dilimini bildiğinizi varsayarsak, genel bilgileri saat dilimi veritabanını kullanarak UTC'den başka bir saat dilimine (ya da isterseniz üçgenleştirebilirsiniz) Zaman dilimleri). Tz veritabanı ile ilgili en iyi şey (bazen Olson veritabanı olarak adlandırılır ) tarih boyunca saat dilimlerindeki değişiklikleri açıklamasıdır; Bir sen sadece bakmak (üzerinde offset almak istediğiniz tarihten bir işlevidir ofset alma 2005 Enerji Politikası Yasası yaz saati ABD'de yürürlüğe girdiğinde tarihleri değişti ).

Elinizdeki veritabanı ile ZoneInfo (tz veritabanı / Olson veritabanı) .NET API'sini kullanabilirsiniz . İkili dağıtım olmadığını, en son sürümü indirmeniz gerektiğini unutmayın ve kendiniz derlemeniz gerekir.

Bu yazı yazıldığı sırada şu anda en son veri dağıtımındaki tüm dosyaları ayrıştırıyor ( 25 Eylül'de ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz dosyasına karşı çalıştırdım , 2011; Mart 2017'de bunu https://iana.org/time-zones veya ftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz adresinden edinebilirsiniz. ) .

Yani sf4answers'da, adresi aldıktan sonra, bir enlem / boylam kombinasyonuna coğrafi olarak kodlanır ve daha sonra tz veritabanındaki bir girişe karşılık gelen bir saat dilimi almak için üçüncü taraf bir web hizmetine gönderilir. Oradan, başlangıç ​​ve bitiş zamanlarıDateTimeOffset uygun UTC ofseti olan örneklere ve sonra veritabanında saklanır.

SO ve web sitelerinde bununla ilgilenmek, kitleye ve ne göstermeye çalıştığınıza bağlıdır. Fark ederseniz, çoğu sosyal web sitesi (ve SO ve sf4answers üzerindeki etkinlikler bölümü) etkinlikleri göreli olarak görüntüler veya mutlak bir değer kullanılırsa, genellikle UTC'dir.

Ancak, kitleniz yerel saatleri DateTimeOffsetbeklerse, dönüştürmek için zaman dilimini alan bir uzantı yöntemiyle birlikte kullanmak iyi olur; SQL veri türü datetimeoffsetdaha DateTimeOffsetsonra GetUniversalTimeyöntemi kullanmak için evrensel zaman alabilirsiniz . Oradan, ZoneInfoUTC'den yerel saate dönüştürmek için sınıftaki yöntemleri kullanmanız yeterlidir (bunu yapmak için küçük bir iş yapmanız gerekir.DateTimeOffset , ancak bunu yapmak için yeterince basit).

Dönüşüm nerede yapılır? Bu, bir yere ödemek zorunda kalacağınız bir maliyettir ve "en iyi" yol yoktur. Ben görünüm için görünüm görünümünün bir parçası olarak zaman dilimi ofset ile görünüm olsa tercih ediyorum. Bu şekilde, görünüm için gereksinimler değişirse, değişikliği karşılamak için görünüm modelinizi değiştirmeniz gerekmez. Sizin JsonResultsadece bir model içerecektir ve ofset.IEnumerable<T>

Giriş tarafında bir model bağlayıcı mı kullanıyorsunuz? Kesinlikle hayır diyemem. Tüm tarihlerin (şimdi veya gelecekte) bu şekilde dönüştürülmesi gerektiğini garanti edemezsiniz , bu işlemi gerçekleştirmek için kumandanızın açık bir işlevi olmalıdır. Yine, gereksinimler değişirse, ModelBinderiş mantığınızı ayarlamak için bir veya daha fazla örneği değiştirmenize gerek yoktur ; ve olduğu o denetleyicisi olmalıdır vasıta iş mantığı.


Ayrıntılı yanıtınız için teşekkürler. Umarım kaba olarak rastlamak değil, ama bu gerçekten ASP.NET MVC ile benim endişelerimi cevap vermedi: Kullanıcı girişi hakkında ne (özel bir model bağlayıcı kullanmak yeterli olurdu)? Ve en önemlisi, bu tarihleri göstermeye ne dersiniz (kullanıcının yerel saat diliminde)? Uzatma yöntemi gereksiz görünüyor benim görüşlere çok ağırlık ekleyecek gibi hissediyorum.
TheCloudlessSky

1
@TheCloudlessSky: Düzenlenen yanıtımın son iki paragrafına bakın. Şahsen, dönüşümlerin nerede yapılacağına dair detayların küçük olduğunu düşünüyorum; büyük sorun datetime verilerinin gerçek dönüşüm ve depolama (btw, datetimeoffsetSQL Server ve DateTimeOffsetburada .NET kullanımını yeterince vurgulayamıyorum , onlar gerçekten muazzam şeyler basitleştirmek) hangi NET yeterince işlemez inanıyorum Yukarıda belirtilen nedenlerden dolayı. NYC'de 2003 yılında girilen bir tarihiniz varsa ve 2011'de LA'da bir tarihe çevrilmesini istiyorsanız, .NET bu durumda başarısız olur.
casperOne

Bir model bağlayıcı (veya işlemden önce herhangi bir katman) kullanmama sorunu, her denetleyicinin tarihlerle çalışırken test edilmesinin çok zor hale gelmesidir (hepsi tarih dönüşümüne bağımlı olurlar). Bu, birim test tarihlerinin UTC'de yazılmasına izin verecektir. Uygulamamın bir profille ilişkilendirilmiş kullanıcıları var (uygulamalarıyla ilişkili doktorları / sekreterleri düşünün). Profil, saat dilimi bilgilerini tutar. Bu nedenle, geçerli kullanıcının profilinin saat dilimini almak ve ciltleme sırasında dönüşüm yapmak oldukça kolaydır. Buna karşı başka tartışmalarınız var mı? Girdiniz için teşekkürler!
TheCloudlessSky

Buna karşı dava, testlerinizin test senaryolarını doğru bir şekilde yansıtmayacağıdır. Girişiniz edilir değil UTC olacak, bu yüzden test durumları kullanmak için hazırlanmış edilmemelidir. Konumlarla ve tümüyle gerçek dünyadaki tarihleri ​​kullanmalıdır (eğer kullanırsanız DateTimeOffsetbunu büyük ölçüde azaltabilirsiniz, IMO).
casperOne

Evet - ancak bu , tarihlerle ilgilenen her testin bunu hesaba katması gerektiği anlamına gelir . Tarihler ile ilgili her eylem daima UTC'ye dönüşür. Bana göre bu, model bağlayıcı için birincil aday ve sonra model bağlayıcıyı test edin .
TheCloudlessSky

5

Bu sadece benim görüşüm, MVC uygulama veri sunumu sorunu veri modeli yönetiminden iyi ayırmak gerektiğini düşünüyorum. Bir veritabanı verileri yerel sunucu zamanında saklayabilir, ancak yerel kullanıcı saat dilimini kullanarak tarih saatini oluşturmak sunum katmanının görevidir. Bu bana farklı ülkeler için I18N ve sayı biçimi ile aynı problem gibi geliyor. Sizin durumunuzda, uygulamanız Culturekullanıcının ve saat dilimini algılamalı ve farklı metin, sayı ve datime sunumu gösteren Görünümü değiştirmelidir, ancak saklanan veriler aynı biçime sahip olabilir.


Yanıtınız için teşekkürler. Evet, temelde anlattığım şey bu, bunun MVC ile nasıl zarif bir şekilde başarılabileceğini merak ediyorum . Uygulama, hesap oluşturma işlemlerinin bir parçası olarak kullanıcının saat dilimini saklar (saat dilimlerini seçerler). Sorun yine (umarım) için oluşturduğum yöntemler ile onları çöp zorunda kalmadan her görünümde tarihleri ​​nasıl render ile geliyor DateTimeExtensions.
TheCloudlessSky

Bunu yapmanın birçok yolu vardır, biri önerdiğiniz gibi yardımcı yöntemleri kullanmaktır, belki de daha karmaşık ama zarif, istekleri ve yanıtları işleyen ve datetime dönüştüren bir filtrenin kullanılmasıdır. Başka bir yol, görünüm modelinizin DateTime türündeki alanlara açıklama eklemek için özel bir Display özelliği geliştirmektir.
Massimo Zerbini

Bunlar benim aradığım şeyler. OP'ye bakarsanız , görünüm / json sonucuna gitmeden önce , ancak eylem yürütüldükten sonra bazı işlemleri yapmak için AutoMapper kullanarak bahsetmiştim .
TheCloudlessSky

1

Çıktı için, buna benzer bir ekran / düzenleyici şablonu oluşturun

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

Yalnızca belirli modellerin bu şablonları kullanmasını istiyorsanız, bunları modelinizdeki özelliklere göre bağlayabilirsiniz.

Buraya ve buraya bakınÖzel düzenleyici şablonları oluşturma hakkında daha fazla bilgi için .

Alternatif olarak, hem giriş hem de çıkış için çalışmasını istediğiniz için, bir kontrolü genişletmenizi veya hatta kendinizinkini oluşturmanızı öneririm. Bu şekilde hem giriş hem de çıkışları kesebilir ve metni / değeri gerektiği gibi dönüştürebilirsiniz.

Bu bağlantıEğer bu yolda ilerlemek istiyorsanız umarım sizi doğru yöne itecektir.

Her iki durumda da, zarif bir çözüm istiyorsanız, biraz iş olacak. Parlak tarafta, bir kez yaptıktan sonra, ileride kullanmak için kod kitaplığınızda tutabilirsiniz!


Özel ekran / editör şablonları ve bağlayıcı en zarif çözüm olduğunu düşünüyorum, çünkü bir kez uygulandığında "sadece çalışır". Hiçbir geliştirici doğru sadece kullanmak çalışan tarihleri almak için özel bir şey bilmemiz gerekir DisplayForve EditorForve her zaman çalışacaktır. +1
Kevin Stricker

17
bu, tarayıcının saatini değil uygulama sunucusunun zamanını oluşturmaz mı?
Alex

0

Bu muhtemelen bir somun kırmak için bir balyozdur, ancak UI ve Business katmanları arasında, döndürülen nesne grafiklerinde günleri şeffaf olarak yerel saate ve giriş datetime parametrelerinde UTC'ye dönüştüren bir katman enjekte edebilirsiniz.

Bunun PostSharp veya kontrol kabının ters çevrilmesi ile elde edilebileceğini düşünüyorum.

Şahsen, sadece tarihlerini kullanıcı arayüzünde açıkça dönüştürmekle giderdim ...

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.