Membuat DateTime di Zona Waktu tertentu di c #

162

Saya mencoba membuat unit test untuk menguji kasus ketika zona waktu berubah pada mesin karena telah diatur secara tidak benar dan kemudian diperbaiki.

Dalam pengujian saya harus dapat membuat objek DateTime di zona waktu tidak ada lokal untuk memastikan bahwa orang yang menjalankan tes dapat melakukannya dengan sukses terlepas dari di mana mereka berada.

Dari apa yang dapat saya lihat dari konstruktor DateTime saya dapat mengatur TimeZone menjadi zona waktu lokal, zona waktu UTC atau tidak ditentukan.

Bagaimana cara membuat DateTime dengan zona waktu tertentu seperti PST?

Jack Hughes
sumber
Pertanyaan terkait - stackoverflow.com/questions/2532729/…
Oded

Jawaban:

216

Jawaban Jon berbicara tentang TimeZone , tapi saya sarankan menggunakan TimeZoneInfo sebagai gantinya.

Secara pribadi saya suka menyimpan barang-barang di UTC jika memungkinkan (setidaknya untuk masa lalu; menyimpan UTC untuk masa depan memiliki masalah potensial ), jadi saya sarankan struktur seperti ini:

public struct DateTimeWithZone
{
    private readonly DateTime utcDateTime;
    private readonly TimeZoneInfo timeZone;

    public DateTimeWithZone(DateTime dateTime, TimeZoneInfo timeZone)
    {
        var dateTimeUnspec = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
        utcDateTime = TimeZoneInfo.ConvertTimeToUtc(dateTimeUnspec, timeZone); 
        this.timeZone = timeZone;
    }

    public DateTime UniversalTime { get { return utcDateTime; } }

    public TimeZoneInfo TimeZone { get { return timeZone; } }

    public DateTime LocalTime
    { 
        get 
        { 
            return TimeZoneInfo.ConvertTime(utcDateTime, timeZone); 
        }
    }        
}

Anda mungkin ingin mengubah nama "TimeZone" menjadi "TimeZoneInfo" agar lebih jelas - saya lebih suka nama yang lebih singkat.

Jon Skeet
sumber
5
Saya tidak tahu tentang konstruksi SQL Server yang setara, saya khawatir. Saya menyarankan memiliki nama zona waktu sebagai satu kolom, dan nilai UTC di kolom lain. Ambil secara terpisah dan kemudian Anda dapat membuat instance dengan cukup mudah.
Jon Skeet
2
Tidak yakin tentang penggunaan konstruktor yang diharapkan yang menggunakan DateTime dan TimeZoneInfo, tetapi mengingat bahwa Anda memanggil metode dateTime.ToUniversalTime (), saya menduga Anda menduga itu "mungkin" berada dalam waktu lokal. Dalam hal ini, saya pikir Anda harus benar-benar menggunakan TimeZoneInfo yang telah lewat untuk mengonversinya menjadi UTC karena mereka memberi tahu Anda bahwa itu seharusnya berada di zona waktu itu.
IDisposable
2
@ChrisMoschini: Pada titik itu Anda hanya menciptakan skema ID Anda sendiri - sebuah skema yang tidak digunakan orang lain di dunia. Saya akan tetap dengan zoneinfo standar industri, terima kasih. (Sulit untuk melihat bagaimana "Eropa / London" tidak berarti, misalnya.)
Jon Skeet
2
@ChrisMoschini: Contoh berbeda lalu: CST. Apakah itu UTC-5 atau UTC-6? Bagaimana dengan IST - apakah itu Israel, India atau Irlandia dalam database Anda? (Dan bahkan jika Anda tahu offsetnya sekarang, berbagai negara yang mengamati singkatan yang sama dapat berubah pada waktu yang berbeda. Jadi masih ada ambiguitas tentang zona waktu yang sebenarnya artinya. Zona waktu! = Offset.) Kembali ke kasus Anda: Anda mengklaim bahwa menggunakan singkatan sebaiknya memecahkan masalah Anda. Bagaimana cara menggunakan ID zona waktu standar industri menjadi lebih buruk?
Jon Skeet
6
@ChrisMoschini: Yah saya akan terus merekomendasikan menggunakan ID zonainfo standar industri yang tidak ambigu daripada singkatan yang ambigu. Ini bukan masalah perpustakaan mana yang lebih disukai - kepengarangan perpustakaan sebenarnya tidak menjadi masalah. Jika seseorang keinginan untuk menggunakan perpustakaan lain dengan baik pilihan identifier, itu bagus. Pilihan pengidentifikasi untuk zona waktu adalah salah satu yang penting meskipun, dan saya pikir itu sangat penting bahwa pembaca menyadari bahwa singkatan yang ambigu, seperti yang saya telah menunjukkan dengan contoh IST.
Jon Skeet
54

Struktur DateTimeOffset dibuat untuk jenis penggunaan yang tepat ini.

Lihat: http://msdn.microsoft.com/en-us/library/system.datetimeoffset.aspx

Berikut adalah contoh membuat objek DateTimeOffset dengan zona waktu tertentu:

DateTimeOffset do1 = new DateTimeOffset(2008, 8, 22, 1, 0, 0, new TimeSpan(-5, 0, 0));

CleverPatrick
sumber
1
Terima kasih, ini adalah cara yang baik untuk mencapainya. Setelah Anda mendapatkan objek DateTimeOffset Anda dalam zona waktu yang tepat, Anda dapat menggunakan properti .UtcDateTime untuk mendapatkan waktu UTC untuk yang Anda buat. Jika Anda menyimpan tanggal Anda di UTC, maka mengubahnya menjadi waktu lokal untuk setiap pengguna bukanlah masalah besar :)
Redth
2
Saya tidak berpikir ini menangani Daylight Savings Time dengan benar karena beberapa Zona Waktu menghargainya sementara yang lain tidak. Juga "pada hari" DST dimulai / berakhir, bagian-bagian hari itu akan mati.
crokusek
14
Pelajaran. DST adalah aturan zona waktu tertentu. DateTimeOffset bukan tidak tidak tidak terkait dengan zona waktu mana pun. Jangan bingung nilai offset UTC, seperti -5, dengan zona waktu. Ini bukan zona waktu, ini offset. Offset yang sama sering digunakan bersama oleh banyak zona waktu, jadi ini adalah cara yang ambigu untuk merujuk ke zona waktu. Karena DateTimeOffset dikaitkan dengan offset, bukan zona waktu, ia tidak mungkin menerapkan aturan DST. Jadi jam 3 pagi akan jam 3 pagi pada setiap hari dalam setahun, tanpa terkecuali dalam struktur DateTimeOffset (misalnya dalam properti Hours and TimeOfDay).
Triynko
Di mana Anda mungkin bingung adalah jika Anda melihat properti LocalDateTime dari DateTimeOffset. Properti itu BUKAN DateTimeOffset, itu adalah instance DateTime yang jenisnya adalah DateTimeKind.Local. Contoh itu terkait dengan zona waktu ... apa pun zona waktu sistem lokal. Properti itu AKAN mencerminkan penghematan siang hari.
Triynko
4
Jadi, masalah sebenarnya dengan DateTimeOffset adalah tidak menyertakan cukup informasi. Ini termasuk offset, bukan zona waktu. Offsetnya ambigu dengan beberapa zona waktu.
Triynko
41

Jawaban lain di sini bermanfaat tetapi tidak mencakup cara mengakses Pasifik secara khusus - ini dia:

public static DateTime GmtToPacific(DateTime dateTime)
{
    return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
        TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"));
}

Anehnya, walaupun "Waktu Standar Pasifik" biasanya berarti sesuatu yang berbeda dari "Waktu Siang Pasifik", dalam hal ini mengacu pada waktu Pasifik secara umum. Bahkan, jika Anda menggunakannya FindSystemTimeZoneByIduntuk mengambilnya, salah satu properti yang tersedia adalah bool yang memberi tahu Anda apakah zona waktu saat ini dalam penghematan siang hari atau tidak.

Anda dapat melihat lebih banyak contoh umum ini di perpustakaan yang akhirnya saya lempar bersama untuk menangani DateTimes yang saya butuhkan di Zona Waktu berbeda berdasarkan dari mana pengguna meminta, dll:

https://github.com/b9chris/TimeZoneInfoLib.Net

Ini tidak akan berfungsi di luar Windows (misalnya Mono di Linux) karena daftar waktu berasal dari Windows Registry: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\

Di bawahnya Anda akan menemukan kunci (ikon folder di Peninjau Suntingan Registri); nama kunci-kunci itulah yang Anda gunakan FindSystemTimeZoneById. Di Linux, Anda harus menggunakan satu set definisi zona waktu standar Linux yang terpisah, yang saya belum cukup jelajahi.

Chris Moschini
sumber
1
Selain itu ada ConvertTimeBySystemTimeZoneId () mis: TimeZoneInfo.ConvertTimeBySystemTimeZoneId (DateTime.UtcNow, "Waktu Standar Pusat")
Brent
Di windows TimeZone Id List juga dapat melihat jawaban ini: stackoverflow.com/a/24460750/4573839
yu yang Jian
7

Saya mengubah sedikit jawaban Jon Skeet untuk web dengan metode ekstensi. Ini juga berfungsi pada biru seperti pesona.

public static class DateTimeWithZone
{

private static readonly TimeZoneInfo timeZone;

static DateTimeWithZone()
{
//I added web.config <add key="CurrentTimeZoneId" value="Central Europe Standard Time" />
//You can add value directly into function.
    timeZone = TimeZoneInfo.FindSystemTimeZoneById(ConfigurationManager.AppSettings["CurrentTimeZoneId"]);
}


public static DateTime LocalTime(this DateTime t)
{
     return TimeZoneInfo.ConvertTime(t, timeZone);   
}
}
Jernej Novak
sumber
2

Anda harus membuat objek khusus untuk itu. Objek khusus Anda akan berisi dua nilai:

Tidak yakin apakah sudah ada tipe data yang disediakan CLR yang memilikinya, tetapi setidaknya komponen TimeZone sudah tersedia.

Jon Limjap
sumber
2

Saya suka jawaban Jon Skeet, tetapi ingin menambahkan satu hal. Saya tidak yakin apakah Jon mengharapkan ctor untuk selalu dilewati di zona waktu lokal. Tapi saya ingin menggunakannya untuk kasus-kasus di mana itu sesuatu yang lain daripada lokal

Saya membaca nilai dari basis data, dan saya tahu zona waktu tempat basis data itu. Jadi di ctor, saya akan melewati zona waktu dari basis data. Tapi kemudian saya ingin nilai waktu setempat. LocalTime Jon tidak mengembalikan tanggal asli yang dikonversi menjadi tanggal zona waktu lokal. Ini mengembalikan tanggal yang dikonversi ke zona waktu asli (apa pun yang Anda berikan ke ctor).

Saya pikir nama-nama properti ini membersihkannya ...

public DateTime TimeInOriginalZone { get { return TimeZoneInfo.ConvertTime(utcDateTime, timeZone); } }
public DateTime TimeInLocalZone    { get { return TimeZoneInfo.ConvertTime(utcDateTime, TimeZoneInfo.Local); } }
public DateTime TimeInSpecificZone(TimeZoneInfo tz)
{
    return TimeZoneInfo.ConvertTime(utcDateTime, tz);
}
Gabe Halsmer
sumber
0

Menggunakan kelas TimeZones memudahkan untuk membuat tanggal khusus zona waktu.

TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById(TimeZones.Paris.Id));
Meghs Dhameliya
sumber
1
Maaf, tetapi tidak tersedia di Asp .NET Core 2.2 di sini, VS2017 menyarankan saya untuk menginstal paket Outlook Nuget.
Machado
contoh => TimeZoneInfo.ConvertTime (DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById ("Waktu Standar Pasifik"))
AZ_