Tạo một DateTime trong Múi giờ cụ thể trong c #


162

Tôi đang cố gắng tạo một bài kiểm tra đơn vị để kiểm tra trường hợp khi múi giờ thay đổi trên máy vì nó đã được đặt không chính xác và sau đó sửa.

Trong thử nghiệm, tôi cần có khả năng tạo các đối tượng DateTime theo múi giờ không cục bộ để đảm bảo rằng những người đang chạy thử nghiệm có thể thực hiện thành công bất kể vị trí của chúng ở đâu.

Từ những gì tôi có thể thấy từ hàm tạo DateTime, tôi có thể đặt TimeZone thành múi giờ cục bộ, múi giờ UTC hoặc không được chỉ định.

Làm cách nào để tạo DateTime với múi giờ cụ thể như PST?


Câu trả lời:


216

Câu trả lời của Jon nói về TimeZone , nhưng tôi khuyên bạn nên sử dụng TimeZoneInfo .

Cá nhân tôi thích giữ mọi thứ trong UTC khi có thể (ít nhất là trong quá khứ; lưu trữ UTC cho tương lai có vấn đề tiềm ẩn ), vì vậy tôi đề xuất một cấu trúc như thế này:

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

Bạn có thể muốn thay đổi tên "TimeZone" thành "TimeZoneInfo" để làm cho mọi thứ rõ ràng hơn - bản thân tôi thích những cái tên dễ hiểu hơn.


5
Tôi không biết bất kỳ cấu trúc SQL Server tương đương nào, tôi sợ. Tôi sẽ đề nghị có tên múi giờ là một cột và giá trị UTC trong cột khác. Lấy chúng một cách riêng biệt và sau đó bạn có thể tạo các thể hiện khá dễ dàng.
Jon Skeet

2
Không chắc chắn về việc sử dụng trình xây dựng dự kiến ​​sử dụng DateTime và TimeZoneInfo, nhưng cho rằng bạn đang gọi phương thức dateTime.ToUniversalTime (), tôi nghi ngờ bạn đoán nó là "có thể" theo giờ địa phương. Trong trường hợp đó, tôi nghĩ rằng bạn thực sự nên sử dụng TimeZoneInfo đã qua để chuyển đổi nó thành UTC vì họ nói với bạn rằng nó đáng lẽ phải ở trong múi giờ đó.
IDis Dùng

2
@ChrisMoschini: Vào thời điểm đó, bạn chỉ đang phát minh ra lược đồ ID của riêng mình - một lược đồ mà không ai khác trên thế giới sử dụng. Tôi sẽ gắn bó với khu vực thông tin tiêu chuẩn ngành, cảm ơn. (Thật khó để thấy "Châu Âu / Luân Đôn" là vô nghĩa, chẳng hạn.)
Jon Skeet

2
@ChrisMoschini: Ví dụ khác nhau sau đó: CST. Đó là UTC-5 hay UTC-6? Làm thế nào về IST - đó là Israel, Ấn Độ hoặc Ireland trong cơ sở dữ liệu của bạn? (Và ngay cả khi bạn biết phần bù ngay bây giờ, các quốc gia khác nhau quan sát cùng một chữ viết tắt có thể thay đổi vào các thời điểm khác nhau. Vì vậy, vẫn còn mơ hồ về nghĩa là múi giờ thực tế. Múi giờ! = Bù lại. sử dụng chữ viết tắt đã giải quyết tốt nhất vấn đề của bạn. Làm thế nào để sử dụng ID múi giờ tiêu chuẩn công nghiệp đã tồi tệ hơn?
Jon Skeet

6
@ChrisMoschini: Vâng, tôi sẽ tiếp tục khuyên bạn nên sử dụng ID khu vực không rõ ràng, tiêu chuẩn công nghiệp thay vì viết tắt mơ hồ. Đây không phải là vấn đề thư viện của ai được ưa thích - quyền tác giả của thư viện thực sự không phải là vấn đề. Nếu mong muốn một người nào đó để sử dụng thư viện khác với một tốt lựa chọn nhận dạng, đó là tốt. Mặc dù vậy, sự lựa chọn định danh cho múi giờ là một điều quan trọng và tôi nghĩ rằng điều rất quan trọng là người đọc nhận thức được rằng các chữ viết tắt mơ hồ, như tôi đã trình bày với ví dụ IST.
Jon Skeet

54

Cấu trúc DateTime Offerset đã được tạo cho chính xác loại sử dụng này.

Xem: http://msdn.microsoft.com/en-us/l Library / system.datetimeoffset.aspx

Dưới đây là ví dụ về việc tạo đối tượng DateTime Offerset với múi giờ cụ thể:

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


1
Cảm ơn, đây là một cách tốt để thực hiện nó. Sau khi bạn nhận được đối tượng DateTimePackset trong múi giờ phù hợp, bạn có thể sử dụng thuộc tính .UtcDateTime để lấy thời gian UTC cho thời gian bạn đã tạo. Nếu bạn lưu trữ ngày của bạn trong UTC, thì việc chuyển đổi chúng thành giờ địa phương cho mỗi người dùng không phải là vấn đề lớn :)
Redth

2
Tôi không nghĩ rằng điều này xử lý chính xác Giờ tiết kiệm ánh sáng ban ngày vì một số múi giờ tôn vinh nó trong khi những người khác thì không. Ngoài ra, "vào ngày" DST bắt đầu / kết thúc, các phần của ngày hôm đó sẽ bị tắt.
crokusek

14
Bài học. DST là một quy tắc của một múi giờ cụ thể. DateTimePackset không phải không không không liên quan đến bất kỳ múi giờ nào. Không nhầm lẫn giá trị bù UTC, chẳng hạn như -5, với múi giờ. Nó không phải là múi giờ, nó là một sự bù đắp. Sự bù trừ tương tự thường được chia sẻ bởi nhiều múi giờ, vì vậy đó là một cách mơ hồ để đề cập đến múi giờ. Vì DateTime Offerset được liên kết với phần bù, không phải múi giờ, nên không thể áp dụng quy tắc DST. Vì vậy, 3 giờ sáng sẽ là 3 giờ sáng vào mỗi ngày trong năm, không có ngoại lệ trong cấu trúc DateTime Offerset (ví dụ: trong các thuộc tính Giờ và Thời gian của TimeOfDay).
Triynko

Nơi bạn có thể bị nhầm lẫn là nếu bạn nhìn vào thuộc tính LocalDateTime của DateTimePackset. Thuộc tính đó KHÔNG phải là DateTimePackset, đó là phiên bản DateTime có loại là DateTimeKind.Local. Ví dụ IS được liên kết với múi giờ ... bất kể múi giờ của hệ thống địa phương là gì. Tài sản đó sẽ phản ánh tiết kiệm ánh sáng ban ngày.
Triynko

4
Vì vậy, vấn đề thực sự với DateTimePackset là nó không bao gồm đủ thông tin. Nó bao gồm một phần bù, không phải múi giờ. Phần bù không rõ ràng với nhiều múi giờ.
Triynko

41

Các câu trả lời khác ở đây rất hữu ích nhưng chúng không bao gồm cách truy cập cụ thể vào Thái Bình Dương - ở đây bạn đi:

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

Thật kỳ lạ, mặc dù "Giờ chuẩn Thái Bình Dương" thường có nghĩa khác với "Giờ ban ngày Thái Bình Dương", trong trường hợp này nói chung là giờ Thái Bình Dương nói chung. Trong thực tế, nếu bạn sử dụng FindSystemTimeZoneByIdđể tìm nạp nó, một trong những thuộc tính có sẵn là một bool cho bạn biết liệu múi giờ đó có đang tiết kiệm ánh sáng ban ngày hay không.

Bạn có thể xem các ví dụ tổng quát hơn về điều này trong thư viện mà tôi đã kết hợp với nhau để đối phó với DateTimes tôi cần trong các múi giờ khác nhau dựa trên nơi người dùng đang hỏi, v.v.

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

Điều này sẽ không hoạt động bên ngoài Windows (ví dụ Mono trên Linux) vì danh sách thời gian đến từ Windows Registry: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\

Bên dưới bạn sẽ tìm thấy các khóa (biểu tượng thư mục trong Registry Editor); tên của các phím đó là những gì bạn chuyển đến FindSystemTimeZoneById. Trên Linux, bạn phải sử dụng một bộ định nghĩa múi giờ tiêu chuẩn Linux riêng biệt mà tôi chưa khám phá đầy đủ.


1
Ngoài ra, còn có ConvertTimeBySystemTimeZoneId () ex: TimeZoneInfo.ConvertTimeBySystemTimeZoneId (DateTime.UtcNow, "Giờ chuẩn miền trung")
Brent

Trong cửa sổ Danh sách Id TimeZone cũng có thể thấy câu trả lời này: stackoverflow.com/a/24460750/4573839
yu yang Jian

7

Tôi đã thay đổi Jon Skeet trả lời một chút cho web bằng phương pháp mở rộng. Nó cũng hoạt động trên azure như một nét duyên dáng.

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

2

Bạn sẽ phải tạo một đối tượng tùy chỉnh cho điều đó. Đối tượng tùy chỉnh của bạn sẽ chứa hai giá trị:

  • một giá trị DateTime
  • một đối tượng TimeZone

Không chắc chắn đã có loại dữ liệu do CLR cung cấp có chưa, nhưng ít nhất thành phần TimeZone đã có sẵn.


2

Tôi thích câu trả lời của Jon Skeet, nhưng muốn thêm một điều. Tôi không chắc liệu Jon có mong đợi ctor luôn được thông qua trong múi giờ địa phương hay không. Nhưng tôi muốn sử dụng nó cho những trường hợp khác với địa phương.

Tôi đang đọc các giá trị từ cơ sở dữ liệu và tôi biết cơ sở dữ liệu đó thuộc múi giờ nào. Vì vậy, trong ctor, tôi sẽ chuyển qua múi giờ của cơ sở dữ liệu. Nhưng sau đó tôi muốn giá trị trong giờ địa phương. LocalTime của Jon không trả về ngày ban đầu được chuyển đổi thành ngày múi giờ địa phương. Nó trả về ngày được chuyển đổi thành múi giờ ban đầu (bất cứ điều gì bạn đã chuyển vào ctor).

Tôi nghĩ rằng những tên tài sản này rõ ràng ...

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

0

Sử dụng lớp TimeZones giúp dễ dàng tạo ngày cụ thể theo múi giờ.

TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById(TimeZones.Paris.Id));

1
Xin lỗi, nhưng nó không khả dụng trên Asp .NET Core 2.2 tại đây, VS2017 đang đề nghị tôi cài đặt gói Outlook Nuget.
Machado

example => TimeZoneInfo.ConvertTime (DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById ("Giờ chuẩn Thái Bình Dương"))
AZ_
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.