Ubah waktu UTC / GMT ke waktu lokal

301

Kami sedang mengembangkan aplikasi C # untuk klien layanan web. Ini akan berjalan pada PC Windows XP.

Salah satu bidang yang dikembalikan oleh layanan web adalah bidang DateTime. Server mengembalikan bidang dalam format GMT yaitu dengan "Z" di akhir.

Namun, kami menemukan bahwa .NET tampaknya melakukan semacam konversi implisit dan waktu selalu keluar 12 jam.

Sampel kode berikut menyelesaikan ini sampai taraf tertentu di mana perbedaan 12 jam telah hilang tetapi tidak membuat penghematan untuk siang hari NZ.

CultureInfo ci = new CultureInfo("en-NZ");
string date = "Web service date".ToString("R", ci);
DateTime convertedDate = DateTime.Parse(date);            

Sesuai situs tanggal ini :

Offset UTC / GMT

Zona waktu standar: UTC / GMT +12 jam
Waktu musim panas: +1 jam
Offset zona waktu saat ini: UTC / GMT +13 jam

Bagaimana kita menyesuaikan jam ekstra? Apakah ini dapat dilakukan secara terprogram atau apakah ini semacam pengaturan pada PC?

rbrayb
sumber
2
yang Zwaktu mengacu UTC, tidak GMT. Keduanya dapat berbeda hingga 0,9 detik.
mc0e

Jawaban:

374

Untuk string seperti 2012-09-19 01:27:30.000, DateTime.Parsetidak dapat menentukan dari zona waktu tanggal dan waktu.

DateTimememiliki properti Kind , yang dapat memiliki satu dari tiga opsi zona waktu:

  • Tidak ditentukan
  • Lokal
  • UTC

CATATAN Jika Anda ingin mewakili tanggal / waktu selain UTC atau zona waktu lokal Anda, maka Anda harus menggunakannya DateTimeOffset.


Jadi untuk kode dalam pertanyaan Anda:

DateTime convertedDate = DateTime.Parse(dateStr);

var kind = convertedDate.Kind; // will equal DateTimeKind.Unspecified

Anda mengatakan Anda tahu jenis apa itu, jadi katakan saja.

DateTime convertedDate = DateTime.SpecifyKind(
    DateTime.Parse(dateStr),
    DateTimeKind.Utc);

var kind = convertedDate.Kind; // will equal DateTimeKind.Utc

Sekarang, setelah sistem mengetahui waktu UTC, Anda cukup menelepon ToLocalTime:

DateTime dt = convertedDate.ToLocalTime();

Ini akan memberi Anda hasil yang Anda butuhkan.

Drew Noakes
sumber
19
hanya cara lain untuk menentukan jenis:DateTime convertedTime = new DateTime(DateTime.Parse(dateStr).Ticks), DateTimeKind.Utc);
Brad
bukankah ToLocalTime ()? @Brad - parens Anda tidak cocok.
TrueWill
2
Apakah solusi ini memperhitungkan penghematan siang hari? Ketika saya mencobanya, saya libur satu jam.
Bob Horn
7
Langkah mengubah Kinddari DateTimedari Unspecifiedmenjadi UTCtidak perlu. Unspecifieddiasumsikan UTCuntuk keperluan ToLocalTime: msdn.microsoft.com/en-us/library/…
CJ7
16
@ CJ7: Ya, tetapi menjadi eksplisit sangat membantu pengembang lain yang mungkin harus menjaga kode.
Ryan
121

Saya akan melihat ke dalam menggunakan kelas System.TimeZoneInfo jika Anda berada di .NET 3.5. Lihat http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx . Ini harus memperhitungkan perubahan tabungan siang hari dengan benar.

// Coordinated Universal Time string from 
// DateTime.Now.ToUniversalTime().ToString("u");
string date = "2009-02-25 16:13:00Z"; 
// Local .NET timeZone.
DateTime localDateTime = DateTime.Parse(date); 
DateTime utcDateTime = localDateTime.ToUniversalTime();

// ID from: 
// "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Time Zone"
// See http://msdn.microsoft.com/en-us/library/system.timezoneinfo.id.aspx
string nzTimeZoneKey = "New Zealand Standard Time";
TimeZoneInfo nzTimeZone = TimeZoneInfo.FindSystemTimeZoneById(nzTimeZoneKey);
DateTime nzDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, nzTimeZone);
Daniel Ballinger
sumber
Jika Anda bekerja di zona waktu Anda sendiri (en-NZ dalam hal ini) maka Anda tidak perlu berurusan dengan TimeZoneInfo. Ini hanya kompleksitas yang tidak perlu. Lihat jawaban saya untuk lebih detail.
Drew Noakes
11
Dan jika kebutuhan siapa pun, di sini adalah daftar zona waktu saya telah menemukan untuk TimeZoneInfo.FindSystemTimeZoneById - codeproject.com/Messages/3867850/...
nikib3ro
Cemerlang! Terima kasih untuk posting ini Dan. Saya sudah mencari perbaikan ini selama 3 hari.
Kevin Moore
58
TimeZone.CurrentTimeZone.ToLocalTime(date);
coder1
sumber
8
Ini hanya berfungsi jika sistem mengetahui bahwa tanggal yang dikonversi adalah dalam UTC. Tolong lihat jawaban saya.
Drew Noakes
1
Tetapi UTC adalah standarnya, bukan? Karenanya ia berfungsi untuk "tidak ditentukan" seperti pada jawaban CJ7.
NickG
25

DateTimeobjek memiliki Kindof Unspecifiedsecara default, yang untuk tujuan ToLocalTimediasumsikan UTC.

Untuk mendapatkan waktu lokal suatu Unspecified DateTimeobjek, Anda hanya perlu melakukan ini:

convertedDate.ToLocalTime();

Langkah mengubah Kinddari DateTimedari Unspecifiedmenjadi UTCtidak perlu. Unspecifieddiasumsikan UTCuntuk keperluan ToLocalTime: http://msdn.microsoft.com/en-us/library/system.datetime.tolocaltime.aspx

CJ7
sumber
6
Dan sebaliknya: convertedDate.FromLocalTime();akan dikonversi menjadi UTC.
R. Schreurs
16

Saya tahu ini adalah pertanyaan yang lebih lama, tetapi saya mengalami situasi yang sama, dan saya ingin berbagi apa yang saya temukan untuk pencari masa depan, mungkin termasuk saya sendiri :).

DateTime.Parse()bisa rumit - lihat di sini misalnya.

Jika DateTimeberasal dari layanan Web atau sumber lain dengan format yang dikenal, Anda mungkin ingin mempertimbangkan sesuatu seperti

DateTime.ParseExact(dateString, 
                   "MM/dd/yyyy HH:mm:ss", 
                   CultureInfo.InvariantCulture, 
                   DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)

atau, bahkan lebih baik,

DateTime.TryParseExact(...)

The AssumeUniversalbendera memberitahu parser bahwa tanggal / waktu sudah UTC; kombinasi dari AssumeUniversaldan AdjustToUniversalmemberitahu itu untuk tidak mengubah hasilnya ke waktu "lokal", yang akan coba lakukan secara default. (Saya pribadi mencoba untuk berurusan secara eksklusif dengan UTC di lapisan bisnis / aplikasi / layanan. Tetapi dengan memintas konversi ke waktu lokal juga mempercepat segalanya - hingga 50% atau lebih dalam pengujian saya, lihat di bawah.)

Inilah yang kami lakukan sebelumnya:

DateTime.Parse(dateString, new CultureInfo("en-US"))

Kami telah membuat profil aplikasi dan menemukan bahwa DateTime.Parse mewakili persentase penggunaan CPU yang signifikan. (Kebetulan, CultureInfokonstruktor itu bukan kontributor signifikan untuk penggunaan CPU.)

Jadi saya mengatur aplikasi konsol untuk mengurai string tanggal / waktu 10000 kali dalam berbagai cara. Intinya:
Parse()10 detik
ParseExact()(mengkonversi ke lokal) 20-45 ms
ParseExact()(tidak mengkonversi ke lokal) 10-15 ms
... dan ya, hasilnya Parse()dalam detik , sedangkan yang lain dalam milidetik .

David
sumber
14

Saya hanya ingin menambahkan catatan umum tentang kehati-hatian.

Jika semua yang Anda lakukan adalah mendapatkan waktu saat ini dari jam internal komputer untuk meletakkan tanggal / waktu pada tampilan atau laporan, maka semuanya baik-baik saja. Tetapi jika Anda menyimpan informasi tanggal / waktu untuk referensi di kemudian hari atau menghitung tanggal / waktu, waspadalah!

Katakanlah Anda menentukan bahwa sebuah kapal pesiar tiba di Honolulu pada 20 Desember 2007 pukul 15:00 UTC. Dan Anda ingin tahu jam berapa waktu itu. Penghematan waktu siang hari diterapkan secara berbeda (atau tidak sama sekali) di berbagai subdivisi politik. Jangan berpikir bahwa hanya karena negara Anda berubah pada tanggal tertentu, maka negara lain juga akan melakukannya.
1. Mungkin ada setidaknya tiga 'penduduk setempat' yang terlibat. Lokal mungkin berarti Honolulu, atau itu bisa berarti di mana komputer Anda berada, atau itu mungkin berarti lokasi di mana pelanggan Anda berada.
2. Jika Anda menggunakan fungsi bawaan untuk melakukan konversi, itu mungkin salah. Ini karena waktu penghematan siang hari (mungkin) saat ini berlaku di komputer Anda, tetapi TIDAK berlaku pada bulan Desember. Tetapi Windows tidak tahu ini ... yang dimilikinya hanyalah satu bendera untuk menentukan apakah waktu musim panas berlaku. Dan jika saat ini berlaku, maka dengan senang hati akan menambahkan satu jam bahkan ke tanggal di bulan Desember.
3.

Tandai T
sumber
6
Sebenarnya, # 2 tidak sepenuhnya benar. Sebenarnya ada aturan tentang DST di setiap zona waktu yang komputer Anda akan tahu jika info telah diinstal (dan diperbarui). Untuk banyak zona, aturan itu ditetapkan. Yang lain menerapkan "DST dinamis". Brasil adalah hewan peliharaan saya kesal untuk ini. Singkatnya, polisi Anda dapat bekerja jika waktu setempat Anda akan menjadi DST pada bulan Desember, dengan asumsi bahwa tidak ada perubahan yang disahkan menjadi undang-undang antara sekarang dan kemudian.
Roger Willcocks
Bahkan jika Anda tidak tinggal di Brasil, DST adalah "dinamis" di mana politisi dapat mengubahnya kapan saja (seperti yang dilakukan beberapa tahun yang lalu di AS). Karena sebagian besar perangkat lunak ditulis dengan menggunakan masa depan dalam pikiran, penting untuk menyadari bahwa TIDAK ada cara praktis, dapat diprediksi atau bahkan teoritis untuk mengetahui apa aturan DST akan berlaku. Anda bisa mendekati, tetapi selamatkan diri Anda dari frustrasi dengan menyerah pada kesempurnaan.
DaveWalley
6
@TimeZoneInfo.ConvertTimeFromUtc(timeUtc, TimeZoneInfo.Local)
Pangeran Prasad
sumber
5

Jangan lupa jika Anda sudah memiliki objek DateTime dan tidak yakin apakah itu UTC atau Lokal, cukup mudah untuk menggunakan metode pada objek secara langsung:

DateTime convertedDate = DateTime.Parse(date);
DateTime localDate = convertedDate.ToLocalTime();

Bagaimana kita menyesuaikan jam ekstra?

Kecuali .net yang ditentukan akan menggunakan pengaturan pc lokal. Saya akan membaca: http://msdn.microsoft.com/en-us/library/system.globalization.daylighttime.aspx

Dari tampilannya, kodenya mungkin terlihat seperti:

DaylightTime daylight = TimeZone.CurrentTimeZone.GetDaylightChanges( year );

Dan seperti yang disebutkan di atas, periksa kembali pengaturan zona waktu server Anda aktif. Ada artikel di internet tentang cara aman mempengaruhi perubahan di IIS.

Brendan Kowitz
sumber
Sistem akan menangani kerumitan ini untuk Anda, asalkan Anda memberi tahu sistem apa 'jenis' tanggal Anda (lokal / utc / tidak ditentukan).
Drew Noakes
2

Sebagai jawaban atas saran Dana:

Contoh kode sekarang terlihat seperti:

string date = "Web service date"..ToString("R", ci);
DateTime convertedDate = DateTime.Parse(date);            
DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(convertedDate);

Tanggal aslinya adalah 20/08/08; jenisnya adalah UTC.

Baik "convertDate" dan "dt" sama:

21/08/08 10:00:26; jenisnya lokal

rbrayb
sumber
Silakan lihat jawaban saya untuk penjelasan tentang ini.
Drew Noakes
1

Saya punya masalah dengan itu dalam kumpulan data didorong melintasi kawat (layanan web ke klien) yang secara otomatis akan berubah karena bidang DateType DataColumn diatur ke lokal. Pastikan Anda memeriksa apa DateType jika Anda mendorong DataSets.

Jika Anda tidak ingin itu berubah, tetapkan ke Tidak ditentukan

Miles
sumber
1

Saya menemukan pertanyaan ini karena saya mengalami masalah dengan tanggal UTC yang Anda dapatkan kembali melalui API twitter (bidang yang dibuat pada status); Saya perlu mengubahnya menjadi DateTime. Tidak ada satu pun jawaban / contoh kode dalam jawaban pada halaman ini yang cukup untuk menghentikan saya mendapatkan kesalahan "String tidak dikenali sebagai DateTime" yang valid (tapi itu adalah yang paling dekat saya harus menemukan jawaban yang benar pada SO)

Posting tautan ini di sini kalau-kalau ini membantu orang lain - jawaban yang saya butuhkan ditemukan di posting blog ini: http://www.wduffy.co.uk/blog/parsing-dates-when-aspnets-datetimeparse-doesnt-work/ - pada dasarnya menggunakan DateTime.ParseExact dengan string format, bukan DateTime.Parse

DannykPowell
sumber