Сохранение и восстановление часовых поясов
Класс TimeZoneInfo использует реестр для получения предопределенных данных часового пояса. Однако реестр представляет собой динамическую структуру. Кроме того, сведения о часовом поясе, содержащиеся в реестре, используются операционной системой в основном для обработки корректировки времени и преобразований за текущий год. Это имеет два основных последствия для приложений, использующих точные данные часового пояса:
Часовой пояс, необходимый приложению, может не быть определен в реестре, или он может быть переименован или удален из реестра.
Часовой пояс, определенный в реестре, может не содержать сведений о конкретных правилах корректировки, необходимых для преобразования временных поясов.
Класс TimeZoneInfo решает эти ограничения с помощью поддержки сериализации (сохранения) и десериализации (восстановления) данных часового пояса.
Сериализация часовых поясов и десериализация
Сохранение и восстановление часового пояса путем сериализации и десериализации данных часового пояса включает только два вызова метода:
Можно сериализовать TimeZoneInfo объект, вызвав метод этого объекта ToSerializedString . Метод не принимает параметров и возвращает строку, содержащую сведения о часовом поясе.
Можно десериализировать TimeZoneInfo объект из сериализованной строки, передав эту строку методу
static
(Shared
в Visual Basic). TimeZoneInfo.FromSerializedString
Сценарии сериализации и десериализации
Возможность сохранения (или сериализации) TimeZoneInfo объекта в строку и восстановления (или десериализации) для последующего TimeZoneInfo использования увеличивает как служебную программу, так и гибкость класса. В этом разделе рассматриваются некоторые ситуации, в которых сериализация и десериализация наиболее полезны.
Сериализация и десериализация данных часового пояса в приложении
Сериализованный часовой пояс можно восстановить из строки при необходимости. Приложение может сделать это, если часовой пояс, полученный из реестра, не может правильно преобразовать дату и время в определенный диапазон дат. Например, данные часового пояса в реестре Windows XP поддерживают одно правило корректировки, а часовые пояса, определенные в реестре Windows Vista, обычно предоставляют сведения о двух правилах корректировки. Это означает, что исторические преобразования времени могут быть неточными. Сериализация и десериализация данных часового пояса могут обрабатывать это ограничение.
В следующем примере определен пользовательский класс TimeZoneInfo без правил корректировки, представляющий время в восточном стандартном часовом поясе США в период с 1883 по 1917 годы до введения перехода на летнее время в США. Настраиваемый часовой пояс сериализуется в переменной с глобальными область. Для ConvertUtcTime
преобразования часового пояса передается время преобразования в формате UTC. Если дата и время происходит в 1917 или более ранней версии, пользовательский часовой пояс Восточного стандарта восстанавливается из сериализованной строки и заменяет часовой пояс, полученный из реестра.
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);
}
}
}
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
Обработка исключений часового пояса
Так как реестр является динамической структурой, его содержимое подлежит случайному или преднамеренному изменении. Это означает, что часовой пояс, который должен быть определен в реестре и который требуется для успешного выполнения приложения, может быть отсутствует. Без поддержки сериализации и десериализации часового пояса у вас мало выбора, но для обработки результирующего TimeZoneNotFoundException приложения. Однако с помощью сериализации и десериализации часового пояса можно обрабатывать непредвиденные TimeZoneNotFoundException действия, восстанавливая требуемый часовой пояс из сериализованной строки, и приложение продолжит работать.
В следующем примере создается и сериализуется настраиваемый часовой пояс центрального стандарта. Затем он пытается получить центральный часовой пояс из реестра. Если операция извлечения вызывает либо исключение TimeZoneNotFoundExceptionInvalidTimeZoneException, обработчик исключений десериализирует часовой пояс.
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);
}
}
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
Хранение сериализованной строки и восстановление ее при необходимости
В предыдущих примерах данные часового пояса хранятся в строковой переменной и восстанавливаются при необходимости. Однако строка, содержащая сериализованные сведения часового пояса, может храниться в некоторых носителях хранилища, таких как внешний файл, файл ресурсов, внедренный в приложение или реестр. (Обратите внимание, что сведения о пользовательских часовых поясах должны храниться отдельно от разделов часового пояса системы в реестре.)
Хранение сериализованной строки часового пояса таким образом также отделяет подпрограмму создания часового пояса от самого приложения. Например, подпрограмма создания часового пояса может выполнять и создавать файл данных, содержащий исторические сведения о часовом поясе, которые может использовать приложение. Затем файл данных можно установить с приложением, и его можно открыть, а один или несколько часовых поясов можно десериализировать, когда приложение требует их.
Пример использования внедренного ресурса для хранения сериализованных данных часового пояса см. в статье "Практическое руководство. Сохранение часовых поясов в внедренном ресурсе и практическое руководство. Восстановление часовых поясов из внедренного ресурса".