Scegliere tra DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnly e TimeZoneInfo

Le applicazioni .NET possono usare le informazioni di data e ora in diversi modi. Gli usi più comuni delle informazioni relative a data e ora includono:

  • Indicare solo una data, perché le informazioni sull'ora non sono importanti.
  • Indicare solo un'ora, perché le informazioni sulla data non sono importanti.
  • Indicare una data e un'ora astratte non associate a un momento e a un luogo specifici. Ad esempio, la maggior parte dei negozi di una catena internazionale apre alle 09:00 di ogni giorno feriale.
  • Recuperare informazioni su data e ora da origini esterne a .NET, in genere quando tali informazioni sono archiviate in un tipo di dati semplice.
  • Identificare in modo univoco e senza ambiguità un singolo momento. Per alcune applicazioni è necessario che una data e un'ora non siano ambigue solo nel sistema host. Per altre app è necessario che non siano ambigue in tutti i sistemi (ovvero una data serializzata in un sistema può essere deserializzata in modo significativo e usata in un altro sistema in qualsiasi punto del mondo).
  • Mantenere più ore correlate, ad esempio l'ora locale del richiedente e l'ora di ricezione di una richiesta Web nel server.
  • Eseguire operazioni aritmetiche per date e ore, possibilmente con un risultato che identifichi in modo univoco e senza ambiguità una singolo momento.

.NET include i tipi DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnly e TimeZoneInfo, che possono tutti essere usati per compilare applicazioni in grado di funzionare con date e ore.

Nota

Questo articolo non illustra TimeZone perché la funzionalità è quasi interamente incorporata nella classe TimeZoneInfo. Quando possibile, usare la classe TimeZoneInfo anziché la classe TimeZone.

Struttura DateTimeOffset

La struttura DateTimeOffset rappresenta un valore di data e ora, insieme a un offset che indica la differenza di tale valore rispetto all'ora UTC. In questo modo, il valore identifica sempre senza ambiguità un singolo momento.

Il tipo DateTimeOffset include tutte le funzionalità del tipo DateTime insieme alla compatibilità del fuso orario. Questo lo rende adatto per le applicazioni che:

  • Identificano in modo univoco e non ambiguo un singolo momento. Il tipo DateTimeOffset può essere usato per definire senza ambiguità il significato di "adesso", per registrare data e ora delle transazioni, del sistema o degli eventi delle applicazioni, nonché registrare data e ora di creazione e modifica dei file.
  • Eseguono operazioni aritmetiche su data e ora.
  • Mantengono più date e ore correlate, purché vengano archiviate come due valori separati o due membri di una struttura.

Nota

Questi utilizzi per i valori DateTimeOffset sono molto più comuni di quelli per i valori DateTime . Di conseguenza, considerare DateTimeOffset come il tipo di data e ora predefinito per lo sviluppo di applicazioni.

Un valore DateTimeOffset non è associato a un determinato fuso orario, ma può derivare da fusi orari diversi. L'esempio seguente elenca i fusi orari a cui può appartenere un certo numero di valori DateTimeOffset (incluso un valore con ora solare Pacifico locale).

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

L'output mostra che ogni valore di data e ora nell'esempio può appartenere almeno a tre fusi orari diversi. Il valore DateTimeOffset 6/10/2007 mostra che se un valore di data e ora rappresenta un orario con ora legale, la sua differenza dall'ora UTC non corrisponde necessariamente alla differenza dall'ora UTC di base del fuso orario di origine o alla differenza dall'ora UTC indicata nel nome visualizzato. Poiché un singolo valore DateTimeOffset non è strettamente collegato al proprio fuso orario, non può indicare la transizione di un fuso orario da e verso l'ora legale. Questo comportamento può rivelarsi problematico quando vengono usate operazioni aritmetiche per date e ore per modificare un valore DateTimeOffset. Per informazioni su come eseguire operazioni aritmetiche per date e ore che tengano conto delle regole di adeguamento del fuso orario, vedere Performing arithmetic operations with dates and times (Esecuzione di operazioni aritmetiche con date e ore).

Struttura DateTime

Un valore DateTime definisce una data e un'ora specifiche. Include una proprietà Kind che contiene informazioni limitate sul fuso orario a cui appartengono la data e l'ora. Il valore DateTimeKind restituito dalla proprietà Kind indica se il valore DateTime rappresenta l'ora locale (DateTimeKind.Local), l'ora UTC (Coordinated Universal Time) (DateTimeKind.Utc) o un'ora non specificata (DateTimeKind.Unspecified).

La struttura DateTime è adatta alle applicazioni con una o più delle caratteristiche seguenti:

  • Usano date e ore astratte.
  • Usano date e ore per le quali mancano informazioni sul fuso orario.
  • Usano solo date e ore UTC.
  • Eseguono operazioni aritmetiche su date e ore, ma con particolare attenzione ai risultati generali. Ad esempio, in un'operazione di addizione che aggiunge sei mesi a una data e un'ora specifiche, spesso non è importante se il risultato viene adattato per l'ora legale.

A meno che un determinato valore DateTime non rappresenti un'ora UTC, il valore di data e ora è spesso ambiguo e limitato in fatto di portabilità. Ad esempio, se un valore DateTime rappresenta l'ora locale, è portabile all'interno del fuso orario locale. Di conseguenza, se il valore viene deserializzato in un altro sistema con lo stesso fuso orario, identifica comunque un singolo momento senza ambiguità. Al di fuori del fuso orario locale, tale valore DateTime è soggetto a più interpretazioni. Se la proprietà Kind del valore è DateTimeKind.Unspecified, è ancora meno portabile, in quanto è ora ambiguo all'interno dello stesso fuso orario e probabilmente anche nello stesso sistema in cui è stato inizialmente serializzato. Solo se un valore DateTime rappresenta un'ora UTC, identifica senza ambiguità un singolo momento, indipendentemente dal sistema o dal fuso orario in cui viene usato.

Importante

Quando si salvano o si condividono i dati DateTime, usare l'ora UTC e impostare la proprietà Kind del valore DateTime su DateTimeKind.Utc.

Struttura di DateOnly

La struttura di DateOnly rappresenta una data specifica, senza l'ora. Poiché non ha il componente ora, rappresenta una data dall'inizio alla fine del giorno. Questa struttura è ideale per archiviare date specifiche, ad esempio una data di nascita, una data di anniversario, una vacanza o date correlate all'azienda.

Anche se è possibile usare DateTime ignorando il componente ora, ci sono alcuni vantaggi nell'usare DateOnly invece di DateTime:

  • La struttura di DateTime può comprendere il giorno precedente o successivo se c'è una differenza in base a un fuso orario. DateOnly non può essere scostata da un fuso orario e rappresenta sempre la data impostata.
  • La serializzazione di una struttura DateTime include il componente ora, che può nascondere la finalità dei dati. Inoltre, DateOnly serializza un numero inferiore di dati.
  • Quando il codice interagisce con un database, ad esempio SQL Server, le date intere vengono in genere archiviate come tipo di dati date, che non includono un'ora. DateOnly corrisponde al tipo di database migliore.

Per altre informazioni su DateOnly, vedere Come usare le strutture di DateOnly e TimeOnly.

Importante

DateOnly non è disponibile in .NET Framework.

Struttura TimeSpan

La struttura TimeSpan rappresenta un intervallo di tempo. Ecco i due utilizzi tipici:

  • Indicare l'intervallo di tempo tra due valori di data e ora. Ad esempio, la sottrazione di un valore DateTime da un altro restituisce un valore TimeSpan .
  • Misurare il tempo trascorso. Ad esempio, la proprietà Stopwatch.Elapsed restituisce un valore TimeSpan che indica l'intervallo di tempo trascorso dalla chiamata a uno dei metodi Stopwatch che inizia a misurare il tempo trascorso.

Un valore TimeSpan può essere usato anche come sostituzione per un valore DateTime quando tale valore indica un momento senza riferimento a un determinato giorno. Questo utilizzo è simile alle proprietà DateTime.TimeOfDay e DateTimeOffset.TimeOfDay, che restituiscono un valore TimeSpan che rappresenta l'ora senza riferimento a una data. Ad esempio, la struttura TimeSpan può essere usata per indicare l'ora di apertura o di chiusura di un negozio oppure per rappresentare l'ora a cui si verifica un evento regolare.

L'esempio seguente definisce una struttura StoreInfo che include oggetti TimeSpan per le ore di apertura e di chiusura di un negozio, nonché un oggetto TimeZoneInfo che rappresenta il fuso orario del negozio. La struttura include anche due metodi, IsOpenNow e IsOpenAt, che indicano se il negozio è aperto a un'ora specificata dall'utente, che si suppone si trovi nel fuso orario locale.

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

La struttura StoreInfo può quindi essere usata da codice client simile al seguente.

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: False
//       Store is open at 04:59:00: False
//       Store is open at 18:31:00: False
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

Struttura di TimeOnly

La struttura di TimeOnly rappresenta un valore dell'ora del giorno, ad esempio una sveglia quotidiana o l'ora in cui si pranza ogni giorno. TimeOnly è limitato all'intervallo di 00:00:00.0000000 - 23:59:59.9999999, un'ora specifica del giorno.

Prima dell'introduzione del tipo TimeOnly, i programmatori usavano in genere il tipo DateTime o il tipo TimeSpan per rappresentare un orario specifico. Tuttavia, l'uso di queste strutture per simulare un'ora senza una data può introdurre alcuni problemi, che TimeOnly risolve:

  • TimeSpan rappresenta il tempo trascorso, ad esempio il tempo misurato con un cronometro. L'intervallo superiore è superiore a 29.000 anni e il suo valore può essere negativo per indicare lo spostamento indietro nel tempo. Un TimeSpan negativo non indica un'ora specifica del giorno.
  • Se TimeSpan viene usato come ora del giorno, c'è il rischio che possa essere modificato in un valore che non rientra nel giorno di 24 ore. TimeOnly non presenta questo rischio. Ad esempio, se il turno di lavoro di un dipendente inizia alle 18:00 e dura 8 ore, aggiungendo 8 ore alla struttura TimeOnly si passa a 02:00.
  • L'uso di DateTime per un'ora del giorno richiede che una data arbitraria sia associata all'ora e quindi ignorata in un secondo momento. È prassi comune scegliere DateTime.MinValue (0001-01-01) come data, tuttavia, se le ore vengono sottratte dal valore di DateTime, potrebbe verificarsi un'eccezione OutOfRange. TimeOnly non presenta questo problema perché il tempo scorre avanti e indietro intorno all'intervallo di 24 ore.
  • La serializzazione di una struttura DateTime include il componente data, che può nascondere la finalità dei dati. Inoltre, TimeOnly serializza un numero inferiore di dati.

Per altre informazioni su TimeOnly, vedere Come usare le strutture di DateOnly e TimeOnly.

Importante

TimeOnly non è disponibile in .NET Framework.

Classe TimeZoneInfo

La classe TimeZoneInfo class represents any of the Earth's time zones, and enables the conversion of any date and time in one time zone to its equivalent in another time zone. La classe TimeZoneInfo permette di usare date e ore in modo che qualsiasi valore di data e ora identifichi un singolo momento senza ambiguità. La classe TimeZoneInfo può anche essere estesa. Benché dipenda dalle informazioni sul fuso orario fornite per i sistemi Windows e definite nel Registro di sistema, questa classe supporta la creazione di fusi orari personalizzati. Supporta anche la serializzazione e la deserializzazione delle informazioni sul fuso orario.

In alcuni casi, per sfruttare tutti i vantaggi della classe TimeZoneInfo possono essere necessarie attività aggiuntive di sviluppo. I valori di data e ora non sono strettamente collegati ai fusi orari a cui appartengono, è necessario lavorarci ulteriormente. A meno che l'applicazione non fornisca un meccanismo per collegare una data e un'ora al fuso orario associato, è facile che una data e un'ora specifiche non risultino associate al rispettivo fuso orario. Un metodo per collegare queste informazioni consiste nel definire una classe o una struttura che contiene sia i valori di data e ora sia l'oggetto fuso orario associato.

Per sfruttare il supporto per i fusi orari in .NET è necessario conoscere il fuso orario cui appartiene un valore di data e ora quando viene creata un'istanza dell'oggetto data e ora. Il fuso orario spesso non è noto, in particolare nelle app Web o di rete.

Vedi anche