Crear una fecha y hora en una zona horaria específica en c #


162

Estoy tratando de crear una prueba unitaria para probar el caso cuando la zona horaria cambia en una máquina porque se ha configurado incorrectamente y luego se ha corregido.

En la prueba, necesito poder crear objetos DateTime en una zona horaria que no sea local para garantizar que las personas que ejecutan la prueba puedan hacerlo con éxito independientemente de dónde se encuentren.

Por lo que puedo ver desde el constructor DateTime, puedo configurar TimeZone para que sea la zona horaria local, la zona horaria UTC o no especificada.

¿Cómo creo un DateTime con una zona horaria específica como PST?


Respuestas:


216

La respuesta de Jon habla de TimeZone , pero sugeriría usar TimeZoneInfo en su lugar.

Personalmente, me gusta mantener las cosas en UTC siempre que sea posible (al menos para el pasado; almacenar UTC para el futuro tiene problemas potenciales ), por lo que sugeriría una estructura como esta:

public struct DateTimeWithZone
{
    private readonly DateTime utcDateTime;
    private readonly TimeZoneInfo timeZone;

    public DateTimeWithZone(DateTime dateTime, TimeZoneInfo timeZone)
    {
        var dateTimeUnspec = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
        utcDateTime = TimeZoneInfo.ConvertTimeToUtc(dateTimeUnspec, timeZone); 
        this.timeZone = timeZone;
    }

    public DateTime UniversalTime { get { return utcDateTime; } }

    public TimeZoneInfo TimeZone { get { return timeZone; } }

    public DateTime LocalTime
    { 
        get 
        { 
            return TimeZoneInfo.ConvertTime(utcDateTime, timeZone); 
        }
    }        
}

Es posible que desee cambiar los nombres de "TimeZone" a "TimeZoneInfo" para aclarar las cosas. Prefiero los nombres más breves.


55
No sé de ninguna construcción equivalente de SQL Server, me temo. Sugeriría tener el nombre de la zona horaria como una columna, y el valor UTC en otra columna. Recógelos por separado y luego puedes crear instancias con bastante facilidad.
Jon Skeet

2
No estoy seguro sobre el uso esperado del constructor que toma DateTime y TimeZoneInfo, pero dado que está llamando al método dateTime.ToUniversalTime (), sospecho que está adivinando que "tal vez" esté en hora local. En ese caso, creo que realmente deberías usar el TimeZoneInfo pasado para convertirlo a UTC ya que te dicen que se supone que debe estar en esa zona horaria.
IDisposable

2
@ChrisMoschini: Sin embargo, en ese momento solo estás inventando tu propio esquema de identificación, un esquema que nadie más en el mundo usa. Me quedaré con la información de zona estándar de la industria, gracias. (Es difícil ver cómo "Europa / Londres" no tiene sentido, por ejemplo).
Jon Skeet

2
@ChrisMoschini: Ejemplo diferente entonces: CST. ¿Es eso UTC-5 o UTC-6? ¿Qué hay de IST? ¿Es Israel, India o Irlanda en su base de datos? (E incluso si conoce la compensación en este momento, diferentes países que observan la misma abreviatura pueden cambiar en diferentes momentos. Por lo tanto, todavía hay ambigüedad sobre qué zona horaria real significa. ¡Zona horaria! = Compensación). Volviendo a su caso: usted reclama que el uso de abreviaturas resolvió mejor su problema. ¿Cómo hubiera sido peor el uso de ID de zona horaria estándar de la industria?
Jon Skeet

66
@ChrisMoschini: Bueno, continuaré recomendando el uso de identificadores de información de zona no ambiguos estándar de la industria en lugar de las abreviaturas ambiguas. No se trata de la biblioteca de quién se prefiere: la autoría de la biblioteca realmente no es un problema. Si alguien desea usar otra biblioteca con una buena elección de identificador, está bien. Sin embargo, la elección del identificador para una zona horaria es importante, y creo que es muy importante que los lectores sean conscientes de que las abreviaturas son ambiguas, como he demostrado con el ejemplo de IST.
Jon Skeet

54

La estructura DateTimeOffset se creó exactamente para este tipo de uso.

Ver: http://msdn.microsoft.com/en-us/library/system.datetimeoffset.aspx

Aquí hay un ejemplo de cómo crear un objeto DateTimeOffset con una zona horaria específica:

DateTimeOffset do1 = new DateTimeOffset(2008, 8, 22, 1, 0, 0, new TimeSpan(-5, 0, 0));


1
Gracias, esta es una buena manera de lograrlo. Después de obtener su objeto DateTimeOffset dentro de la zona horaria correcta, puede usar la propiedad .UtcDateTime para obtener una hora UTC para la que creó. Si almacena sus fechas en UTC, convertirlas a la hora local para cada usuario no es gran cosa :)
Redth

2
No creo que esto maneje el horario de verano correctamente, ya que algunas zonas horarias lo respetan mientras que otras no. También "en el día" comienza / termina el horario de verano, partes de ese día estarían apagadas.
crokusek

14
Lección. El horario de verano es una regla de una zona horaria particular. DateTimeOffset no está asociado a ninguna zona horaria. No confunda un valor de desplazamiento UTC, como -5, con una zona horaria. No es una zona horaria, es un desplazamiento. El mismo desplazamiento a menudo es compartido por muchas zonas horarias, por lo que es una forma ambigua de referirse a una zona horaria. Dado que DateTimeOffset está asociado con un desplazamiento, no con una zona horaria, no es posible que aplique reglas DST. Por lo tanto, las 3am serán las 3am todos los días del año, sin excepción en una estructura DateTimeOffset (por ejemplo, en sus propiedades Hours y TimeOfDay).
Triynko

Donde puede confundirse es si observa la propiedad LocalDateTime de DateTimeOffset. Esa propiedad NO es un DateTimeOffset, es una instancia de DateTime cuyo tipo es DateTimeKind.Local. Esa instancia está asociada con una zona horaria ... sea cual sea la zona horaria del sistema local. Esa propiedad reflejará el horario de verano.
Triynko

44
Entonces, el verdadero problema con DateTimeOffset es que no incluye suficiente información. Incluye un desplazamiento, no una zona horaria. El desplazamiento es ambiguo con múltiples zonas horarias.
Triynko

41

Las otras respuestas aquí son útiles, pero no cubren cómo acceder específicamente a Pacific; aquí tienes:

public static DateTime GmtToPacific(DateTime dateTime)
{
    return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
        TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"));
}

Por extraño que parezca, aunque "Hora estándar del Pacífico" normalmente significa algo diferente de "Hora de verano del Pacífico", en este caso se refiere a la hora del Pacífico en general. De hecho, si usa FindSystemTimeZoneByIdpara buscarlo, una de las propiedades disponibles es un bool que le dice si esa zona horaria está actualmente en horario de verano o no.

Puede ver ejemplos más generalizados de esto en una biblioteca que terminé juntando para tratar con DateTimes que necesito en diferentes TimeZones en función de dónde pregunta el usuario, etc.

https://github.com/b9chris/TimeZoneInfoLib.Net

Esto no funcionará fuera de Windows (por ejemplo, Mono en Linux) ya que la lista de veces proviene del Registro de Windows: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\

Debajo encontrará claves (iconos de carpeta en el Editor del Registro); los nombres de esas teclas son a lo que pasas FindSystemTimeZoneById. En Linux, debe usar un conjunto separado de definiciones de zonas horarias estándar de Linux, que no he explorado adecuadamente.


1
Además, hay ConvertTimeBySystemTimeZoneId () por ejemplo: TimeZoneInfo.ConvertTimeBySystemTimeZoneId (DateTime.UtcNow, "Hora estándar central")
Brent

En Windows, TimeZone Id List también puede ver esta respuesta: stackoverflow.com/a/24460750/4573839
yu yang Jian

7

Alteré Jon Skeet responder un poco para la web con el método de extensión. También funciona en azul como un encanto.

public static class DateTimeWithZone
{

private static readonly TimeZoneInfo timeZone;

static DateTimeWithZone()
{
//I added web.config <add key="CurrentTimeZoneId" value="Central Europe Standard Time" />
//You can add value directly into function.
    timeZone = TimeZoneInfo.FindSystemTimeZoneById(ConfigurationManager.AppSettings["CurrentTimeZoneId"]);
}


public static DateTime LocalTime(this DateTime t)
{
     return TimeZoneInfo.ConvertTime(t, timeZone);   
}
}

2

Tendrás que crear un objeto personalizado para eso. Su objeto personalizado contendrá dos valores:

  • un valor de fecha y hora
  • un objeto TimeZone

No estoy seguro de si ya hay un tipo de datos proporcionado por CLR que lo tenga, pero al menos el componente TimeZone ya está disponible.


2

Me gusta la respuesta de Jon Skeet, pero me gustaría agregar una cosa. No estoy seguro de si Jon esperaba que el ctor se pasara siempre en la zona horaria local. Pero quiero usarlo para casos en los que es algo diferente a lo local.

Estoy leyendo valores de una base de datos, y sé en qué zona horaria se encuentra esa base de datos. Entonces, en el ctor, pasaré la zona horaria de la base de datos. Pero luego me gustaría el valor en hora local. Jon's LocalTime no devuelve la fecha original convertida en una fecha de zona horaria local. Devuelve la fecha convertida en la zona horaria original (lo que haya pasado al ctor).

Creo que estos nombres de propiedad lo aclaran ...

public DateTime TimeInOriginalZone { get { return TimeZoneInfo.ConvertTime(utcDateTime, timeZone); } }
public DateTime TimeInLocalZone    { get { return TimeZoneInfo.ConvertTime(utcDateTime, TimeZoneInfo.Local); } }
public DateTime TimeInSpecificZone(TimeZoneInfo tz)
{
    return TimeZoneInfo.ConvertTime(utcDateTime, tz);
}

0

El uso de la clase TimeZones facilita la creación de la fecha específica de la zona horaria.

TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById(TimeZones.Paris.Id));

1
Lo sentimos, pero no está disponible en Asp .NET Core 2.2 aquí, VS2017 me sugiere instalar un paquete Nuget de Outlook.
Machado

ejemplo => TimeZoneInfo.ConvertTime (DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById ("Hora estándar del Pacífico"))
AZ_
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.