Saya memiliki situs web yang dihosting di zona waktu berbeda dari pengguna yang menggunakan aplikasi. Selain itu, pengguna dapat memiliki zona waktu tertentu. Saya bertanya-tanya bagaimana pengguna SO lainnya dan aplikasi mendekati ini? Bagian yang paling jelas adalah bahwa di dalam DB, tanggal / waktu disimpan dalam UTC. Ketika di server, semua tanggal / waktu harus ditangani dalam UTC. Namun, saya melihat tiga masalah yang saya coba atasi:
Mendapatkan waktu saat ini di UTC (diselesaikan dengan mudah dengan
DateTime.UtcNow
).Menarik tanggal / waktu dari database dan menampilkannya kepada pengguna. Ada banyak kemungkinan panggilan untuk mencetak tanggal pada tampilan yang berbeda. Saya sedang memikirkan beberapa lapisan di antara tampilan dan pengontrol yang dapat menyelesaikan masalah ini. Atau memiliki metode ekstensi khusus pada
DateTime
(lihat di bawah). Sisi buruk utama adalah bahwa di setiap lokasi menggunakan datetime dalam tampilan, metode ekstensi harus dipanggil!Ini juga akan menambah kesulitan untuk menggunakan sesuatu seperti
JsonResult
. Anda tidak bisa lagi dengan mudah meneleponJson(myEnumerable)
, itu harus terjadiJson(myEnumerable.Select(transformAllDates))
. Mungkin AutoMapper dapat membantu dalam situasi ini?Mendapatkan input dari pengguna (Lokal ke UTC). Sebagai contoh, POSTing formulir dengan tanggal akan memerlukan konversi tanggal ke UTC sebelumnya. Hal pertama yang terlintas dalam pikiran adalah menciptakan kebiasaan
ModelBinder
.
Berikut ini ekstensi yang saya pikir akan digunakan dalam tampilan:
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);
}
}
Saya akan berpikir bahwa berurusan dengan zona waktu akan menjadi hal yang umum mengingat banyak aplikasi sekarang berbasis cloud di mana waktu lokal server bisa jauh berbeda dari zona waktu yang diharapkan.
Apakah ini telah diselesaikan dengan elegan sebelumnya? Apakah ada sesuatu yang saya lewatkan? Gagasan dan pemikiran sangat dihargai.
EDIT: Untuk menghapus beberapa kebingungan saya pikir menambahkan beberapa detail lagi. Masalahnya sekarang bukan bagaimana menyimpan waktu UTC di db, ini lebih tentang proses pergi dari UTC-> Lokal dan Lokal-> UTC. Seperti yang ditunjukkan oleh @Max Zerbini, jelas pintar untuk menempatkan UTC-> kode lokal dalam tampilan, tetapi apakah DateTimeExtensions
benar-benar menggunakan jawabannya? Ketika mendapatkan input dari pengguna, apakah masuk akal untuk menerima tanggal sebagai waktu lokal pengguna (karena itulah yang akan digunakan JS) dan kemudian menggunakan a ModelBinder
untuk mengubah ke UTC? Zona waktu pengguna disimpan dalam DB dan mudah diambil.
sumber
ModelBinder
.Jawaban:
Bukannya ini adalah rekomendasi, ini lebih merupakan pembagian paradigma, tetapi cara paling agresif yang pernah saya lihat dalam menangani informasi zona waktu di aplikasi web (yang tidak eksklusif untuk ASP.NET MVC) adalah sebagai berikut:
Semua waktu tanggal di server adalah UTC. Itu berarti menggunakan, seperti yang Anda katakan
DateTime.UtcNow
,.Cobalah untuk mempercayai klien melewati tanggal ke server sesedikit mungkin. Misalnya, jika Anda perlu "sekarang", jangan membuat tanggal pada klien dan kemudian meneruskannya ke server. Buat tanggal di GET Anda dan berikan ke ViewModel atau POST do
DateTime.UtcNow
.Sejauh ini, ongkosnya lumayan standar, tapi di sinilah segalanya menjadi 'menarik'.
Jika Anda harus menerima tanggal dari klien, maka gunakan javascript untuk memastikan data yang Anda posting ke server ada di UTC. Klien tahu zona waktu apa yang digunakan, sehingga dapat dengan akurat mengubah waktu menjadi UTC.
Saat merender tampilan, mereka menggunakan
<time>
elemen HTML5 , mereka tidak akan merender datetimes secara langsung di ViewModel. Diimplementasikan sebagaiHtmlHelper
ekstensi, sesuatu sepertiHtml.Time(Model.when)
. Itu akan membuat<time datetime='[utctime]' data-date-format='[datetimeformat]'></time>
.Kemudian mereka akan menggunakan javascript untuk menerjemahkan waktu UTC ke waktu lokal klien. Script akan menemukan semua
<time>
elemen dan menggunakandate-format
properti data untuk memformat tanggal dan mengisi konten elemen.Dengan cara ini mereka tidak pernah harus melacak, menyimpan, atau mengelola zona waktu klien. Server tidak peduli zona waktu apa yang digunakan klien, juga tidak perlu melakukan terjemahan zona waktu apa pun. Itu hanya memuntahkan UTC dan membiarkan klien mengubahnya menjadi sesuatu yang masuk akal. Yang mana mudah dari peramban, karena tahu zona waktu apa yang digunakan. Jika klien mengubah zona waktunya, aplikasi web akan secara otomatis memperbarui sendiri. Satu-satunya hal yang mereka simpan adalah string format datetime untuk lokal pengguna.
Saya tidak mengatakan itu adalah pendekatan terbaik, tapi itu pendekatan yang berbeda yang belum pernah saya lihat sebelumnya. Mungkin Anda akan mendapatkan beberapa ide menarik darinya.
sumber
<time>
idenya cukup keren. Satu-satunya kelemahan adalah bahwa itu berarti bahwa saya perlu meminta seluruh DOM untuk elemen-elemen ini, yang tidak persis baik hanya untuk mengubah tanggal (bagaimana dengan JS dinonaktifkan?) Terima kasih lagi!Setelah beberapa umpan balik, inilah solusi terakhir saya yang menurut saya bersih dan sederhana dan mencakup masalah penghematan siang hari.
1 - Kami menangani konversi di tingkat model. Jadi, di kelas Model, kita menulis:
2 - Dalam penolong global, kami membuat fungsi kustom kami "ToLocalTime":
3 - Kami dapat meningkatkan ini lebih lanjut, dengan menyimpan id zona waktu di setiap profil Pengguna sehingga kami dapat mengambil dari kelas pengguna alih-alih menggunakan "Waktu Standar China" yang konstan:
4 - Di sini kita bisa mendapatkan daftar zona waktu untuk ditampilkan kepada pengguna untuk dipilih dari dropdownbox:
Jadi, sekarang pada jam 9:25 di Cina, Situs web di-host di AS, tanggal disimpan dalam UTC di basis data, inilah hasil akhirnya:
EDIT
Terima kasih kepada Matt Johnson untuk menunjukkan bagian lemah dari solusi asli, dan maaf karena menghapus posting asli, tetapi mendapat masalah dengan format tampilan kode yang benar ... ternyata editor memiliki masalah mencampur "peluru" dengan "kode pra", jadi saya dihapus bulles dan itu ok.
sumber
Di bagian acara di sf4answer , pengguna memasukkan alamat untuk suatu acara, serta tanggal mulai dan tanggal akhir opsional. Waktu-waktu ini diterjemahkan ke
datetimeoffset
dalam server SQL yang memperhitungkan offset dari UTC.Ini adalah masalah yang sama yang Anda hadapi (meskipun Anda mengambil pendekatan yang berbeda untuk itu, dalam hal yang Anda gunakan
DateTime.UtcNow
); Anda memiliki lokasi dan Anda perlu menerjemahkan waktu dari satu zona waktu ke yang lain.Ada dua hal utama yang saya lakukan yang bekerja untuk saya. Pertama, gunakan
DateTimeOffset
struktur , selalu. Itu menyumbang offset dari UTC dan jika Anda bisa mendapatkan informasi itu dari klien Anda, itu membuat hidup Anda sedikit lebih mudah.Kedua, saat melakukan terjemahan, dengan asumsi Anda tahu lokasi / zona waktu tempat klien berada, Anda dapat menggunakan basis data zona waktu info publik untuk menerjemahkan waktu dari UTC ke zona waktu lain (atau melakukan triangulasi, jika Anda mau, antara dua zona waktu). Hal yang hebat tentang basis data tz (kadang-kadang disebut sebagai basis data Olson ) adalah bahwa ia bertanggung jawab atas perubahan zona waktu sepanjang sejarah; mendapatkan offset adalah fungsi dari tanggal di mana Anda ingin mendapatkan offset tersebut (lihat saja Undang-Undang Kebijakan Energi 2005 yang mengubah tanggal ketika waktu penghematan siang hari berlaku di AS ).
Dengan database di tangan, Anda dapat menggunakan ZoneInfo (tz database / Olson database) .NET API . Perhatikan bahwa tidak ada distribusi biner, Anda harus mengunduh versi terbaru dan mengompilasinya sendiri.
Pada saat penulisan ini, saat ini mem-parsing semua file dalam distribusi data terbaru (saya benar-benar menjalankannya terhadap file ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz pada 25 September, 2011; pada Maret 2017, Anda akan mendapatkannya melalui https://iana.org/time-zones atau dari ftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz ).
Jadi pada sf4answers, setelah mendapatkan alamat, geocoded menjadi kombinasi lintang / bujur dan kemudian dikirim ke layanan web pihak ketiga untuk mendapatkan zona waktu yang sesuai dengan entri dalam basis data tz. Dari sana, waktu mulai dan berakhir dikonversi menjadi
DateTimeOffset
instance dengan offset UTC yang tepat dan kemudian disimpan dalam database.Adapun untuk menghadapinya di SO dan situs web, itu tergantung pada audiens dan apa yang Anda coba tampilkan. Jika Anda perhatikan, sebagian besar situs web sosial (dan SO, dan bagian acara di sf4answer) menampilkan acara dalam waktu relatif , atau, jika nilai absolut digunakan, biasanya UTC.
Namun, jika audiens Anda mengharapkan waktu lokal, maka menggunakan
DateTimeOffset
bersama dengan metode ekstensi yang mengambil zona waktu untuk dikonversi akan baik-baik saja; tipe data SQLdatetimeoffset
akan menerjemahkan ke. NETDateTimeOffset
yang kemudian Anda bisa mendapatkan waktu universal untuk menggunakanGetUniversalTime
metode ini . Dari sana, Anda cukup menggunakan metode diZoneInfo
kelas untuk mengkonversi dari UTC ke waktu lokal (Anda harus melakukan sedikit pekerjaan untuk membuatnya menjadiDateTimeOffset
, tetapi cukup sederhana untuk dilakukan).Di mana harus melakukan transformasi? Itu adalah biaya yang harus Anda bayar di suatu tempat , dan tidak ada cara "terbaik". Saya akan memilih tampilan, dengan zona waktu diimbangi sebagai bagian dari model tampilan yang disajikan kepada tampilan. Dengan begitu, jika persyaratan untuk tampilan berubah, Anda tidak perlu mengubah model tampilan Anda untuk mengakomodasi perubahan. Anda
JsonResult
hanya akan berisi model dengan dan offset.IEnumerable<T>
Di sisi input, menggunakan pengikat model? Saya akan mengatakan tidak mungkin. Anda tidak dapat menjamin bahwa semua tanggal (sekarang atau di masa depan) harus diubah dengan cara ini, itu harus menjadi fungsi eksplisit dari pengontrol Anda untuk melakukan tindakan ini. Sekali lagi, jika persyaratan berubah, Anda tidak perlu mengubah satu atau banyak
ModelBinder
contoh untuk menyesuaikan logika bisnis Anda; dan itu adalah logika bisnis, yang berarti harus ada di controller.sumber
datetimeoffset
di SQL Server danDateTimeOffset
. NET di sini, mereka benar-benar menyederhanakan banyak hal) yang saya percaya. NET tidak menangani secara memadai sama sekali untuk alasan yang diuraikan di atas. Jika Anda memiliki tanggal di NYC yang dimasukkan pada tahun 2003, dan kemudian Anda menginginkannya diterjemahkan ke tanggal di LA pada tahun 2011, .NET gagal dalam hal ini.DateTimeOffset
Anda akan mengurangi ini, IMO).Ini hanya pendapat saya, saya pikir aplikasi MVC harus memisahkan masalah penyajian data dengan baik dari manajemen model data. Basis data dapat menyimpan data dalam waktu server lokal tetapi merupakan tugas lapisan presentasi untuk membuat data menggunakan zona waktu pengguna lokal. Bagi saya ini masalah yang sama dengan I18N dan format angka untuk berbagai negara. Dalam kasus Anda, aplikasi Anda harus mendeteksi
Culture
zona waktu pengguna dan mengubah tampilan yang menampilkan presentasi teks, angka, dan datime yang berbeda, tetapi data yang disimpan dapat memiliki format yang sama.sumber
DateTimeExtensions
.Untuk hasil, buat template tampilan / editor seperti ini
Anda dapat mengikatnya berdasarkan atribut pada model Anda jika Anda hanya menginginkan model tertentu untuk menggunakan templat tersebut.
Lihat di sini dan di sini untuk rincian lebih lanjut tentang cara membuat template editor khusus.
Atau, karena Anda ingin itu berfungsi untuk input dan output, saya sarankan memperluas kontrol atau bahkan membuat Anda sendiri. Dengan begitu Anda dapat mencegat input dan output dan mengonversi teks / nilai sesuai kebutuhan.
Tautan ini diharapkan akan mendorong Anda ke arah yang benar jika Anda ingin menyusuri jalan itu.
Either way, jika Anda menginginkan solusi yang elegan, itu akan menjadi sedikit pekerjaan. Sisi baiknya, begitu Anda selesai melakukannya, Anda bisa menyimpannya di pustaka kode untuk digunakan di lain waktu!
sumber
DisplayFor
danEditorFor
dan itu akan bekerja setiap saat. +1Ini mungkin palu godam untuk memecahkan kacang, tetapi Anda bisa menyuntikkan lapisan antara UI dan lapisan Bisnis yang secara transparan mengubah waktu data ke waktu lokal pada grafik objek yang dikembalikan, dan ke UTC pada parameter datetime input.
Saya membayangkan ini bisa dicapai dengan menggunakan PostSharp atau inversi wadah kontrol.
Secara pribadi, saya hanya akan secara eksplisit mengkonversi datetimes Anda di UI ...
sumber