2010-01-06 66 views
0

我正在構建必須正確全球化並因此適用於所有可用時區的時態表達式庫。DateTimeOffset添加TimeSpan會爲其TimeZoneInfo返回無效的UTC偏移

現在我似乎被卡住了,因爲我不知道如何在正確的夏令時(DST)中從DateTimeOffset對象中檢索調整的日期,當使用各種.Add來移動過渡邊界時,可以移動天,小時等等。

有趣的是,我想出了本地系統時區的解決方法,但還沒有找到任何方法將相同的策略應用於任意時區。

我能夠找到一個片段(沒有保留源代碼,對不起!),它試圖通過偏移來反轉查找時區信息,但是由於存在多個潛在結果,每個潛在結果可能具有不同的DST規則不行。 (還可有一些優化,但基礎PREMIS是有缺陷的,我認爲)

public TimeZoneInfo GetTimeZoneInfo(DateTimeOffset Value) 
{ 
    // Search available sytem time zones for a matching one 
    foreach (var tzi in TimeZoneInfo.GetSystemTimeZones()) 
    { 
     // Compare value offset with time zone offset 
     if (tzi.GetUtcOffset(Value).Equals(Value.Offset)) 
     { 
      return tzi; 
     } 
    } 
} 

這東西可能是有點乏味,證明出來,所以我已經提取的核心問題變成一對夫婦的方法和單元測試,這將希望能夠證明我面臨的問題。

public DateTimeOffset GetNextDay_Wrong(DateTimeOffset FromDateTimeOffset) 
{ 
    // Cannot create a new DateTimeOffset using simply the supplied value's UtcOffset 
    // because in PST, for example, it could be -7 or -8 depending on DST 
    return new DateTimeOffset(FromDateTimeOffset.Date.AddDays(1), FromDateTimeOffset.Offset); 
} 

[TestMethod] 
public void GetNextDay_WrongTest() 
{ 
    var tz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); 

    var workingDate = new DateTime(2009, 11, 2, 0, 0, 0); 
    var failingDate = new DateTime(2009, 11, 1, 0, 0, 0); 

    var workingDate_tz = new DateTimeOffset(workingDate, tz.GetUtcOffset(workingDate)); 
    var failingDate_tz = new DateTimeOffset(failingDate, tz.GetUtcOffset(failingDate)); 

    var actual_workingDate_tz = GetNextDay_Wrong(workingDate_tz); 
    var actual_failingDate_tz = GetNextDay_Wrong(failingDate_tz); 

    var expected_workingDate = new DateTime(2009, 11, 3, 0, 0, 0); 
    var expected_failingDate = new DateTime(2009, 11, 2, 0, 0, 0); 

    var expected_workingDate_tz = new DateTimeOffset(expected_workingDate, tz.GetUtcOffset(expected_workingDate)); 
    var expected_failingDate_tz = new DateTimeOffset(expected_failingDate, tz.GetUtcOffset(expected_failingDate)); 

    Assert.AreEqual(expected_workingDate_tz, actual_workingDate_tz, "Should have found the following day's midnight"); 
    Assert.AreEqual(expected_failingDate_tz, actual_failingDate_tz, "Failing date does not have the correct offset for it's DST"); 
} 

public DateTimeOffset GetNextDay_LooksRight(DateTimeOffset FromDateTimeOffset) 
{ 
    // Because we cannot create a new DateTimeOffset we simply adjust the one provided! 
    var temp = FromDateTimeOffset; 
    // Move back to midnight of the current day 
    temp = temp.Subtract(new TimeSpan(temp.Hour, temp.Minute, temp.Second)); 
    // Now move to the next day 
    temp = temp.AddDays(1); 
    // Let the DateTimeOffset class do it's magic 
    temp = temp.ToLocalTime(); 
    // Check if the time zone has changed 
    if (FromDateTimeOffset.Offset != temp.Offset) 
    { 
     // Calculate the change amount (could be 30 mins or even stranger) 
     var delta = FromDateTimeOffset.Offset - temp.Offset; 
     // Adjust the temp value by the delta 
     temp = temp.Add(delta); 
    } 
    return temp.ToLocalTime(); 
} 

[TestMethod] 
public void GetNextDay_LooksRightTest() 
{ 
    // Everything is looking good and the test passes now, so we're home free yeah? 

    // { To work this needs to match your system's configured Local Time Zone, I'm in PST } 
    var tz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); 

    var workingDate = new DateTime(2009, 11, 2, 0, 0, 0); 
    var failingDate = new DateTime(2009, 11, 1, 0, 0, 0); 

    var workingDate_tz = new DateTimeOffset(workingDate, tz.GetUtcOffset(workingDate)); 
    var failingDate_tz = new DateTimeOffset(failingDate, tz.GetUtcOffset(failingDate)); 

    var actual_workingDate_tz = GetNextDay_LooksRight(workingDate_tz); 
    var actual_failingDate_tz = GetNextDay_LooksRight(failingDate_tz); 

    var expected_workingDate = new DateTime(2009, 11, 3, 0, 0, 0); 
    var expected_failingDate = new DateTime(2009, 11, 2, 0, 0, 0); 

    var expected_workingDate_tz = new DateTimeOffset(expected_workingDate, tz.GetUtcOffset(expected_workingDate)); 
    var expected_failingDate_tz = new DateTimeOffset(expected_failingDate, tz.GetUtcOffset(expected_failingDate)); 

    Assert.AreEqual(expected_workingDate_tz, actual_workingDate_tz, "Should have found the following day's midnight"); 
    Assert.AreEqual(expected_failingDate_tz, actual_failingDate_tz, "Failing date does not have the correct offset for it's DST"); 
} 

[TestMethod] 
public void GetNextDay_LooksRight_FAILTest() 
{ 
    // Here is where the frustrating part is... aparantly the "magic" that DateTimeOffset provides only works for your systems Local Time Zone... 

    // { To properly fail this cannot match your system's configured Local Time Zone, I'm in PST so I use EST } 
    var tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); 

    var workingDate = new DateTime(2009, 11, 2, 0, 0, 0); 
    var failingDate = new DateTime(2009, 11, 1, 0, 0, 0); 

    var workingDate_tz = new DateTimeOffset(workingDate, tz.GetUtcOffset(workingDate)); 
    var failingDate_tz = new DateTimeOffset(failingDate, tz.GetUtcOffset(failingDate)); 

    var actual_workingDate_tz = GetNextDay_LooksRight(workingDate_tz); 
    var actual_failingDate_tz = GetNextDay_LooksRight(failingDate_tz); 

    var expected_workingDate = new DateTime(2009, 11, 3, 0, 0, 0); 
    var expected_failingDate = new DateTime(2009, 11, 2, 0, 0, 0); 

    var expected_workingDate_tz = new DateTimeOffset(expected_workingDate, tz.GetUtcOffset(expected_workingDate)); 
    var expected_failingDate_tz = new DateTimeOffset(expected_failingDate, tz.GetUtcOffset(expected_failingDate)); 

    Assert.AreEqual(expected_workingDate_tz, actual_workingDate_tz, "Should have found the following day's midnight"); 
    Assert.AreEqual(expected_failingDate_tz, actual_failingDate_tz, "Failing date does not have the correct offset for it's DST"); 
} 
+0

你不能使這項工作。使用UTC。 – 2010-01-06 15:58:33

+0

我不能使用UTC,因爲例如在表示諸如Days Of the Week(允許選擇1-N天)的重複模式時,我需要能夠在下次發生重複發生時解決它,並且它具有從發佈查詢的用戶的角度來看是有效的。鑑於MTWTh模式,星期五可能是星期五,但仍然是Thurstay PST,如果純粹用UTC計算,則需要{日期} 00:00:00.000 - 00:00,並且需要{日期} 00:00:00.000 -08 :00這是8個小時。 – Perry 2010-01-06 20:05:00

+0

順便說一句,我希望有一個更優雅的解決方案,但同時我正在研究一個包裝類,該類跟蹤DateTimeOffset和TimeZoneInfo.Id它創建的,這樣我就可以獲得正確生成所需的信息一個新的DateTimeOffset,但它a)似乎重複的DateTimeOffset類的價值和b)使API更不友好(也許我可以添加一些助手/擴展方法來清理它備份了一下...) – Perry 2010-01-06 20:47:34

回答

0

這種情況下的根本問題是沒有足夠的信息來執行適當的時區轉換。簡單偏移量不足以推斷時區,因爲對於特定時刻的給定偏移量,可能存在多個潛在時區。由於DateTimeOffset對象不捕獲和維護有關時區的信息,因此它必須由該類的外部責任來維護此關係。

把我從香味中拋出來的東西就是ToLocalTime(),我後來認識到它實際上在計算中引入了一個隱含的TimeZoneInfo,即爲機器配置的本地時間,我相信在內部DateTimeOffset必須通過簡單地刪除配置的偏移量,然後使用構造函數創建一個新的DateTimeOffset類,該類採用DateTime [UTC]和TimeZoneInfo [來自本地系統]來生成正確的dst意識結果日期,從而轉換爲UTC。

鑑於此限制,我不再在DateTimeOffset類中看到DateTime和TimeZoneInfo同等準確且更有價值的組合的任何值。

+0

「DateTimeOffset」中有值。請參閱[這裏](http://stackoverflow.com/a/14269924/634824)。您在最後一段中討論的內容沒有.Net BCL實現,但NodaTime將其稱爲[ZonedDateTime](http://noda-time.googlecode.com/hg/docs/api/html/T_NodaTime_ZonedDateTime。htm),這也有價值 - 只是一個不同的。 – 2013-02-13 01:20:35