Udostępnij za pośrednictwem


Wybierz między DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnly i TimeZoneInfo

Aplikacje .NET mogą używać informacji o dacie i godzinie na kilka sposobów. Bardziej typowe zastosowania informacji o dacie i godzinie to:

  • Aby odzwierciedlić tylko datę, bez uwzględniania informacji o godzinie.
  • Aby odzwierciedlić tylko godzinę, w taki sposób, że informacje o dacie nie były ważne.
  • Aby odzwierciedlić abstrakcyjną datę i godzinę, która nie jest powiązana z określonym czasem i miejscem (na przykład większość sklepów w sieci międzynarodowej jest otwarta w dni powszednie o godzinie 9:00).
  • Aby pobrać informacje o dacie i czasie ze źródeł spoza platformy .NET, gdzie te informacje są zazwyczaj przechowywane w prostych typach danych.
  • Aby jednoznacznie i bez wątpliwości wskazać pojedynczy punkt w czasie. Niektóre aplikacje wymagają, aby data i godzina były jednoznaczne tylko w systemie gospodarza. Inne aplikacje wymagają, aby data była jednoznaczna we wszystkich systemach (to znaczy, że data zserializowana w jednym systemie może być poprawnie deserializowana i używana w innym systemie gdziekolwiek na świecie).
  • Aby zachować wiele powiązanych czasów (na przykład czasu lokalnego obiektu żądającego i czasu otrzymania żądania internetowego przez serwer).
  • Aby wykonać arytmetykę daty i czasu, uzyskując wynik, który jednoznacznie identyfikuje pojedynczy punkt w czasie.

Platforma .NET zawiera typy DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnlyi TimeZoneInfo, z których wszystkie mogą służyć do tworzenia aplikacji, które współpracują z datami i godzinami.

Uwaga

W tym artykule nie omówiono TimeZone, ponieważ jego funkcjonalność jest prawie całkowicie włączona do klasy TimeZoneInfo. Jeśli to możliwe, użyj klasy TimeZoneInfo zamiast klasy TimeZone.

Struktura DateTimeOffset

Struktura DateTimeOffset reprezentuje wartość daty i godziny wraz z przesunięciem wskazującym, ile ta wartość różni się od czasu UTC. W związku z tym wartość zawsze jednoznacznie identyfikuje pojedynczy punkt w czasie.

Typ DateTimeOffset zawiera wszystkie funkcje typu DateTime wraz z rozpoznawaniem strefy czasowej. To sprawia, że nadaje się do aplikacji, które:

  • Jednoznacznie i jednoznacznie identyfikuje pojedynczy punkt w czasie. Typ DateTimeOffset może służyć do jednoznacznego zdefiniowania znaczenia "teraz", rejestrowania czasów transakcji, rejestrowania czasów systemu lub zdarzeń aplikacji oraz rejestrowania czasu tworzenia i modyfikowania plików.
  • Wykonaj ogólne działania arytmetyczne na datach i godzinach.
  • Zachowaj wiele powiązanych czasów, o ile te czasy są przechowywane jako dwie oddzielne wartości lub jako dwa elementy struktury.

Uwaga

Te zastosowania dla wartości DateTimeOffset są znacznie bardziej powszechne niż te dla wartości DateTime. W związku z tym należy rozważyć DateTimeOffset jako domyślny typ daty i godziny tworzenia aplikacji.

Wartość DateTimeOffset nie jest powiązana z określoną strefą czasową, ale może pochodzić z różnych stref czasowych. W poniższym przykładzie wymieniono strefy czasowe, do których może należeć kilka DateTimeOffset wartości (w tym lokalny czas standardowy pacyficzny).

using System;
using System.Collections.ObjectModel;

public class TimeOffsets
{
   public static void Main()
   {
      DateTime thisDate = new DateTime(2007, 3, 10, 0, 0, 0);
      DateTime dstDate = new DateTime(2007, 6, 10, 0, 0, 0);
      DateTimeOffset thisTime;

      thisTime = new DateTimeOffset(dstDate, new TimeSpan(-7, 0, 0));
      ShowPossibleTimeZones(thisTime);

      thisTime = new DateTimeOffset(thisDate, new TimeSpan(-6, 0, 0));
      ShowPossibleTimeZones(thisTime);

      thisTime = new DateTimeOffset(thisDate, new TimeSpan(+1, 0, 0));
      ShowPossibleTimeZones(thisTime);
   }

   private static void ShowPossibleTimeZones(DateTimeOffset offsetTime)
   {
      TimeSpan offset = offsetTime.Offset;
      ReadOnlyCollection<TimeZoneInfo> timeZones;

      Console.WriteLine("{0} could belong to the following time zones:",
                        offsetTime.ToString());
      // Get all time zones defined on local system
      timeZones = TimeZoneInfo.GetSystemTimeZones();
      // Iterate time zones
      foreach (TimeZoneInfo timeZone in timeZones)
      {
         // Compare offset with offset for that date in that time zone
         if (timeZone.GetUtcOffset(offsetTime.DateTime).Equals(offset))
            Console.WriteLine("   {0}", timeZone.DisplayName);
      }
      Console.WriteLine();
   }
}
// This example displays the following output to the console:
//       6/10/2007 12:00:00 AM -07:00 could belong to the following time zones:
//          (GMT-07:00) Arizona
//          (GMT-08:00) Pacific Time (US & Canada)
//          (GMT-08:00) Tijuana, Baja California
//
//       3/10/2007 12:00:00 AM -06:00 could belong to the following time zones:
//          (GMT-06:00) Central America
//          (GMT-06:00) Central Time (US & Canada)
//          (GMT-06:00) Guadalajara, Mexico City, Monterrey - New
//          (GMT-06:00) Guadalajara, Mexico City, Monterrey - Old
//          (GMT-06:00) Saskatchewan
//
//       3/10/2007 12:00:00 AM +01:00 could belong to the following time zones:
//          (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
//          (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague
//          (GMT+01:00) Brussels, Copenhagen, Madrid, Paris
//          (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb
//          (GMT+01:00) West Central Africa
Imports System.Collections.ObjectModel

Module TimeOffsets
    Public Sub Main()
        Dim thisTime As DateTimeOffset

        thisTime = New DateTimeOffset(#06/10/2007#, New TimeSpan(-7, 0, 0))
        ShowPossibleTimeZones(thisTime)

        thisTime = New DateTimeOffset(#03/10/2007#, New TimeSpan(-6, 0, 0))
        ShowPossibleTimeZones(thisTime)

        thisTime = New DateTimeOffset(#03/10/2007#, New TimeSpan(+1, 0, 0))
        ShowPossibleTimeZones(thisTime)
    End Sub

    Private Sub ShowPossibleTimeZones(offsetTime As DateTimeOffset)
        Dim offset As TimeSpan = offsetTime.Offset
        Dim timeZones As ReadOnlyCollection(Of TimeZoneInfo)

        Console.WriteLine("{0} could belong to the following time zones:", _
                          offsetTime.ToString())
        ' Get all time zones defined on local system
        timeZones = TimeZoneInfo.GetSystemTimeZones()
        ' Iterate time zones
        For Each timeZone As TimeZoneInfo In timeZones
            ' Compare offset with offset for that date in that time zone
            If timeZone.GetUtcOffset(offsetTime.DateTime).Equals(offset) Then
                Console.WriteLine("   {0}", timeZone.DisplayName)
            End If
        Next
        Console.WriteLine()
    End Sub
End Module
' This example displays the following output to the console:
'       6/10/2007 12:00:00 AM -07:00 could belong to the following time zones:
'          (GMT-07:00) Arizona
'          (GMT-08:00) Pacific Time (US & Canada)
'          (GMT-08:00) Tijuana, Baja California
'       
'       3/10/2007 12:00:00 AM -06:00 could belong to the following time zones:
'          (GMT-06:00) Central America
'          (GMT-06:00) Central Time (US & Canada)
'          (GMT-06:00) Guadalajara, Mexico City, Monterrey - New
'          (GMT-06:00) Guadalajara, Mexico City, Monterrey - Old
'          (GMT-06:00) Saskatchewan
'       
'       3/10/2007 12:00:00 AM +01:00 could belong to the following time zones:
'          (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
'          (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague
'          (GMT+01:00) Brussels, Copenhagen, Madrid, Paris
'          (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb
'          (GMT+01:00) West Central Africa

Dane wyjściowe pokazują, że każda wartość daty i godziny w tym przykładzie może należeć do co najmniej trzech różnych stref czasowych. Wartość DateTimeOffset z dnia 6.10.2007 pokazuje, że jeśli wartość daty i godziny reprezentuje czas letni, jej przesunięcie od czasu UTC nie musi koniecznie odpowiadać ani podstawowemu przesunięciu UTC źródłowej strefy czasowej, ani przesunięciu UTC, które znajduje się w nazwie wyświetlanej. Ponieważ pojedyncza wartość DateTimeOffset nie jest ściśle połączona ze strefą czasową, nie może odzwierciedlać przejścia strefy czasowej do i od czasu letniego. Może to być problematyczne, gdy arytmetyka daty i godziny jest używana do manipulowania wartością DateTimeOffset. Aby zapoznać się z omówieniem sposobu wykonywania operacji arytmetycznych daty i godziny w sposób uwzględniający reguły korekty strefy czasowej, zobacz Wykonywanie operacji arytmetycznych z datami i godzinami.

Struktura DateTime

Wartość DateTime definiuje określoną datę i godzinę. Zawiera ona właściwość Kind, która zawiera ograniczone informacje o strefie czasowej, do której należy ta data i godzina. Wartość DateTimeKind zwracana przez właściwość Kind wskazuje, czy wartość DateTime reprezentuje czas lokalny (DateTimeKind.Local), uniwersalny czas koordynowany (UTC) (DateTimeKind.Utc) lub nieokreślony czas (DateTimeKind.Unspecified).

Struktura DateTime jest odpowiednia dla aplikacji o co najmniej jednej z następujących cech:

  • Praca z abstrakcyjnymi datami i godzinami.
  • Praca z datami i godzinami, dla których brakuje informacji o strefie czasowej.
  • Pracuj wyłącznie z datami i godzinami UTC.
  • Wykonuj obliczenia z datami i czasem, ale zwracaj uwagę na ogólne wyniki. Na przykład w operacji dodawania, która dodaje sześć miesięcy do określonej daty i godziny, często nie jest ważne, czy wynik jest dostosowywany do czasu letniego.

O ile określona wartość DateTime nie reprezentuje UTC, ta wartość daty i godziny jest często niejednoznaczna lub ograniczona w swojej przenośności. Jeśli na przykład wartość DateTime reprezentuje czas lokalny, jest to przenośne w tej lokalnej strefie czasowej (czyli jeśli wartość jest deserializowana w innym systemie w tej samej strefie czasowej, ta wartość nadal jednoznacznie identyfikuje pojedynczy punkt w czasie). Poza lokalną strefą czasową wartość DateTime może mieć wiele interpretacji. Jeśli właściwość Kind wartości jest DateTimeKind.Unspecified, jest jeszcze mniej przenośna: jest teraz niejednoznaczna w obrębie tej samej strefy czasowej, a nawet w tym samym systemie, w którym została po raz pierwszy zserializowana. Tylko wtedy, gdy wartość DateTime reprezentuje UTC, ta wartość jednoznacznie identyfikuje pojedynczy punkt w czasie niezależnie od systemu lub strefy czasowej, w której jest używana wartość.

Ważny

Podczas zapisywania lub udostępniania danych DateTime użyj czasu UTC i ustaw właściwość DateTime wartości Kind na DateTimeKind.Utc.

Struktura DateOnly

Struktura DateOnly reprezentuje określoną datę bez godziny. Ponieważ nie ma składnika czasu, reprezentuje datę od początku dnia do końca dnia. Ta struktura jest idealna do przechowywania określonych dat, takich jak data urodzenia, data rocznicy, święto lub data związana z firmą.

Chociaż można użyć DateTime, ignorując składnik czasu, istnieje kilka korzyści związanych z używaniem DateOnly niż DateTime:

  • Struktura DateTime może przejść do poprzedniego lub następnego dnia, jeśli zostanie przesunięta przez strefę czasową. DateOnly nie można skompensować przesunięciem czasowym i zawsze reprezentuje datę, która została ustawiona.
  • Serializowanie struktury DateTime obejmuje składnik czasu, który może zasłonić intencję danych. Ponadto DateOnly serializuje mniej danych.
  • Gdy kod wchodzi w interakcję z bazą danych, taką jak program SQL Server, wszystkie daty są zwykle przechowywane jako typ danych date, który nie zawiera godziny. DateOnly lepiej pasuje do typu bazy danych.

Aby uzyskać więcej informacji na temat DateOnly, zobacz Jak używać struktur DateOnly i TimeOnly.

Ważny

DateOnly nie jest dostępna dla programu .NET Framework.

Struktura TimeSpan

Struktura TimeSpan reprezentuje przedział czasu. Są to dwa typowe zastosowania:

  • Odzwierciedla interwał czasu między dwiema wartościami daty i godziny. Na przykład odejmowanie jednej DateTime wartości z innej zwraca wartość TimeSpan.
  • Pomiar czasu, który upłynął. Na przykład właściwość Stopwatch.Elapsed zwraca wartość TimeSpan, która odzwierciedla przedział czasu, który upłynął od wywołania jednej z metod Stopwatch, które zaczynają mierzyć upływ czasu.

Wartość TimeSpan może być również używana jako zamiana wartości DateTime, gdy ta wartość odzwierciedla godzinę bez odwołania do określonego dnia. To użycie jest podobne do właściwości DateTime.TimeOfDay i DateTimeOffset.TimeOfDay, które zwracają wartość TimeSpan reprezentującą godzinę bez odwołania do daty. Na przykład struktura TimeSpan może służyć do odzwierciedlenia dziennego otwarcia lub zamknięcia sklepu lub może służyć do reprezentowania czasu, w którym występuje dowolne zdarzenie regularne.

W poniższym przykładzie zdefiniowano strukturę StoreInfo zawierającą TimeSpan obiektów do otwierania i zamykania magazynu, a także obiektu TimeZoneInfo reprezentującego strefę czasową magazynu. Struktura obejmuje również dwie metody, IsOpenNow i IsOpenAt, które wskazują, czy sklep jest otwarty w czasie określonym przez użytkownika, który przyjmuje się, że znajduje się w lokalnej strefie czasowej.

using System;

public struct StoreInfo
{
   public String store;
   public TimeZoneInfo tz;
   public TimeSpan open;
   public TimeSpan close;

   public bool IsOpenNow()
   {
      return IsOpenAt(DateTime.Now.TimeOfDay);
   }

   public bool IsOpenAt(TimeSpan time)
   {
      TimeZoneInfo local = TimeZoneInfo.Local;
      TimeSpan offset = TimeZoneInfo.Local.BaseUtcOffset;

      // Is the store in the same time zone?
      if (tz.Equals(local)) {
         return time >= open & time <= close;
      }
      else {
         TimeSpan delta = TimeSpan.Zero;
         TimeSpan storeDelta = TimeSpan.Zero;

         // Is it daylight saving time in either time zone?
         if (local.IsDaylightSavingTime(DateTime.Now.Date + time))
            delta = local.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1].DaylightDelta;

         if (tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(DateTime.Now.Date + time, local, tz)))
            storeDelta = tz.GetAdjustmentRules()[tz.GetAdjustmentRules().Length - 1].DaylightDelta;

         TimeSpan comparisonTime = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate();
         return comparisonTime >= open && comparisonTime <= close;
      }
   }
}
Public Structure StoreInfo
    Dim store As String
    Dim tz As TimeZoneInfo
    Dim open As TimeSpan
    Dim close As TimeSpan

    Public Function IsOpenNow() As Boolean
        Return IsOpenAt(Date.Now.TimeOfDay)
    End Function

    Public Function IsOpenAt(time As TimeSpan) As Boolean
        Dim local As TimeZoneInfo = TimeZoneInfo.Local
        Dim offset As TimeSpan = TimeZoneInfo.Local.BaseUtcOffset

        ' Is the store in the same time zone?
        If tz.Equals(local) Then
            Return time >= open AndAlso time <= close
        Else
            Dim delta As TimeSpan = TimeSpan.Zero
            Dim storeDelta As TimeSpan = TimeSpan.Zero

            ' Is it daylight saving time in either time zone?
            If local.IsDaylightSavingTime(Date.Now.Date + time) Then
                delta = local.GetAdjustmentRules(local.GetAdjustmentRules().Length - 1).DaylightDelta
            End If
            If tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(Date.Now.Date + time, local, tz))
                storeDelta = tz.GetAdjustmentRules(tz.GetAdjustmentRules().Length - 1).DaylightDelta
            End If
            Dim comparisonTime As TimeSpan = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate
            Return (comparisonTime >= open AndAlso comparisonTime <= close)
        End If
    End Function
End Structure

Struktura StoreInfo może być następnie używana przez kod klienta, taki jak poniżej.

public class Example
{
   public static void Main()
   {
      // Instantiate a StoreInfo object.
      var store103 = new StoreInfo();
      store103.store = "Store #103";
      store103.tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
      // Store opens at 8:00.
      store103.open = new TimeSpan(8, 0, 0);
      // Store closes at 9:30.
      store103.close = new TimeSpan(21, 30, 0);

      Console.WriteLine("Store is open now at {0}: {1}",
                        DateTime.Now.TimeOfDay, store103.IsOpenNow());
      TimeSpan[] times = { new TimeSpan(8, 0, 0), new TimeSpan(21, 0, 0),
                           new TimeSpan(4, 59, 0), new TimeSpan(18, 31, 0) };
      foreach (var time in times)
         Console.WriteLine("Store is open at {0}: {1}",
                           time, store103.IsOpenAt(time));
   }
}
// The example displays the following output:
//       Store is open now at 15:29:01.6129911: True
//       Store is open at 08:00:00: True
//       Store is open at 21:00:00: True
//       Store is open at 04:59:00: False
//       Store is open at 18:31:00: True
Module Example
    Public Sub Main()
        ' Instantiate a StoreInfo object.
        Dim store103 As New StoreInfo()
        store103.store = "Store #103"
        store103.tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time")
        ' Store opens at 8:00.
        store103.open = new TimeSpan(8, 0, 0)
        ' Store closes at 9:30.
        store103.close = new TimeSpan(21, 30, 0)

        Console.WriteLine("Store is open now at {0}: {1}",
                          Date.Now.TimeOfDay, store103.IsOpenNow())
        Dim times() As TimeSpan = {New TimeSpan(8, 0, 0),
                                    New TimeSpan(21, 0, 0),
                                    New TimeSpan(4, 59, 0),
                                    New TimeSpan(18, 31, 0)}
        For Each time In times
            Console.WriteLine("Store is open at {0}: {1}",
                              time, store103.IsOpenAt(time))
        Next
    End Sub
End Module
' The example displays the following output:
'       Store is open now at 15:29:01.6129911: True
'       Store is open at 08:00:00: True
'       Store is open at 21:00:00: False
'       Store is open at 04:59:00: False
'       Store is open at 18:31:00: False

Struktura TimeOnly

Struktura TimeOnly reprezentuje wartość godziny dnia, taką jak dzienny zegar alarmowy lub czas jedzenia lunchu każdego dnia. TimeOnly jest ograniczony do zakresu 00:00:00.00000000 - 23:59:59.999999999, określonej godziny dnia.

Przed wprowadzeniem typu TimeOnly programiści zazwyczaj używali typu DateTime lub typu TimeSpan do reprezentowania określonego czasu. Jednak użycie tych struktur do symulowania godziny bez daty może spowodować pewne problemy, które TimeOnly rozwiązać:

  • TimeSpan reprezentuje czas, który upłynął, na przykład czas mierzony za pomocą stopera. Górny zakres wynosi ponad 29 000 lat, a jego wartość może być ujemna, aby wskazać przechodzenie do tyłu w czasie. Ujemna TimeSpan nie wskazuje określonej godziny dnia.
  • Jeśli TimeSpan jest używany jako pora dnia, istnieje ryzyko, że można nim manipulować na wartość poza 24-godzinnym dniem. TimeOnly nie ma tego ryzyka. Jeśli na przykład zmiana pracy pracownika rozpoczyna się o godzinie 18:00 i trwa 8 godzin, dodanie 8 godzin do struktury TimeOnly przerzuci się na 2:00.
  • Użycie DateTime przez godzinę dnia wymaga, aby dowolna data została skojarzona z godziną, a następnie później zignorowana. Powszechną praktyką jest wybranie DateTime.MinValue (0001-01-01) jako daty, jednak jeśli godziny są odejmowane od wartości DateTime, może wystąpić wyjątek OutOfRange. TimeOnly nie ma tego problemu, ponieważ czas przemieszcza się do przodu i do tyłu w ramach 24-godzinnego przedziału czasu.
  • Serializowanie struktury DateTime obejmuje składnik daty, który może zasłonić intencję danych. Ponadto TimeOnly serializuje mniej danych.

Aby uzyskać więcej informacji na temat TimeOnly, zobacz Jak używać struktur DateOnly i TimeOnly.

Ważny

TimeOnly nie jest dostępna dla programu .NET Framework.

Klasa TimeZoneInfo

Klasa TimeZoneInfo reprezentuje dowolną strefę czasową Ziemi i umożliwia konwersję dowolnej daty i godziny w jednej strefie czasowej na jej odpowiednik w innej strefie czasowej. Klasa TimeZoneInfo umożliwia pracę z datami i godzinami, dzięki czemu każda wartość daty i godziny jednoznacznie identyfikuje pojedynczy punkt w czasie. Klasa TimeZoneInfo jest również rozszerzalna. Chociaż zależy to od informacji o strefie czasowej dostarczonej dla systemów Windows i zdefiniowanych w rejestrze, obsługuje tworzenie niestandardowych stref czasowych. Obsługuje również serializację i deserializację informacji o strefach czasowych.

W niektórych przypadkach pełne wykorzystanie klasy TimeZoneInfo może wymagać dalszych prac programistycznych. Jeśli wartości daty i godziny nie są ściśle powiązane ze strefami czasowymi, do których należą, wymagana jest dalsza praca. Jeśli aplikacja nie udostępnia mechanizmu łączenia daty i godziny z odpowiadającą im strefą czasową, łatwo jest, aby określona wartość daty i godziny stała się rozłączona z jej strefą czasową. Jedną z metod łączenia tych informacji jest zdefiniowanie klasy lub struktury zawierającej zarówno wartość daty, jak i godziny oraz skojarzony obiekt strefy czasowej.

Aby skorzystać z obsługi strefy czasowej na platformie .NET, musisz znać strefę czasową, do której należy wartość daty i godziny, gdy wystąpi wystąpienie tego obiektu daty i godziny. Strefa czasowa często nie jest znana, szczególnie w aplikacjach internetowych lub sieciowych.

Zobacz też