Wykonywanie operacji arytmetycznych na wartościach dat i godzin

Chociaż zarówno struktury, jak DateTime i DateTimeOffset zapewniają elementy członkowskie, które wykonują operacje arytmetyczne na ich wartościach, wyniki operacji arytmetycznych są bardzo różne. W tym artykule omówiono te różnice, odnosi się do stopnia świadomości strefy czasowej w danych daty i godziny oraz omówiono sposób wykonywania w pełni świadomych operacji strefy czasowej przy użyciu danych daty i godziny.

Porównania i operacje arytmetyczne z wartościami DateTime

Właściwość DateTime.Kind umożliwia przypisanie DateTimeKind wartości do daty i godziny, aby wskazać, czy reprezentuje czas lokalny, uniwersalny czas koordynowany (UTC) lub godzinę w nieokreślonej strefie czasowej. Jednak te ograniczone informacje o strefie czasowej są ignorowane podczas porównywania lub wykonywania arytmetyki daty i godziny dla DateTimeKind wartości. Poniższy przykład, który porównuje bieżącą godzinę lokalną z bieżącą godziną UTC, ilustruje sposób ignorowania informacji o strefie czasowej.

using System;

public enum TimeComparison
{
   EarlierThan = -1,
   TheSameAs = 0,
   LaterThan = 1
}

public class DateManipulation
{
   public static void Main()
   {
      DateTime localTime = DateTime.Now;
      DateTime utcTime = DateTime.UtcNow;

      Console.WriteLine("Difference between {0} and {1} time: {2}:{3} hours",
                        localTime.Kind,
                        utcTime.Kind,
                        (localTime - utcTime).Hours,
                        (localTime - utcTime).Minutes);
      Console.WriteLine("The {0} time is {1} the {2} time.",
                        localTime.Kind,
                        Enum.GetName(typeof(TimeComparison), localTime.CompareTo(utcTime)),
                        utcTime.Kind);
   }
}
// If run in the U.S. Pacific Standard Time zone, the example displays
// the following output to the console:
//    Difference between Local and Utc time: -7:0 hours
//    The Local time is EarlierThan the Utc time.
Public Enum TimeComparison As Integer
    EarlierThan = -1
    TheSameAs = 0
    LaterThan = 1
End Enum

Module DateManipulation
    Public Sub Main()
        Dim localTime As Date = Date.Now
        Dim utcTime As Date = Date.UtcNow

        Console.WriteLine("Difference between {0} and {1} time: {2}:{3} hours", _
                          localTime.Kind.ToString(), _
                          utcTime.Kind.ToString(), _
                          (localTime - utcTime).Hours, _
                          (localTime - utcTime).Minutes)
        Console.WriteLine("The {0} time is {1} the {2} time.", _
                          localTime.Kind.ToString(), _
                          [Enum].GetName(GetType(TimeComparison), localTime.CompareTo(utcTime)), _
                          utcTime.Kind.ToString())
        ' If run in the U.S. Pacific Standard Time zone, the example displays 
        ' the following output to the console:
        '    Difference between Local and Utc time: -7:0 hours
        '    The Local time is EarlierThan the Utc time.                                                    
    End Sub
End Module

Metoda CompareTo(DateTime) zgłasza, że czas lokalny jest wcześniejszy niż (lub mniej niż) czasu UTC, a operacja odejmowania wskazuje, że różnica między utc a czasem lokalnym dla systemu w strefie czasowej Pacyfik Pacyfik (czas standardowy) stanów Zjednoczonych wynosi siedem godzin. Jednak ponieważ te dwie wartości zapewniają różne reprezentacje pojedynczego punktu w czasie, w tym przypadku jest jasne, że interwał czasu jest całkowicie przypisany do przesunięcia lokalnej strefy czasowej z czasu UTC.

Ogólnie rzecz biorąc, DateTime.Kind właściwość nie ma wpływu na wyniki zwracane przez Kind metody porównania i arytmetyczne (jak wskazuje porównanie dwóch identycznych punktów w czasie), chociaż może mieć wpływ na interpretację tych wyników. Na przykład:

  • Wynik dowolnej operacji arytmetycznej wykonywanej na dwóch wartościach daty i godziny, których DateTime.Kind właściwości są równe DateTimeKind , odzwierciedlają rzeczywisty przedział czasu między dwiema wartościami. Podobnie porównanie dwóch takich wartości daty i godziny dokładnie odzwierciedla relację między godzinami.

  • Wynik dowolnej operacji arytmetycznej lub porównania wykonywanej na dwóch wartościach daty i godziny, których DateTime.Kind właściwości są równe DateTimeKind lub na dwóch wartościach daty i godziny z różnymi DateTime.Kind wartościami właściwości odzwierciedlają różnicę w czasie zegara między dwiema wartościami.

  • Operacje arytmetyczne lub porównawcze w lokalnych wartościach daty i godziny nie uwzględniają, czy określona wartość jest niejednoznaczna, czy nieprawidłowa, ani nie uwzględniają wpływu żadnych reguł korekty, które wynikają z przejścia lokalnej strefy czasowej do lub z czasu letniego.

  • Każda operacja, która porównuje lub oblicza różnicę między czasem UTC a czasem lokalnym, obejmuje interwał czasu równy przesunięciom lokalnej strefy czasowej z czasu UTC w wyniku.

  • Każda operacja, która porównuje lub oblicza różnicę między nieokreślonym czasem a czasem UTC lub czasem lokalnym, odzwierciedla prosty czas zegara. Różnice strefy czasowej nie są brane pod uwagę, a wynik nie odzwierciedla zastosowania reguł korekty strefy czasowej.

  • Każda operacja, która porównuje lub oblicza różnicę między dwoma nieokreślonymi godzinami, może zawierać nieznany interwał, który odzwierciedla różnicę między czasem w dwóch różnych strefach czasowych.

Istnieje wiele scenariuszy, w których różnice strefy czasowej nie mają wpływu na obliczenia daty i godziny (w celu omówienia niektórych z tych scenariuszy zobacz Wybieranie między wartościami DateTime, DateTimeOffset, TimeSpan i TimeZoneInfo) lub w których kontekst danych daty i godziny definiuje znaczenie operacji porównawczych lub arytmetycznych.

Porównania i operacje arytmetyczne z wartościami DateTimeOffset

DateTimeOffset Wartość zawiera nie tylko datę i godzinę, ale także przesunięcie, które jednoznacznie definiuje datę i godzinę względem czasu UTC. To przesunięcie umożliwia zdefiniowanie równości inaczej niż dla DateTime wartości. Wartości DateTime są równe, jeśli mają tę samą wartość daty i godziny, wartości są równe, DateTimeOffset jeśli oba odwołują się do tego samego punktu w czasie. W przypadku użycia w porównaniach i w większości operacji arytmetycznych, które określają interwał między dwiema datami i godzinami, DateTimeOffset wartość jest dokładniejsza i mniej potrzebna do interpretacji. Poniższy przykład, który jest DateTimeOffset odpowiednikiem poprzedniego przykładu, który porównał wartości lokalne i UTC DateTimeOffset , ilustruje tę różnicę w zachowaniu.

using System;

public enum TimeComparison
{
   EarlierThan = -1,
   TheSameAs = 0,
   LaterThan = 1
}

public class DateTimeOffsetManipulation
{
   public static void Main()
   {
      DateTimeOffset localTime = DateTimeOffset.Now;
      DateTimeOffset utcTime = DateTimeOffset.UtcNow;

      Console.WriteLine("Difference between local time and UTC: {0}:{1:D2} hours",
                        (localTime - utcTime).Hours,
                        (localTime - utcTime).Minutes);
      Console.WriteLine("The local time is {0} UTC.",
                        Enum.GetName(typeof(TimeComparison), localTime.CompareTo(utcTime)));
   }
}
// Regardless of the local time zone, the example displays
// the following output to the console:
//    Difference between local time and UTC: 0:00 hours.
//    The local time is TheSameAs UTC.
Public Enum TimeComparison As Integer
    EarlierThan = -1
    TheSameAs = 0
    LaterThan = 1
End Enum

Module DateTimeOffsetManipulation
    Public Sub Main()
        Dim localTime As DateTimeOffset = DateTimeOffset.Now
        Dim utcTime As DateTimeOffset = DateTimeOffset.UtcNow

        Console.WriteLine("Difference between local time and UTC: {0}:{1:D2} hours.", _
                          (localTime - utcTime).Hours, _
                          (localTime - utcTime).Minutes)
        Console.WriteLine("The local time is {0} UTC.", _
                          [Enum].GetName(GetType(TimeComparison), localTime.CompareTo(utcTime)))
    End Sub
End Module
' Regardless of the local time zone, the example displays 
' the following output to the console:
'    Difference between local time and UTC: 0:00 hours.
'    The local time is TheSameAs UTC.
'          Console.WriteLine(e.GetType().Name)

W tym przykładzie CompareTo metoda wskazuje, że bieżąca godzina lokalna i bieżąca godzina UTC są równe, a odejmowanie CompareTo(DateTimeOffset) wartości wskazuje, że różnica między dwoma razy wynosi TimeSpan.Zero.

Głównym ograniczeniem używania DateTimeOffset wartości w arytmetyce daty i godziny jest to, że chociaż DateTimeOffset wartości mają pewną świadomość strefy czasowej, nie są w pełni świadomi strefy czasowej. DateTimeOffset Mimo że przesunięcie wartości odzwierciedla przesunięcie strefy czasowej z czasu UTC, gdy DateTimeOffset zmienna jest po raz pierwszy przypisana wartości, staje się ona odłączona od strefy czasowej. Ponieważ nie jest już bezpośrednio skojarzony z rozpoznawalnym czasem, dodawanie i odejmowanie interwałów daty i godziny nie uwzględnia reguł korekty strefy czasowej.

Aby zilustrować, przejście do czasu letniego w centralnej strefie czasowej w USA odbywa się o godzinie 2:00 w dniu 9 marca 2008 r. Mając to na uwadze, dodanie dwugodzinnego interwału do centralnego czasu standardowego 1:30 w dniu 9 marca 2008 r., powinno spowodować wygenerowanie daty i godziny 5:00 w dniu 9 marca 2008 r. Jednak jak pokazano w poniższym przykładzie, wynik dodania wynosi 4:00 w dniu 9 marca 2008 r. Wynik tej operacji reprezentuje prawidłowy punkt w czasie, chociaż nie jest to czas w strefie czasowej, w której jesteśmy zainteresowani (czyli nie ma oczekiwanego przesunięcia strefy czasowej).

using System;

public class IntervalArithmetic
{
   public static void Main()
   {
      DateTime generalTime = new DateTime(2008, 3, 9, 1, 30, 0);
      const string tzName = "Central Standard Time";
      TimeSpan twoAndAHalfHours = new TimeSpan(2, 30, 0);

      // Instantiate DateTimeOffset value to have correct CST offset
      try
      {
         DateTimeOffset centralTime1 = new DateTimeOffset(generalTime,
                    TimeZoneInfo.FindSystemTimeZoneById(tzName).GetUtcOffset(generalTime));

         // Add two and a half hours
         DateTimeOffset centralTime2 = centralTime1.Add(twoAndAHalfHours);
         // Display result
         Console.WriteLine("{0} + {1} hours = {2}", centralTime1,
                                                    twoAndAHalfHours.ToString(),
                                                    centralTime2);
      }
      catch (TimeZoneNotFoundException)
      {
         Console.WriteLine("Unable to retrieve Central Standard Time zone information.");
      }
   }
}
// The example displays the following output to the console:
//    3/9/2008 1:30:00 AM -06:00 + 02:30:00 hours = 3/9/2008 4:00:00 AM -06:00
Module IntervalArithmetic
    Public Sub Main()
        Dim generalTime As Date = #03/09/2008 1:30AM#
        Const tzName As String = "Central Standard Time"
        Dim twoAndAHalfHours As New TimeSpan(2, 30, 0)

        ' Instantiate DateTimeOffset value to have correct CST offset
        Try
            Dim centralTime1 As New DateTimeOffset(generalTime, _
                       TimeZoneInfo.FindSystemTimeZoneById(tzName).GetUtcOffset(generalTime))

            ' Add two and a half hours      
            Dim centralTime2 As DateTimeOffset = centralTime1.Add(twoAndAHalfHours)
            ' Display result
            Console.WriteLine("{0} + {1} hours = {2}", centralTime1, _
                                                       twoAndAHalfHours.ToString(), _
                                                       centralTime2)
        Catch e As TimeZoneNotFoundException
            Console.WriteLine("Unable to retrieve Central Standard Time zone information.")
        End Try
    End Sub
End Module
' The example displays the following output to the console:
'    3/9/2008 1:30:00 AM -06:00 + 02:30:00 hours = 3/9/2008 4:00:00 AM -06:00

Operacje arytmetyczne z czasami w strefach czasowych

Klasa TimeZoneInfo zawiera metody konwersji, które automatycznie stosują korekty, gdy konwertują czasy z jednej strefy czasowej na inną. Te metody konwersji obejmują:

Aby uzyskać szczegółowe informacje, zobacz Konwertowanie czasów między strefami czasowymi.

Klasa TimeZoneInfo nie udostępnia żadnych metod, które automatycznie stosują reguły korekty podczas wykonywania arytmetyki daty i godziny. Można jednak zastosować reguły korekty, konwertując czas w strefie czasowej na UTC, wykonując operację arytmetyczną, a następnie konwertując z czasu UTC z powrotem na czas w strefie czasowej. Aby uzyskać szczegółowe informacje, zobacz How to: Use time zones in date and time arithmetic (Jak używać stref czasowych w arytmetyce daty i godziny).

Na przykład poniższy kod jest podobny do poprzedniego kodu, który dodał dwie i pół godziny do 2:00 w dniu 9 marca 2008 r. Jednak ponieważ konwertuje środkowy czas standardowy na UTC przed wykonaniem arytmetyki daty i godziny, a następnie konwertuje wynik z czasu UTC z powrotem na środkowy czas standardowy, wynikowy czas odzwierciedla przejście centralnej standardowej strefy czasowej na czas letni.

using System;

public class TimeZoneAwareArithmetic
{
   public static void Main()
   {
      const string tzName = "Central Standard Time";

      DateTime generalTime = new DateTime(2008, 3, 9, 1, 30, 0);
      TimeZoneInfo cst = TimeZoneInfo.FindSystemTimeZoneById(tzName);
      TimeSpan twoAndAHalfHours = new TimeSpan(2, 30, 0);

      // Instantiate DateTimeOffset value to have correct CST offset
      try
      {
         DateTimeOffset centralTime1 = new DateTimeOffset(generalTime,
                                       cst.GetUtcOffset(generalTime));

         // Add two and a half hours
         DateTimeOffset utcTime = centralTime1.ToUniversalTime();
         utcTime += twoAndAHalfHours;

         DateTimeOffset centralTime2 = TimeZoneInfo.ConvertTime(utcTime, cst);
         // Display result
         Console.WriteLine("{0} + {1} hours = {2}", centralTime1,
                                                    twoAndAHalfHours.ToString(),
                                                    centralTime2);
      }
      catch (TimeZoneNotFoundException)
      {
         Console.WriteLine("Unable to retrieve Central Standard Time zone information.");
      }
   }
}
// The example displays the following output to the console:
//    3/9/2008 1:30:00 AM -06:00 + 02:30:00 hours = 3/9/2008 5:00:00 AM -05:00
Module TimeZoneAwareArithmetic
    Public Sub Main()
        Const tzName As String = "Central Standard Time"

        Dim generalTime As Date = #03/09/2008 1:30AM#
        Dim cst As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tzName)
        Dim twoAndAHalfHours As New TimeSpan(2, 30, 0)

        ' Instantiate DateTimeOffset value to have correct CST offset
        Try
            Dim centralTime1 As New DateTimeOffset(generalTime, _
                       cst.GetUtcOffset(generalTime))

            ' Add two and a half hours 
            Dim utcTime As DateTimeOffset = centralTime1.ToUniversalTime()
            utcTime += twoAndAHalfHours

            Dim centralTime2 As DateTimeOffset = TimeZoneInfo.ConvertTime(utcTime, cst)
            ' Display result
            Console.WriteLine("{0} + {1} hours = {2}", centralTime1, _
                                                       twoAndAHalfHours.ToString(), _
                                                       centralTime2)
        Catch e As TimeZoneNotFoundException
            Console.WriteLine("Unable to retrieve Central Standard Time zone information.")
        End Try
    End Sub
End Module
' The example displays the following output to the console:
'    3/9/2008 1:30:00 AM -06:00 + 02:30:00 hours = 3/9/2008 5:00:00 AM -05:00

Zobacz też