Поделиться через


Сохранение и восстановление часовых поясов

Класс 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

Хранение сериализованной строки и восстановление ее при необходимости

В предыдущих примерах данные часового пояса хранятся в строковой переменной и восстанавливаются при необходимости. Однако строка, содержащая сериализованные сведения часового пояса, может храниться в некоторых носителях хранилища, таких как внешний файл, файл ресурсов, внедренный в приложение или реестр. (Обратите внимание, что сведения о пользовательских часовых поясах должны храниться отдельно от разделов часового пояса системы в реестре.)

Хранение сериализованной строки часового пояса таким образом также отделяет подпрограмму создания часового пояса от самого приложения. Например, подпрограмма создания часового пояса может выполнять и создавать файл данных, содержащий исторические сведения о часовом поясе, которые может использовать приложение. Затем файл данных можно установить с приложением, и его можно открыть, а один или несколько часовых поясов можно десериализировать, когда приложение требует их.

Пример использования внедренного ресурса для хранения сериализованных данных часового пояса см. в статье "Практическое руководство. Сохранение часовых поясов в внедренном ресурсе и практическое руководство. Восстановление часовых поясов из внедренного ресурса".

См. также