Conversione degli orari tra fusi orari

La gestione delle differenze tra fusi orari sta divenendo sempre più importante per le applicazioni che usano date e ore. Non è più possibile presupporre che tutti gli orari possano essere espressi su base locale, ovvero in base all'orario disponibile dalla struttura DateTime. Ad esempio, una pagina Web che visualizza l'orario corrente della parte orientale degli Stati Uniti risulterà non attendibile per un cliente dell'Asia orientale. Questo articolo illustra come convertire gli orari da un fuso orario a un altro e come convertire i valori di DateTimeOffset che hanno una dipendenza limitata dal fuso orario.

Conversione nel formato UTC

UTC (Coordinated Universal Time) è uno standard di alta precisione basato sul tempo atomico. I fusi orari del mondo sono espressi come differenze positive o negative dall'ora UTC. L'ora UTC offre, quindi, un orario indipendente dal fuso orario. L'uso dell'ora UTC è consigliato quando la portabilità di data e ora tra i computer è importante. Per informazioni dettagliate e altre procedure consigliate sull'utilizzo di date e ore, vedere Procedure consigliate per la codifica tramite DateTime in .NET Framework. La conversione di singoli fusi orari in ore UTC semplifica i confronti tra gli orari.

Nota

È anche possibile serializzare una struttura DateTimeOffset per rappresentare in modo non ambiguo un momento preciso. Poiché gli oggetti DateTimeOffset archiviano un valore di data e ora insieme alla differenza dall'ora UTC, rappresentano sempre un momento preciso nel tempo in relazione all'ora UTC.

Il modo più semplice per convertire un'ora in formato UTC consiste nel chiamare il metodo TimeZoneInfo.ConvertTimeToUtc(DateTime)static (Shared in Visual Basic). Il tipo specifico di conversione eseguita dal metodo dipende dal valore della proprietà Kind del parametro dateTime, come illustrato nella tabella seguente:

DateTime.Kind Conversione
DateTimeKind.Local Converte l'ora locale in ora UTC.
DateTimeKind.Unspecified Presuppone che il parametro dateTime sia l'ora locale e converte l'ora locale in ora UTC.
DateTimeKind.Utc Restituisce il parametro dateTime invariato.

Il codice seguente converte l'ora locale corrente in ora UTC e visualizza il risultato nella console:

DateTime dateNow = DateTime.Now;
Console.WriteLine($"The date and time are {TimeZoneInfo.ConvertTimeToUtc(dateNow)} UTC.");
Dim dateNow As Date = Date.Now
Console.WriteLine("The date and time are {0} UTC.", _
                  TimeZoneInfo.ConvertTimeToUtc(dateNow))

Se il valore di data e ora non rappresenta l'ora locale o l'ora UTC, il metodo ToUniversalTime restituirà probabilmente un risultato errato. È tuttavia possibile usare il metodo TimeZoneInfo.ConvertTimeToUtc per convertire la data e l'ora da un fuso orario specifico. Per informazioni dettagliate sul recupero di un oggetto TimeZoneInfo che rappresenta il fuso orario di destinazione, vedere Ricerca dei fusi orari definiti in un sistema locale. Il codice seguente usa il metodo TimeZoneInfo.ConvertTimeToUtc per convertire l'ora solare del fuso orientale in formato UTC:

DateTime easternTime = new DateTime(2007, 01, 02, 12, 16, 00);
string easternZoneId = "Eastern Standard Time";
try
{
    TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId);
    Console.WriteLine($"The date and time are {TimeZoneInfo.ConvertTimeToUtc(easternTime, easternZone)} UTC.");
}
catch (TimeZoneNotFoundException)
{
    Console.WriteLine($"Unable to find the {easternZoneId} zone in the registry.");
}
catch (InvalidTimeZoneException)
{
    Console.WriteLine($"Registry data on the {easternZoneId} zone has been corrupted.");
}
Dim easternTime As New Date(2007, 01, 02, 12, 16, 00)
Dim easternZoneId As String = "Eastern Standard Time"
Try
    Dim easternZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId)
    Console.WriteLine("The date and time are {0} UTC.", _
                      TimeZoneInfo.ConvertTimeToUtc(easternTime, easternZone))
Catch e As TimeZoneNotFoundException
    Console.WriteLine("Unable to find the {0} zone in the registry.", _
                      easternZoneId)
Catch e As InvalidTimeZoneException
    Console.WriteLine("Registry data on the {0} zone has been corrupted.", _
                      easternZoneId)
End Try

Il metodo TimeZoneInfo.ConvertTimeToUtc genera un'eccezione ArgumentException se la proprietà Kind dell'oggetto DateTime e il fuso orario non corrispondono. Una mancata corrispondenza si verifica se la proprietà Kind è DateTimeKind.Local ma l'oggetto TimeZoneInfo non rappresenta il fuso orario locale o se la proprietà Kind è DateTimeKind.Utc ma l'oggetto TimeZoneInfo non è uguale a TimeZoneInfo.Utc.

Tutti questi metodi accettano i valori DateTime come parametri e restituiscono un valore DateTime. Per i valori DateTimeOffset, la struttura DateTimeOffset ha un metodo di istanza ToUniversalTime che converte la data e l'ora dell'istanza corrente in formato UTC. Nell'esempio seguente viene chiamato il metodo ToUniversalTime per convertire un'ora locale e diversi altri volti orari in formato UTC:

DateTimeOffset localTime, otherTime, universalTime;

// Define local time in local time zone
localTime = new DateTimeOffset(new DateTime(2007, 6, 15, 12, 0, 0));
Console.WriteLine("Local time: {0}", localTime);
Console.WriteLine();

// Convert local time to offset 0 and assign to otherTime
otherTime = localTime.ToOffset(TimeSpan.Zero);
Console.WriteLine("Other time: {0}", otherTime);
Console.WriteLine("{0} = {1}: {2}",
                  localTime, otherTime,
                  localTime.Equals(otherTime));
Console.WriteLine("{0} exactly equals {1}: {2}",
                  localTime, otherTime,
                  localTime.EqualsExact(otherTime));
Console.WriteLine();

// Convert other time to UTC
universalTime = localTime.ToUniversalTime();
Console.WriteLine("Universal time: {0}", universalTime);
Console.WriteLine("{0} = {1}: {2}",
                  otherTime, universalTime,
                  universalTime.Equals(otherTime));
Console.WriteLine("{0} exactly equals {1}: {2}",
                  otherTime, universalTime,
                  universalTime.EqualsExact(otherTime));
Console.WriteLine();
// The example produces the following output to the console:
//    Local time: 6/15/2007 12:00:00 PM -07:00
//
//    Other time: 6/15/2007 7:00:00 PM +00:00
//    6/15/2007 12:00:00 PM -07:00 = 6/15/2007 7:00:00 PM +00:00: True
//    6/15/2007 12:00:00 PM -07:00 exactly equals 6/15/2007 7:00:00 PM +00:00: False
//
//    Universal time: 6/15/2007 7:00:00 PM +00:00
//    6/15/2007 7:00:00 PM +00:00 = 6/15/2007 7:00:00 PM +00:00: True
//    6/15/2007 7:00:00 PM +00:00 exactly equals 6/15/2007 7:00:00 PM +00:00: True
Dim localTime, otherTime, universalTime As DateTimeOffset

' Define local time in local time zone
localTime = New DateTimeOffset(#6/15/2007 12:00:00PM#)
Console.WriteLine("Local time: {0}", localTime)
Console.WriteLine()

' Convert local time to offset 0 and assign to otherTime
otherTime = localTime.ToOffset(TimeSpan.Zero)
Console.WriteLine("Other time: {0}", otherTime)
Console.WriteLine("{0} = {1}: {2}", _
                  localTime, otherTime, _
                  localTime.Equals(otherTime))
Console.WriteLine("{0} exactly equals {1}: {2}", _
                  localTime, otherTime, _
                  localTime.EqualsExact(otherTime))
Console.WriteLine()

' Convert other time to UTC
universalTime = localTime.ToUniversalTime()
Console.WriteLine("Universal time: {0}", universalTime)
Console.WriteLine("{0} = {1}: {2}", _
                  otherTime, universalTime, _
                  universalTime.Equals(otherTime))
Console.WriteLine("{0} exactly equals {1}: {2}", _
                  otherTime, universalTime, _
                  universalTime.EqualsExact(otherTime))
Console.WriteLine()
' The example produces the following output to the console:
'    Local time: 6/15/2007 12:00:00 PM -07:00
'    
'    Other time: 6/15/2007 7:00:00 PM +00:00
'    6/15/2007 12:00:00 PM -07:00 = 6/15/2007 7:00:00 PM +00:00: True
'    6/15/2007 12:00:00 PM -07:00 exactly equals 6/15/2007 7:00:00 PM +00:00: False
'    
'    Universal time: 6/15/2007 7:00:00 PM +00:00
'    6/15/2007 7:00:00 PM +00:00 = 6/15/2007 7:00:00 PM +00:00: True
'    6/15/2007 7:00:00 PM +00:00 exactly equals 6/15/2007 7:00:00 PM +00:00: True   

Conversione dell'ora UTC in un determinato fuso orario

Per convertire l'ora UTC nell'ora locale, vedere la sezione seguente Conversione dell'ora UTC nell'ora locale. Per convertire l'ora UTC nell'ora di un determinato fuso orario, chiamare il metodo ConvertTimeFromUtc. Il metodo accetta due parametri:

  • L'ora UTC da convertire. Questo deve essere un valore DateTime la cui proprietà Kind è impostata su Unspecified o Utc.

  • Il fuso orario nel quale convertire l'ora UTC.

Nel codice seguente l'ora UTC viene convertita nell'ora solare del fuso centrale:

DateTime timeUtc = DateTime.UtcNow;
try
{
    TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
    DateTime cstTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone);
    Console.WriteLine("The date and time are {0} {1}.",
                      cstTime,
                      cstZone.IsDaylightSavingTime(cstTime) ?
                              cstZone.DaylightName : cstZone.StandardName);
}
catch (TimeZoneNotFoundException)
{
    Console.WriteLine("The registry does not define the Central Standard Time zone.");
}
catch (InvalidTimeZoneException)
{
    Console.WriteLine("Registry data on the Central Standard Time zone has been corrupted.");
}
Dim timeUtc As Date = Date.UtcNow
Try
    Dim cstZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time")
    Dim cstTime As Date = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone)
    Console.WriteLine("The date and time are {0} {1}.", _
                      cstTime, _
                      IIf(cstZone.IsDaylightSavingTime(cstTime), _
                          cstZone.DaylightName, cstZone.StandardName))
Catch e As TimeZoneNotFoundException
    Console.WriteLine("The registry does not define the Central Standard Time zone.")
Catch e As InvalidTimeZoneException
    Console.WriteLine("Registry data on the Central Standard Time zone has been corrupted.")
End Try

Conversione dell'ora UTC nell'ora locale

Per convertire l'ora UTC nell'ora locale, chiamare il metodo ToLocalTime dell'oggetto DateTime per cui si desidera eseguire la conversione dell'ora. Il comportamento esatto del metodo dipende dal valore della proprietà Kind dell'oggetto, come illustrato nella tabella seguente:

DateTime.Kind Conversione
DateTimeKind.Local Restituisce il valore DateTime invariato.
DateTimeKind.Unspecified Presuppone che il valore DateTime sia in formato UTC e converte l'ora UTC nell'ora locale.
DateTimeKind.Utc Converte il valore DateTime nell'ora locale.

Nota

Questo metodo TimeZone.ToLocalTime funziona in modo identico al metodo DateTime.ToLocalTime. Prende un singolo parametro, ovvero il valore di data e ora, da convertire.

È anche possibile convertire l'ora di qualsiasi fuso orario desiderato in ora locale usando il metodo TimeZoneInfo.ConvertTimestatic (Shared in Visual Basic). Questa tecnica viene illustrata nella sezione successiva.

Conversione tra due fusi orari

È possibile eseguire la conversione tra due fusi orari qualsiasi usando uno dei due metodi static (Shared in Visual Basic) seguenti della classe TimeZoneInfo:

  • ConvertTime

    I parametri di questo metodo sono il valore di data e ora da convertire, un oggetto TimeZoneInfo che rappresenta il fuso orario del valore di data e ora e un oggetto TimeZoneInfo che rappresenta il fuso orario in cui convertire il valore di data e ora.

  • ConvertTimeBySystemTimeZoneId

    I parametri di questo metodo sono il valore di data e ora da convertire, l'identificatore del fuso orario del valore di data e ora e l'identificatore del fuso orario in cui convertire il valore di data e ora.

Entrambi i metodi richiedono che la proprietà Kind del valore di data e ora da convertire e l'oggetto TimeZoneInfo o l'identificatore del fuso orario che rappresenta il relativo fuso orario corrispondano tra loro. In caso contrario, verrà generata un'eccezione ArgumentException. Ad esempio, se la proprietà Kind del valore di data e ora è DateTimeKind.Local, viene generata un'eccezione se l'oggetto TimeZoneInfo passato come parametro al metodo non è uguale a TimeZoneInfo.Local. Un'eccezione viene generata anche se l'identificatore passato come parametro al metodo non è uguale a TimeZoneInfo.Local.Id.

Nell'esempio seguente viene usato il metodo ConvertTime per eseguire la conversione dall'ora solare Hawaii all'ora locale:

DateTime hwTime = new DateTime(2007, 02, 01, 08, 00, 00);
try
{
    TimeZoneInfo hwZone = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time");
    Console.WriteLine("{0} {1} is {2} local time.",
            hwTime,
            hwZone.IsDaylightSavingTime(hwTime) ? hwZone.DaylightName : hwZone.StandardName,
            TimeZoneInfo.ConvertTime(hwTime, hwZone, TimeZoneInfo.Local));
}
catch (TimeZoneNotFoundException)
{
    Console.WriteLine("The registry does not define the Hawaiian Standard Time zone.");
}
catch (InvalidTimeZoneException)
{
    Console.WriteLine("Registry data on the Hawaiian Standard Time zone has been corrupted.");
}
Dim hwTime As Date = #2/01/2007 8:00:00 AM#
Try
    Dim hwZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time")
    Console.WriteLine("{0} {1} is {2} local time.", _
                      hwTime, _
                      IIf(hwZone.IsDaylightSavingTime(hwTime), hwZone.DaylightName, hwZone.StandardName), _
                      TimeZoneInfo.ConvertTime(hwTime, hwZone, TimeZoneInfo.Local))
Catch e As TimeZoneNotFoundException
    Console.WriteLine("The registry does not define the Hawaiian Standard Time zone.")
Catch e As InvalidTimeZoneException
    Console.WriteLine("Registry data on the Hawaiian Standard Time zone has been corrupted.")
End Try

Conversione dei valori DateTimeOffset

I valori di data e ora rappresentati dagli oggetti DateTimeOffset non dipendono completamente dal fuso orario poiché è stata annullata l'associazione dell'oggetto al relativo fuso orario al momento della creazione dell'istanza di tale oggetto. In molti casi è tuttavia necessario che un'applicazione converta semplicemente una data e un'ora in base a due differenze dall'ora UTC diverse anziché in base all'ora di determinati fusi orari. Per eseguire questa conversione, è possibile chiamare il metodo ToOffset dell'istanza corrente. Il singolo parametro del metodo è la differenza del nuovo valore di data e ora restituita dal metodo.

Se, ad esempio, la data e l'ora della richiesta di una pagina Web da parte di un utente sono note e serializzate sotto forma di stringa nel formato MM/gg/aaaa hh:mm:ss zzzz, il metodo ReturnTimeOnServer seguente converte questo valore di data e ora nella data e nell'ora del server Web:

public DateTimeOffset ReturnTimeOnServer(string clientString)
{
   string format = @"M/d/yyyy H:m:s zzz";
   TimeSpan serverOffset = TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.Now);

   try
   {
      DateTimeOffset clientTime = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture);
      DateTimeOffset serverTime = clientTime.ToOffset(serverOffset);
      return serverTime;
   }
   catch (FormatException)
   {
      return DateTimeOffset.MinValue;
   }
}
Public Function ReturnTimeOnServer(clientString As String) As DateTimeOffset
    Dim format As String = "M/d/yyyy H:m:s zzz"
    Dim serverOffset As TimeSpan = TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.Now)

    Try
        Dim clientTime As DateTimeOffset = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture)
        Dim serverTime As DateTimeOffset = clientTime.ToOffset(serverOffset)
        Return serverTime
    Catch e As FormatException
        Return DateTimeOffset.MinValue
    End Try
End Function

Se al metodo viene passata la stringa "9/1/2007 5:32:07 -05:00" che rappresenta la data e l'ora in un fuso orario indietro di cinque ore rispetto all'ora UTC, viene restituito "9/1/2007 3:32:07 AM -07:00" per un server situato nel fuso orario Ora solare del Pacifico (Stati Uniti).

La classe TimeZoneInfo include anche un overload del metodo TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) che esegue conversioni di fuso orario con valori ToOffset(TimeSpan). I parametri del metodo sono un valore DateTimeOffset e un riferimento al fuso orario in cui convertire l'ora. La chiamata al metodo restituisce un valore DateTimeOffset. Ad esempio, il metodo ReturnTimeOnServer nell'esempio precedente può essere riscritto come segue per chiamare il metodo ConvertTime(DateTimeOffset, TimeZoneInfo).

public DateTimeOffset ReturnTimeOnServer(string clientString)
{
   string format = @"M/d/yyyy H:m:s zzz";

   try
   {
      DateTimeOffset clientTime = DateTimeOffset.ParseExact(clientString, format,
                                  CultureInfo.InvariantCulture);
      DateTimeOffset serverTime = TimeZoneInfo.ConvertTime(clientTime,
                                  TimeZoneInfo.Local);
      return serverTime;
   }
   catch (FormatException)
   {
      return DateTimeOffset.MinValue;
   }
}
Public Function ReturnTimeOnServer(clientString As String) As DateTimeOffset
    Dim format As String = "M/d/yyyy H:m:s zzz"

    Try
        Dim clientTime As DateTimeOffset = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture)
        Dim serverTime As DateTimeOffset = TimeZoneInfo.ConvertTime(clientTime, TimeZoneInfo.Local)
        Return serverTime
    Catch e As FormatException
        Return DateTimeOffset.MinValue
    End Try
End Function

Vedi anche