保存和还原时区
TimeZoneInfo 类依赖注册表检索预定义的时区数据。 但是,注册表是一个动态结构。 此外,注册表包含的时区信息主要供操作系统在处理当前年份的时间调整和转换时使用。 对于依赖准确时区数据的应用程序来说,这有两个主要的影响:
注册表中可能未定义应用程序需要的时区,或者该时区可能已被重命名或从注册表中移除。
注册表中定义的时区可能缺少执行历史时区转换所需的特定调整规则的相关信息。
TimeZoneInfo 类支持对时区数据进行序列化(保存)和反序列化(还原),并以此解决了上述限制。
时区序列化和反序列化
在通过序列化和反序列化时区数据来保存和还原时区时,只需执行两次方法调用:
通过调用 TimeZoneInfo 对象的 ToSerializedString 方法可以序列化该对象。 该方法不带任何参数,并会返回一个包含时区信息的字符串。
通过向 static(在 Visual Basic 中为 Shared)TimeZoneInfo.FromSerializedString 方法传递一个序列化字符串,可以从该字符串反序列化 TimeZoneInfo 对象。
序列化和反序列化方案
这种可将 TimeZoneInfo 对象保存(或序列化)到一个字符串中以及还原(或反序列化)该对象以供以后使用的能力提高了 TimeZoneInfo 类的实用性和灵活性。 本节讨论序列化和反序列化最为有用的一些情况。
序列化和反序列化应用程序中的时区数据
应用程序可以根据需要从字符串还原序列化的时区。 如果从注册表中检索到的时区无法正确转换特定日期范围内的日期和时间,应用程序便可能执行还原操作。 例如,Windows XP 注册表中的时区数据只支持一个调整规则,而 Windows Vista 注册表中定义的时区通常会提供有关两个调整规则的信息。 这意味着,历史时间转换可能是不准确的。 对时区数据执行序列化和反序列化可以应对这种限制。
下面的示例定义了一个不带调整规则的自定义 TimeZoneInfo 类, 它表示美国引入夏时制之前的 1883 到 1917 年间的美国东部标准时区。 自定义时区在具有全局范围的变量中序列化。 同时将协调世界时 (UTC) 时间传递给时区转换方法 ConvertUtcTime 来进行转换。 如果该日期和时间发生在 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);
}
}
根据需要存储和还原序列化字符串
上面的示例先将时区信息存储在一个字符串变量中,然后根据需要还原该字符串。 但是,包含序列化时区信息的字符串自身也可以存储在某些存储介质中,例如外部文件、嵌入在应用程序中的资源文件或注册表 (请注意,有关自定义时区的信息应与系统的时区注册表项分开存储)。
按照此方式存储序列化时区字符串还可以将时区创建例程与应用程序本身分开。 例如,时区创建例程可以创建数据文件并在其中包含可供应用程序使用的历史时区信息,然后执行该文件。 这样,该数据文件便可随应用程序一起安装,应用程序在需要时可以打开该数据文件并序列化其中的一个或多个时区。
有关使用嵌入的资源存储序列化时区数据的示例,请参见如何:将时区保存到嵌入的资源中和如何:从嵌入的资源还原时区。