Convertir horas entre zonas horarias

Para cualquier aplicación que trabaje con fechas y horas se está volviendo cada vez más importante controlar las diferencias entre zonas horarias. Una aplicación ya no puede dar por hecho que todas las horas se puedan expresar en la hora local, que es la disponible en la estructura DateTime. Por ejemplo, una página web que muestre la hora actual en la zona oriental de Estados Unidos no tendrá credibilidad para un cliente de Asia Oriental. En este artículo se explica cómo convertir las horas de una zona horaria a otra y cómo convertir valores DateTimeOffset con reconocimiento de zona horaria limitado.

Conversión a la hora universal coordinada

La hora universal coordinada (UTC) es un estándar de hora atómica de alta precisión. Las zonas horarias del mundo se expresan como diferencias positivas o negativas con respecto a UTC. Por lo tanto, UTC proporciona una hora libre o neutra de zona horaria. Se recomienda el uso de la hora UTC cuando la portabilidad de la fecha y la hora entre equipos sea importante. Para obtener detalles y otros procedimientos recomendados con fechas y horas, vea Procedimientos recomendados de codificación mediante DateTime en .NET Framework. La conversión de zonas horarias individuales a UTC facilita las comparaciones de hora.

Nota

También puede serializar una estructura DateTimeOffset para representar de forma inequívoca un único punto en el tiempo. Dado que los objetos DateTimeOffset almacenan un valor de fecha y hora junto con su diferencia horaria con respecto a UTC, siempre representan un punto concreto en el tiempo en relación a UTC.

La manera más fácil de convertir una hora a UTC es llamar al método TimeZoneInfo.ConvertTimeToUtc(DateTime)static (Shared en Visual Basic). La conversión exacta que realiza el método depende del valor de la propiedad Kind del parámetro dateTime, tal como se muestra en la tabla siguiente:

DateTime.Kind Conversión
DateTimeKind.Local Convierte la hora local a UTC.
DateTimeKind.Unspecified Supone que el parámetro dateTime es la hora local y la convierte a UTC.
DateTimeKind.Utc Devuelve el parámetro dateTime sin modificar.

El código siguiente convierte la hora local actual a UTC y muestra el resultado en la consola:

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))

Si el valor de fecha y hora no representa la hora local ni UTC, el método ToUniversalTime probablemente devuelva un resultado erróneo. Pero puede usar el método TimeZoneInfo.ConvertTimeToUtc para convertir la fecha y hora de una zona horaria especificada. Para obtener detalles sobre cómo recuperar un objeto TimeZoneInfo que representa la zona horaria de destino, vea Búsqueda de las zonas horarias definidas en un sistema local. El código siguiente usa el método TimeZoneInfo.ConvertTimeToUtc para convertir la hora estándar del Este a hora 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

El método TimeZoneInfo.ConvertTimeToUtc genera una excepción ArgumentException si la propiedad Kind del objeto DateTime y la zona horaria no coinciden. Se produce un error de coincidencia si la propiedad Kind es DateTimeKind.Local pero el objeto TimeZoneInfo no representa la zona horaria local, o si la propiedad Kind es DateTimeKind.Utc pero el objeto TimeZoneInfo no es igual a TimeZoneInfo.Utc.

Todos estos métodos toman valores DateTime como parámetros y devuelven un valor DateTime. En el caso de los valores DateTimeOffset, la estructura DateTimeOffset tiene un método de instancia ToUniversalTime que convierte la fecha y hora de la instancia actual a UTC. En el ejemplo siguiente se llama al método ToUniversalTime para convertir una hora local y varias horas más a la hora 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   

Conversión de una hora UTC a una zona horaria designada

Para convertir la hora UTC a la hora local, consulte la sección Conversión de una hora UTC a la hora local que aparece a continuación. Para convertir la hora UTC a la hora de cualquier zona horaria que designe, llame al método ConvertTimeFromUtc. El método toma dos parámetros:

  • La hora UTC que se va a convertir. Debe ser un valor DateTime cuya propiedad Kind se establezca en Unspecified o Utc.

  • La zona horaria a la que se va a convertir la hora UTC.

El código siguiente convierte la hora UTC a la hora estándar central:

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

Conversión de una hora UTC a la hora local

Para convertir la hora UTC a la hora local, llame al método ToLocalTime del objeto DateTime cuya hora quiere convertir. El comportamiento exacto del método depende del valor de la propiedad Kind del objeto, tal como se muestra en la tabla siguiente:

DateTime.Kind Conversión
DateTimeKind.Local Devuelve el valor DateTime sin modificar.
DateTimeKind.Unspecified Da por hecho que el valor DateTime es la hora UTC y la convierte a la hora local.
DateTimeKind.Utc Convierte el valor DateTime a la hora local.

Nota

El método TimeZone.ToLocalTime se comporta de manera idéntica al método DateTime.ToLocalTime. Toma un único parámetro, que es el valor de fecha y hora que se va a convertir.

También puede convertir la hora de cualquier zona horaria designada a la hora local mediante el método TimeZoneInfo.ConvertTimestatic (Shared en Visual Basic). Esta técnica se trata en la sección siguiente.

Conversión entre dos zonas horarias cualquiera

Puede convertir entre dos zonas horarias mediante cualquiera de los dos métodos static (Shared en Visual Basic) siguientes de la clase TimeZoneInfo:

  • ConvertTime

    Los parámetros de este método son el valor de fecha y hora que se va a convertir, un objeto TimeZoneInfo que representa la zona horaria del valor de fecha y hora, y un objeto TimeZoneInfo que representa la zona horaria a la que se va a convertir el valor de fecha y hora.

  • ConvertTimeBySystemTimeZoneId

    Los parámetros de este método son el valor de fecha y hora que se va a convertir, el identificador de la zona horaria del valor de fecha y hora, y el identificador de la zona horaria a la que se va a convertir el valor de fecha y hora.

Ambos métodos exigen que la propiedad Kind del valor de fecha y hora que se va a convertir y el objeto TimeZoneInfo o el identificador de la zona horaria que representa a su zona horaria se correspondan entre sí. De lo contrario, se produce una excepción ArgumentException. Por ejemplo, si la propiedad Kind del valor de fecha y hora es DateTimeKind.Local, se inicia una excepción siempre que el objeto TimeZoneInfo pasado como un parámetro al método no sea igual a TimeZoneInfo.Local. También se inicia una excepción si el identificador pasado como un parámetro al método no es igual a TimeZoneInfo.Local.Id.

En el ejemplo siguiente se usa el método ConvertTime para convertir de la hora estándar de Hawái a la hora local:

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

Conversión de valores DateTimeOffset

Los valores de fecha y hora que representan objetos DateTimeOffset no reconocen totalmente la zona horaria porque el objeto está desasociado de su zona horaria en el momento en que se crea su instancia. Pero, en muchos casos, una aplicación simplemente necesita convertir una fecha y hora basada en dos diferencias horarias diferentes con respecto a la hora UTC en lugar de en la hora en zonas horarias determinadas. Para llevar a cabo esta conversión, puede llamar al método ToOffset de la instancia actual. El parámetro único del método es la diferencia horaria del nuevo valor de fecha y hora que el método devolverá.

Por ejemplo, si la fecha y hora de una solicitud de usuario de una página web se conoce y se serializa como una cadena con el formato MM/dd/aaaa hh: mm:ss zzzz, el siguiente método ReturnTimeOnServer convierte este valor de fecha y hora en la fecha y hora del servidor 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

Si el método pasa la cadena "9/1/2007 5:32:07 -05:00", que representa la fecha y hora de una zona horaria cinco horas anterior a la hora UTC, devuelve 9/1/2007 3:32:07 AM -07:00 en un servidor ubicado en la zona horaria estándar del Pacífico de Estados Unidos.

La clase TimeZoneInfo también incluye una sobrecarga del método TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) que realiza conversiones de zona horaria con valores ToOffset(TimeSpan). Los parámetros del método son un valor DateTimeOffset y una referencia a la zona horaria a la que se va a convertir la hora. La llamada de método devuelve un valor DateTimeOffset. Por ejemplo, el método ReturnTimeOnServer del ejemplo anterior se podría reescribir de la manera siguiente para llamar al método 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

Consulte también