การสร้าง DateTime ในเขตเวลาเฉพาะใน c #


162

ฉันกำลังพยายามสร้างการทดสอบหน่วยเพื่อทดสอบกรณีและเวลาที่เขตเวลาเปลี่ยนแปลงบนเครื่องเนื่องจากตั้งค่าไม่ถูกต้องจากนั้นจึงแก้ไข

ในการทดสอบฉันต้องสามารถสร้างวัตถุ DateTime ในเขตเวลาท้องถิ่นไม่ได้เพื่อให้แน่ใจว่าผู้ที่ใช้การทดสอบสามารถทำได้โดยไม่คำนึงถึงที่ตั้งของพวกเขา

จากสิ่งที่ฉันเห็นจากตัวสร้าง DateTime ฉันสามารถตั้งค่า TimeZone ให้เป็นเขตเวลาท้องถิ่นโซนเวลา UTC หรือไม่ได้ระบุไว้

ฉันจะสร้าง DateTime ด้วยเขตเวลาเฉพาะเช่น PST ได้อย่างไร


คำถามที่เกี่ยวข้อง - stackoverflow.com/questions/2532729/…
Oded

คำตอบ:


216

คำตอบของ JonพูดถึงTimeZoneแต่ฉันแนะนำให้ใช้TimeZoneInfoแทน

โดยส่วนตัวแล้วฉันชอบเก็บสิ่งต่าง ๆ ใน UTC หากเป็นไปได้ (อย่างน้อยก็ในอดีตการเก็บ UTC สำหรับอนาคตมีปัญหาที่อาจเกิดขึ้น ) ดังนั้นฉันขอแนะนำโครงสร้างเช่นนี้:

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

คุณอาจต้องการเปลี่ยนชื่อ "TimeZone" เป็น "TimeZoneInfo" เพื่อทำให้สิ่งต่าง ๆ ชัดเจนขึ้น - ฉันชอบชื่อคนดื้อที่ตัวเองมากกว่า


5
ฉันไม่รู้โครงสร้าง SQL Server ที่เทียบเท่ากันฉันกลัว ฉันขอแนะนำให้มีชื่อเขตเวลาเป็นหนึ่งคอลัมน์และค่า UTC ในคอลัมน์อื่น ดึงข้อมูลแยกต่างหากจากนั้นคุณสามารถสร้างอินสแตนซ์ได้อย่างง่ายดาย
Jon Skeet

2
ไม่แน่ใจเกี่ยวกับการใช้ Constructor ที่คาดว่าจะใช้ DateTime และ TimeZoneInfo แต่เนื่องจากคุณกำลังเรียกใช้เมธอด dateTime.ToUniversalTime () ฉันสงสัยว่าคุณคาดเดาว่า "อาจจะ" เป็นเวลาท้องถิ่น ในกรณีนี้ฉันคิดว่าคุณควรใช้ TimeZoneInfo ที่ส่งผ่านเพื่อแปลงเป็น UTC เนื่องจากพวกเขากำลังบอกคุณว่าควรจะอยู่ในเขตเวลานั้น
IDisposable

2
@ChrisMoschini: ณ จุดนี้คุณเพิ่งประดิษฐ์รูปแบบ ID ของคุณเอง - เป็นรูปแบบที่ไม่มีใครในโลกใช้ ฉันจะติดกับ zoneinfo มาตรฐานอุตสาหกรรมขอบคุณ (มันยากที่จะเห็นว่า "ยุโรป / ลอนดอน" ไม่มีความหมายเช่น)
Jon Skeet

2
@ChrisMoschini: ตัวอย่างที่แตกต่างแล้ว: CST นั่นคือ UTC-5 หรือ UTC-6? IST - อิสราเอลอิสราเอลหรือไอร์แลนด์ในฐานข้อมูลของคุณเป็นอย่างไร (และแม้ว่าคุณจะรู้ว่าออฟเซ็ตในขณะนี้ประเทศต่าง ๆ ที่สังเกตตัวย่อเดียวกันอาจเปลี่ยนแปลงในเวลาที่ต่างกันดังนั้นยังมีความกำกวมเกี่ยวกับโซนเวลาจริงที่มันหมายถึงโซนเวลา! = ชดเชย) กลับไปที่กรณีของคุณ: คุณอ้างสิทธิ์ การใช้คำย่อช่วยแก้ไขปัญหาของคุณได้ดีที่สุด การใช้รหัสเขตเวลามาตรฐานอุตสาหกรรมจะแย่ลงได้อย่างไร
Jon Skeet

6
@ChrisMoschini: ฉันจะแนะนำให้ใช้รหัสโซนinfoinfoที่ไม่ได้มาตรฐานในอุตสาหกรรมแทนการใช้คำย่อที่คลุมเครือ นี่ไม่ใช่เรื่องของห้องสมุดที่เป็นที่ต้องการ - การประพันธ์ของห้องสมุดนั้นไม่มีปัญหา หากมีคนต้องการใช้ห้องสมุดอื่นที่มีตัวเลือกที่ดีนั่นก็ดี ตัวเลือกของตัวระบุสำหรับเขตเวลานั้นเป็นสิ่งสำคัญและฉันคิดว่ามันสำคัญมากที่ผู้อ่านจะทราบว่าตัวย่อนั้นไม่ชัดเจนดังที่ฉันแสดงด้วยตัวอย่างของ IST
Jon Skeet

54

โครงสร้าง DateTimeOffset ถูกสร้างขึ้นสำหรับการใช้งานประเภทนี้อย่างแน่นอน

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

นี่คือตัวอย่างของการสร้างวัตถุ DateTimeOffset ด้วยเขตเวลาที่ระบุ:

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


1
ขอบคุณนี่เป็นวิธีที่ดีในการทำให้สำเร็จ หลังจากที่คุณได้รับวัตถุ DateTimeOffset ภายในเขตเวลาที่เหมาะสมคุณสามารถใช้คุณสมบัติ. UTCDateTime เพื่อรับเวลา UTC สำหรับสิ่งที่คุณสร้างขึ้น หากคุณเก็บวันที่ของคุณใน UTC จากนั้นแปลงเป็นเวลาท้องถิ่นสำหรับผู้ใช้แต่ละคนไม่ใช่เรื่องใหญ่ :)
Red

2
ฉันไม่คิดว่านี่จะจัดการกับเวลาออมแสงได้อย่างถูกต้องเนื่องจากเขตเวลาบางแห่งให้เกียรติในขณะที่คนอื่นไม่ทำ นอกจากนี้ "ในวันที่" เวลาเริ่มต้น / สิ้นสุด DST ส่วนของวันนั้นจะถูกปิด
crokusek

14
บทเรียน. DST เป็นกฎของเขตเวลาหนึ่ง ๆ DateTimeOffset ไม่ได้ไม่ได้ไม่เกี่ยวข้องกับเขตเวลาใด ๆ อย่าสับสนค่าออฟเซ็ต UTC เช่น -5 ด้วยเขตเวลา มันไม่ใช่เขตเวลามันเป็นออฟเซ็ต ออฟเซ็ตเดียวกันมักใช้ร่วมกันในหลายเขตเวลาดังนั้นจึงเป็นวิธีที่ไม่ชัดเจนในการอ้างถึงเขตเวลา เนื่องจาก DateTimeOffset เชื่อมโยงกับออฟเซ็ตไม่ใช่เขตเวลาจึงไม่สามารถใช้กฎ DST ได้ ดังนั้น 03:00 จะเป็น 03:00 ทุกวันเดียวของปีโดยไม่มีข้อยกเว้นในโครงสร้าง DateTimeOffset (เช่นในคุณสมบัติชั่วโมงและ TimeOfDay)
Triynko

ที่ที่คุณอาจสับสนคือถ้าคุณดูคุณสมบัติ LocalDateTime ของ DateTimeOffset คุณสมบัตินั้นไม่ใช่ DateTimeOffset เป็นอินสแตนซ์ DateTime ที่มีชนิดเป็น DateTimeKind.Local อินสแตนซ์นั้นเชื่อมโยงกับเขตเวลา ... ไม่ว่าเขตเวลาของระบบในท้องถิ่นคืออะไร คุณสมบัตินั้นจะสะท้อนถึงการประหยัดเวลากลางวัน
Triynko

4
ดังนั้นปัญหาที่แท้จริงของ DateTimeOffset คือมันไม่มีข้อมูลเพียงพอ มันมีการชดเชยไม่ใช่เขตเวลา ออฟเซ็ตไม่ชัดเจนที่มีหลายโซนเวลา
Triynko

41

คำตอบอื่น ๆ ที่นี่มีประโยชน์ แต่ไม่ครอบคลุมถึงวิธีการเข้าถึงแปซิฟิกโดยเฉพาะ - ที่นี่:

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

ผิดปกติพอถึงแม้ว่าปกติ "เวลามาตรฐานแปซิฟิก" หมายถึงบางสิ่งที่แตกต่างจาก "เวลาตามฤดูกาลของแปซิฟิก" ในกรณีนี้มันหมายถึงเวลาแปซิฟิกโดยทั่วไป ในความเป็นจริงถ้าคุณใช้FindSystemTimeZoneByIdเพื่อดึงข้อมูลหนึ่งในคุณสมบัติที่มีคือบูลบอกคุณว่าเขตเวลานั้นอยู่ในการประหยัดเวลากลางวันหรือไม่

คุณสามารถดูตัวอย่างทั่วไปของสิ่งนี้ได้ในห้องสมุดที่ฉันได้รวมตัวกันเพื่อจัดการกับ DateTimes ที่ฉันต้องการในเขตเวลาที่แตกต่างกันโดยขึ้นอยู่กับว่าผู้ใช้ร้องขอจากที่ใด ฯลฯ :

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

สิ่งนี้จะไม่ทำงานนอก Windows (เช่น Mono บน Linux) เนื่องจากรายการเวลามาจาก Windows Registry: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\

ภายใต้ที่คุณจะพบคีย์ (ไอคอนโฟลเดอร์ใน Registry Editor); FindSystemTimeZoneByIdชื่อของคีย์เหล่านั้นเป็นสิ่งที่คุณส่งไป บนลีนุกซ์คุณต้องใช้ชุดคำจำกัดความเขตเวลามาตรฐานแยกต่างหากซึ่งฉันได้สำรวจไม่เพียงพอ


1
นอกจากนี้ยังมี ConvertTimeBySystemTimeZoneId () ex: TimeZoneInfo.ConvertTimeBySystemTimeZoneId (DateTime.UtcNow "เวลามาตรฐานตอนกลาง")
เบรนต์

ใน Windows TimeZone Id Listสามารถเห็นคำตอบนี้: stackoverflow.com/a/24460750/4573839
yu yang Jian

7

ฉันเปลี่ยนJon Skeet ตอบเล็กน้อยสำหรับเว็บด้วยวิธีการขยาย มันยังใช้งานได้กับสีฟ้าเหมือนมีเสน่ห์

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

คุณจะต้องสร้างวัตถุที่กำหนดเองสำหรับสิ่งนั้น วัตถุที่กำหนดเองของคุณจะมีค่าสองค่า:

  • ค่า DateTime
  • ปรบวัตถุ

ไม่แน่ใจว่ามีชนิดข้อมูลที่ให้โดย CLR ที่มีอยู่แล้ว แต่อย่างน้อยคอมโพเนนต์ TimeZone จะมีอยู่แล้ว


2

ฉันชอบคำตอบของ Jon Skeet แต่ต้องการเพิ่มสิ่งหนึ่ง ฉันไม่แน่ใจว่าจอนคาดหวังว่า ctor จะถูกส่งผ่านในเขตเวลาท้องถิ่นหรือไม่ แต่ฉันต้องการใช้สำหรับกรณีที่มันเป็นอย่างอื่นแล้วท้องถิ่น

ฉันกำลังอ่านค่าจากฐานข้อมูลและฉันรู้ว่าเขตเวลานั้นเป็นฐานข้อมูลใดดังนั้นใน ctor ฉันจะส่งผ่านเขตเวลาของฐานข้อมูล แต่ฉันต้องการค่าในเวลาท้องถิ่น LocalTime ของ Jon ไม่ส่งคืนวันที่ดั้งเดิมที่แปลงเป็นวันที่ในท้องถิ่น ส่งคืนวันที่ที่แปลงเป็นเขตเวลาดั้งเดิม (ทุกสิ่งที่คุณส่งผ่านไปยัง ctor)

ฉันคิดว่าชื่ออสังหาริมทรัพย์เหล่านี้ล้างมัน ...

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

ใช้TimeZonesระดับทำให้ง่ายต่อการสร้างวันที่ที่ระบุเขต

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

1
ขออภัย แต่ไม่มีใน Asp .NET Core 2.2 ที่นี่ VS2017 แนะนำให้ฉันติดตั้งแพ็คเกจ Outlook Nuget
Machado

ตัวอย่าง => TimeZoneInfo.ConvertTime (DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById ("เวลามาตรฐานแปซิฟิก")
AZ_
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.