儲存和還原時區
更新:2007 年 11 月
TimeZoneInfo 類別必須藉助登錄才能擷取預先定義的時區資料。但是,登錄是一種動態結構。此外,登錄中內含的時區資訊,主要是由作業系統用來處理當年度的時間調整及轉換。這麼做對於必須藉助時區資料的應用程式而言,有兩個重要的含意:
應用程式所需的時區可能未定義在登錄中,或已重新命名或從登錄中移除。
登錄中所定義的時區可能缺少特定調整規則的資訊,而這些是進行歷史時區轉換時必要的資訊。
TimeZoneInfo 類別經由支援序列化 (儲存) 及還原序列化 (還原) 時區資料,以解決這個限制。
時區序列化及還原序列化
以序列化和還原序列化時區資料的方式儲存及還原時區,需要兩個方法呼叫:
您可以呼叫物件的 ToSerializedString 方法,將 TimeZoneInfo 物件序列化。這個方法不使用參數,並會傳回內含時區資訊的字串。
將已序列化的字串傳送給 static (在 Visual Basic 中為 Shared) TimeZoneInfo.FromSerializedString 方法,就可以從字串中將 TimeZoneInfo 物件還原序列化。
序列化及還原序列化的情形
將 TimeZoneInfo 物件儲存 (序列化) 成字串,以及還原 (還原序列化) 以供日後使用,可以提高 TimeZoneInfo 類別的實用性及彈性。本節將詳細說明幾個最適用序列化及還原序列化的情形。
序列化及還原序列化應用程式中的時區資料
當有需要時,可以從字串還原已序列化的時區。如果擷取自登錄的時區無法正確轉換特定日期範圍內的日期與時間,應用程式就可能會這麼做。例如,Windows XP 登錄中的時區資料支援單一調整規則,而 Windows Vista 登錄中所定義的時區則提供兩種調整規則的資訊。這表示歷史時間轉換可能不精確。而序列化和還原序列化時區資料可以解決這個限制。
下列範例中定義了沒有調整規則的自訂 TimeZoneInfo 類別,以表示還未實施日光節約時間的 1883 年到 1917 年間,美國「東部標準時間」區。自訂的時區會序列化成全域範圍的變數。時區轉換方法 (ConvertUtcTime) 會傳送給 Coordinated Universal Time (UTC) 時間以進行轉換。如果日期與時間發生在 1917 年或更早,自訂的「東部標準時間」區就會從已序列化的字串還原,並取代從登錄中擷取的時區。
Module TimeZoneSerialization
Dim serializedEst As String
Public Sub Main()
' Retrieve Eastern Standard Time zone from registry
Try
Dim est As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time")
' Create custom Eastern Time Zone for historical (pre-1918) conversions
CreateTimeZone()
' Call conversion function with one current and one pre-1918 date and time
Console.WriteLine(ConvertUtcTime(Date.UtcNow, est))
Console.WriteLine(ConvertUtcTime(New Date(1900, 11, 15, 9, 32, 00, DateTimeKind.Utc), est))
Catch e As TimeZoneNotFoundException
Console.WriteLine("The Eastern Standard Time zone is not in the registry.")
Catch e As InvalidTimeZoneException
Console.WriteLine("Data on the Eastern Standard Time Zone in the registry is corrupted.")
End Try
End Sub
Private Sub CreateTimeZone()
' Create a simple Eastern Standard time zone
' without adjustment rules for 1883-1918
Dim earlyEstZone As TimeZoneInfo = TimeZoneInfo.CreateCustomTimeZone("Eastern Standard Time", _
New TimeSpan(-5, 0, 0), _
" (GMT-05:00) Eastern Time (United States)", _
"Eastern Standard Time")
serializedEst = earlyEstZone.ToSerializedString()
End Sub
Private Function ConvertUtcTime(utcDate As Date, tz As TimeZoneInfo) As Date
' Use time zone object from registry
If Year(utcDate) > 1917 Then
Return TimeZoneInfo.ConvertTimeFromUtc(utcDate, tz)
' Handle dates before introduction of DST
Else
' Restore serialized time zone object
tz = TimeZoneInfo.FromSerializedString(serializedEst)
Return TimeZoneInfo.ConvertTimeFromUtc(utcDate, tz)
End If
End Function
End Module
using System;
public class TimeZoneSerialization
{
static string serializedEst;
public static void Main()
{
// Retrieve Eastern Standard Time zone from registry
try
{
TimeZoneSerialization tzs = new TimeZoneSerialization();
TimeZoneInfo est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
// Create custom Eastern Time Zone for historical (pre-1918) conversions
CreateTimeZone();
// Call conversion function with one current and one pre-1918 date and time
Console.WriteLine(ConvertUtcTime(DateTime.UtcNow, est));
Console.WriteLine(ConvertUtcTime(new DateTime(1900, 11, 15, 9, 32, 00, DateTimeKind.Utc), est));
}
catch (TimeZoneNotFoundException)
{
Console.WriteLine("The Eastern Standard Time zone is not in the registry.");
}
catch (InvalidTimeZoneException)
{
Console.WriteLine("Data on the Eastern Standard Time Zone in the registry is corrupted.");
}
}
private static void CreateTimeZone()
{
// Create a simple Eastern Standard time zone
// without adjustment rules for 1883-1918
TimeZoneInfo earlyEstZone = TimeZoneInfo.CreateCustomTimeZone("Eastern Standard Time",
new TimeSpan(-5, 0, 0),
" (GMT-05:00) Eastern Time (United States)",
"Eastern Standard Time");
serializedEst = earlyEstZone.ToSerializedString();
}
private static DateTime ConvertUtcTime(DateTime utcDate, TimeZoneInfo tz)
{
// Use time zone object from registry
if (utcDate.Year > 1917)
{
return TimeZoneInfo.ConvertTimeFromUtc(utcDate, tz);
}
// Handle dates before introduction of DST
else
{
// Restore serialized time zone object
tz = TimeZoneInfo.FromSerializedString(serializedEst);
return TimeZoneInfo.ConvertTimeFromUtc(utcDate, tz);
}
}
}
處理時區例外狀況
因為登錄是一種動態結構,所以內容會受到意外或刻意變更而改變。也就是說,登錄中應該要定義才能使應用程式順利執行的時區,可能並不存在。如果沒有時區序列化及還原序列化支援,您就只能結束應用程式才能處理產生的 TimeZoneNotFoundException 結果。但是,您可以使用時區序列化及還原序列化的方式,將所需的時區從序列化字串還原以處理非預期的 TimeZoneNotFoundException,應用程式就能繼續執行。
下列範例會建立並序列化自訂的「中央標準時間」區。然後嘗試擷取登錄中的「中央標準時間」區。如果擷取作業擲回 TimeZoneNotFoundException 或 InvalidTimeZoneException,例外處理常式就會還原序列化時區。
Imports System.Collections.Generic
Public Class TimeZoneApplication
' Define collection of custom time zones
Private customTimeZones As New Dictionary(Of String, String)
Private cst As TimeZoneInfo
Public Sub New()
' Define custom Central Standard Time
'
' Declare necessary TimeZoneInfo.AdjustmentRule objects for time zone
Dim customTimeZone As TimeZoneInfo
Dim delta As New TimeSpan(1, 0, 0)
Dim adjustment As TimeZoneInfo.AdjustmentRule
Dim adjustmentList As New List(Of TimeZoneInfo.AdjustmentRule)
' Declare transition time variables to hold transition time information
Dim transitionRuleStart, transitionRuleEnd As TimeZoneInfo.TransitionTime
' Define end rule (for 1976-2006)
transitionRuleEnd = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(#02:00:00AM#, 10, 5, DayOfWeek.Sunday)
' Define rule (1976-1986)
transitionRuleStart = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(#2:00:00AM#, 04, 05, DayOfWeek.Sunday)
adjustment = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(#01/01/1976#, #12/31/1986#, delta, transitionRuleStart, transitionRuleEnd)
adjustmentList.Add(adjustment)
' Define rule (1987-2006)
transitionRuleStart = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(#2:00:00AM#, 04, 01, DayOfWeek.Sunday)
adjustment = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(#01/01/1987#, #12/31/2006#, delta, transitionRuleStart, transitionRuleEnd)
adjustmentList.Add(adjustment)
' Define rule (2007- )
transitionRuleStart = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(#2:00:00AM#, 03, 02, DayOfWeek.Sunday)
transitionRuleEnd = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(#2:00:00AM#, 11, 01, DayOfWeek.Sunday)
adjustment = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(#01/01/2007#, Date.MaxValue.Date, delta, transitionRuleStart, transitionRuleEnd)
adjustmentList.Add(adjustment)
' Create custom time zone
customTimeZone = TimeZoneInfo.CreateCustomTimeZone("Central Standard Time", _
New TimeSpan(-6, 0, 0), _
"(GMT-06:00) Central Time (US Only)", "Central Standard Time", _
"Central Daylight Time", adjustmentList.ToArray())
' Add time zone to collection
customTimeZones.Add(customTimeZone.Id, customTimeZone.ToSerializedString())
' Create any other required time zones
End Sub
Public Shared Sub Main()
Dim tza As New TimeZoneApplication()
tza.AppEntryPoint()
End Sub
Private Sub AppEntryPoint()
Try
cst = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time")
Catch e As TimeZoneNotFoundException
If customTimeZones.ContainsKey("Central Standard Time")
HandleTimeZoneException("Central Standard Time")
End If
Catch e As InvalidTimeZoneException
If customTimeZones.ContainsKey("Central Standard Time")
HandleTimeZoneException("Central Standard Time")
End If
End Try
If cst Is Nothing Then
Console.WriteLine("Unable to load Central Standard Time zone.")
Return
End If
Dim currentTime As Date = Date.Now
Console.WriteLine("The current {0} time is {1}.", _
IIf(TimeZoneInfo.Local.IsDaylightSavingTime(currentTime), _
TimeZoneInfo.Local.StandardName, _
TimeZoneInfo.Local.DaylightName), _
currentTime.ToString("f"))
Console.WriteLine("The current {0} time is {1}.", _
IIf(cst.IsDaylightSavingTime(currentTime), _
cst.StandardName, _
cst.DaylightName), _
TimeZoneInfo.ConvertTime(currentTime, TimeZoneInfo.Local, cst).ToString("f"))
End Sub
Private Sub HandleTimeZoneException(timeZoneName As String)
Dim tzString As String = customTimeZones.Item(timeZoneName)
cst = TimeZoneInfo.FromSerializedString(tzString)
End Sub
End Class
using System;
using System.Collections.Generic;
public class TimeZoneApplication
{
// Define collection of custom time zones
private Dictionary<string, string> customTimeZones = new Dictionary<string, string>();
private TimeZoneInfo cst;
public TimeZoneApplication()
{
// Create custom Central Standard Time
//
// Declare necessary TimeZoneInfo.AdjustmentRule objects for time zone
TimeZoneInfo customTimeZone;
TimeSpan delta = new TimeSpan(1, 0, 0);
TimeZoneInfo.AdjustmentRule adjustment;
List<TimeZoneInfo.AdjustmentRule> adjustmentList = new List<TimeZoneInfo.AdjustmentRule>();
// Declare transition time variables to hold transition time information
TimeZoneInfo.TransitionTime transitionRuleStart, transitionRuleEnd;
// Define end rule (for 1976-2006)
transitionRuleEnd = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 2, 0, 0), 10, 5, DayOfWeek.Sunday);
// Define rule (1976-1986)
transitionRuleStart = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 2, 0, 0), 04, 05, DayOfWeek.Sunday);
adjustment = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(new DateTime(1976, 1, 1), new DateTime(1986, 12, 31), delta, transitionRuleStart, transitionRuleEnd);
adjustmentList.Add(adjustment);
// Define rule (1987-2006)
transitionRuleStart = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 2, 0, 0), 04, 01, DayOfWeek.Sunday);
adjustment = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(new DateTime(1987, 1, 1), new DateTime(2006, 12, 31), delta, transitionRuleStart, transitionRuleEnd);
adjustmentList.Add(adjustment);
// Define rule (2007- )
transitionRuleStart = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 2, 0, 0), 03, 02, DayOfWeek.Sunday);
transitionRuleEnd = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 2, 0, 0), 11, 01, DayOfWeek.Sunday);
adjustment = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(new DateTime(2007, 01, 01), DateTime.MaxValue.Date, delta, transitionRuleStart, transitionRuleEnd);
adjustmentList.Add(adjustment);
// Create custom U.S. Central Standard Time zone
customTimeZone = TimeZoneInfo.CreateCustomTimeZone("Central Standard Time",
new TimeSpan(-6, 0, 0),
"(GMT-06:00) Central Time (US Only)", "Central Standard Time",
"Central Daylight Time", adjustmentList.ToArray());
// Add time zone to collection
customTimeZones.Add(customTimeZone.Id, customTimeZone.ToSerializedString());
// Create any other required time zones
}
public static void Main()
{
TimeZoneApplication tza = new TimeZoneApplication();
tza.AppEntryPoint();
}
private void AppEntryPoint()
{
try
{
cst = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
}
catch (TimeZoneNotFoundException)
{
if (customTimeZones.ContainsKey("Central Standard Time"))
HandleTimeZoneException("Central Standard Time");
}
catch (InvalidTimeZoneException)
{
if (customTimeZones.ContainsKey("Central Standard Time"))
HandleTimeZoneException("Central Standard Time");
}
if (cst == null)
{
Console.WriteLine("Unable to load Central Standard Time zone.");
return;
}
DateTime currentTime = DateTime.Now;
Console.WriteLine("The current {0} time is {1}.",
TimeZoneInfo.Local.IsDaylightSavingTime(currentTime) ?
TimeZoneInfo.Local.StandardName :
TimeZoneInfo.Local.DaylightName,
currentTime.ToString("f"));
Console.WriteLine("The current {0} time is {1}.",
cst.IsDaylightSavingTime(currentTime) ?
cst.StandardName :
cst.DaylightName,
TimeZoneInfo.ConvertTime(currentTime, TimeZoneInfo.Local, cst).ToString("f"));
}
private void HandleTimeZoneException(string timeZoneName)
{
string tzString = customTimeZones[timeZoneName];
cst = TimeZoneInfo.FromSerializedString(tzString);
}
}
需要時儲存已序列化的字串及還原
上一個範例將時區資訊儲存成字串變數,並在需要時還原。但是內含序列化時區資訊的字串可以儲存在儲存媒體中,例如外部檔案、應用程式內嵌的資源檔,或是登錄中 (請注意,自訂時區資訊應該要和登錄中的系統時區索引鍵分開儲存)。
以這個方式儲存序列化時區字串,也會將時區建立常式和應用程式分開。例如,時區建立常式也可以執行和建立資料檔,內含應用程式可使用的歷史時區資訊。資料檔就可以和應用程式一起安裝,當應用程式需要時就可以開啟、還原序列化一或多個時區。
如需使用內嵌資源以儲存序列化時區資料的詳細資訊,請參閱 HOW TO:將時區儲存到內嵌資源及 HOW TO:從內嵌資源還原時區。