Convertir hora UTC / GMT a hora local

301

Estamos desarrollando una aplicación C # para un cliente de servicio web. Esto se ejecutará en PC con Windows XP.

Uno de los campos devueltos por el servicio web es un campo DateTime. El servidor devuelve un campo en formato GMT, es decir, con una "Z" al final.

Sin embargo, encontramos que .NET parece hacer algún tipo de conversión implícita y el tiempo siempre fue de 12 horas.

El siguiente ejemplo de código resuelve esto hasta cierto punto porque la diferencia de 12 horas se ha ido pero no permite el horario de verano de Nueva Zelanda.

CultureInfo ci = new CultureInfo("en-NZ");
string date = "Web service date".ToString("R", ci);
DateTime convertedDate = DateTime.Parse(date);            

Según este sitio de fecha :

Compensación UTC / GMT

Zona horaria estándar: UTC / GMT +12 horas
Horario de verano: +1 hora
Compensación de zona horaria actual: UTC / GMT +13 horas

¿Cómo nos ajustamos para la hora extra? ¿Se puede hacer esto mediante programación o es algún tipo de configuración en las PC?

rbrayb
fuente
2
el Ztiempo se refiere a UTC, no a GMT. Los dos pueden diferir en hasta 0.9 segundos.
mc0e

Respuestas:

374

Para cadenas como 2012-09-19 01:27:30.000, DateTime.Parseno se puede decir de qué zona horaria son la fecha y la hora.

DateTimetiene una propiedad Kind , que puede tener una de las tres opciones de zona horaria:

  • Sin especificar
  • Local
  • UTC

NOTA Si desea representar una fecha / hora que no sea UTC o su zona horaria local, entonces debe usar DateTimeOffset.


Entonces, para el código en su pregunta:

DateTime convertedDate = DateTime.Parse(dateStr);

var kind = convertedDate.Kind; // will equal DateTimeKind.Unspecified

Dices que sabes de qué tipo es, así que dilo.

DateTime convertedDate = DateTime.SpecifyKind(
    DateTime.Parse(dateStr),
    DateTimeKind.Utc);

var kind = convertedDate.Kind; // will equal DateTimeKind.Utc

Ahora, una vez que el sistema sabe que es en hora UTC, puede llamar ToLocalTime:

DateTime dt = convertedDate.ToLocalTime();

Esto le dará el resultado que necesita.

Drew Noakes
fuente
19
solo otra forma de especificar el tipo:DateTime convertedTime = new DateTime(DateTime.Parse(dateStr).Ticks), DateTimeKind.Utc);
Brad
¿No es ToLocalTime ()? @Brad: tus padres no coinciden.
TrueWill
2
¿Esta solución representa el horario de verano? Cuando lo intento, estoy fuera de una hora.
Bob Horn
77
El paso de cambiar el Kindde DateTimede Unspecifieda UTCes innecesario. Unspecifiedse supone que es UTCpara los propósitos de ToLocalTime: msdn.microsoft.com/en-us/library/…
CJ7
16
@ CJ7: Sí, pero ser explícito es particularmente útil para otros desarrolladores que tengan que mantener el código.
Ryan
121

Examinaría el uso de la clase System.TimeZoneInfo si está en .NET 3.5. Ver http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx . Esto debe tener en cuenta los cambios de horario de verano correctamente.

// Coordinated Universal Time string from 
// DateTime.Now.ToUniversalTime().ToString("u");
string date = "2009-02-25 16:13:00Z"; 
// Local .NET timeZone.
DateTime localDateTime = DateTime.Parse(date); 
DateTime utcDateTime = localDateTime.ToUniversalTime();

// ID from: 
// "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Time Zone"
// See http://msdn.microsoft.com/en-us/library/system.timezoneinfo.id.aspx
string nzTimeZoneKey = "New Zealand Standard Time";
TimeZoneInfo nzTimeZone = TimeZoneInfo.FindSystemTimeZoneById(nzTimeZoneKey);
DateTime nzDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, nzTimeZone);
Daniel Ballinger
fuente
Si está trabajando en su propia zona horaria (en-NZ en este caso), entonces no necesita lidiar con TimeZoneInfo. Es solo una complejidad innecesaria. Vea mi respuesta para más detalles.
Drew Noakes
11
Y si las necesidades de cualquier persona, aquí está la lista de zonas horarias que he encontrado para TimeZoneInfo.FindSystemTimeZoneById - codeproject.com/Messages/3867850/...
nikib3ro
¡Brillante! Gracias por esta publicación Dan. He estado buscando esta solución durante 3 días.
Kevin Moore
58
TimeZone.CurrentTimeZone.ToLocalTime(date);
codificador1
fuente
8
Esto solo funciona si el sistema sabe que la fecha de conversión es en UTC. Por favor mira mi respuesta.
Drew Noakes
1
Pero UTC es el valor predeterminado, ¿no? Por lo tanto, funciona para "no especificado" como en la respuesta de CJ7.
NickG
25

DateTimelos objetos tienen el Kindde Unspecifiedpor defecto, que a los efectos de ToLocalTimese supone que esUTC .

Para obtener la hora local de un Unspecified DateTimeobjeto, solo necesita hacer esto:

convertedDate.ToLocalTime();

El paso de cambiar el Kind de DateTimede Unspecifieda UTCes innecesario. Unspecifiedse supone que es UTCpara los fines de ToLocalTime: http://msdn.microsoft.com/en-us/library/system.datetime.tolocaltime.aspx

CJ7
fuente
66
Y viceversa: convertedDate.FromLocalTime();se convertirá a UTC.
R. Schreurs
16

Sé que esta es una pregunta anterior, pero me encontré con una situación similar y quería compartir lo que había encontrado para futuros buscadores, posiblemente incluyéndome a mí mismo :).

DateTime.Parse()puede ser complicado - mira aquí por ejemplo.

Si DateTimeproviene de un servicio web o de alguna otra fuente con un formato conocido, es posible que desee considerar algo como

DateTime.ParseExact(dateString, 
                   "MM/dd/yyyy HH:mm:ss", 
                   CultureInfo.InvariantCulture, 
                   DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)

o mejor,

DateTime.TryParseExact(...)

La AssumeUniversalbandera le dice al analizador que la fecha / hora ya es UTC; la combinación de AssumeUniversaly AdjustToUniversalle dice que no convierta el resultado a la hora "local", lo que intentará hacer de forma predeterminada. (Personalmente, trato de tratar exclusivamente con UTC en la (s) capa (s) de negocio / aplicación / servicio de todos modos. Pero omitir la conversión a la hora local también acelera las cosas: un 50% o más en mis pruebas, ver más abajo).

Esto es lo que estábamos haciendo antes:

DateTime.Parse(dateString, new CultureInfo("en-US"))

Habíamos perfilado la aplicación y descubrimos que DateTime.Parse representaba un porcentaje significativo del uso de la CPU. (Por cierto, el CultureInfoconstructor no fue contribuyó significativamente al uso de la CPU).

Así que configuré una aplicación de consola para analizar una cadena de fecha / hora 10000 veces de varias maneras. Línea inferior:
Parse()10 segundos
ParseExact()(convirtiendo a local) 20-45 ms
ParseExact()(no convirtiendo a local) 10-15 ms
... y sí, los resultados Parse()están en segundos , mientras que los demás están en milisegundos .

David
fuente
14

Solo me gustaría agregar una nota general de precaución.

Si todo lo que está haciendo es obtener la hora actual del reloj interno de la computadora para poner una fecha / hora en la pantalla o un informe, entonces todo está bien. Pero si está guardando la información de fecha / hora para referencia posterior o está calculando fecha / hora, ¡tenga cuidado!

Supongamos que determina que un crucero llegó a Honolulu el 20 de diciembre de 2007 a las 15:00 UTC. Y quieres saber qué hora local era esa.
1. Probablemente hay al menos tres 'locales' involucrados. Local puede significar Honolulu, o puede significar dónde se encuentra su computadora, o puede significar la ubicación donde se encuentra su cliente.
2. Si utiliza las funciones integradas para realizar la conversión, probablemente sea incorrecto. Esto se debe a que el horario de verano está (probablemente) actualmente vigente en su computadora, pero NO estuvo vigente en diciembre. Pero Windows no sabe esto ... todo lo que tiene es una bandera para determinar si el horario de verano está actualmente vigente. Y si está actualmente vigente, felizmente agregará una hora incluso a una fecha en diciembre.
3)El horario de verano se implementa de manera diferente (o nada) en varias subdivisiones políticas. No piense que solo porque su país cambia en una fecha específica, también lo harán otros países.

Mark T
fuente
66
En realidad, el n. ° 2 no es del todo correcto. De hecho, hay reglas sobre el horario de verano en cada zona horaria que su computadora sabrá si la información se ha instalado (y actualizado). Para muchas zonas, esas reglas son fijas. Otros implementan "DST dinámico". Brasil es mi motivo favorito para esto. Sin embargo, en resumen, su computadora puede resolver si su hora local sería DST en diciembre, suponiendo que ningún cambio se convierta en ley entre ahora y entonces.
Roger Willcocks
Incluso si no vive en Brasil, el horario de verano es "dinámico", ya que los políticos pueden cambiarlo en cualquier momento (como se hizo hace unos años en los Estados Unidos). Como la mayoría del software está escrito con un uso futuro en mente, es importante darse cuenta de que NO hay una forma práctica, predecible o incluso teórica de saber qué reglas de DST estarán vigentes. Puede acercarse, pero ahórrese algo de frustración al renunciar a la perfección.
DaveWalley
6
@TimeZoneInfo.ConvertTimeFromUtc(timeUtc, TimeZoneInfo.Local)
Príncipe Prasad
fuente
5

No olvide que si ya tiene un objeto DateTime y no está seguro de si es UTC o Local, es bastante fácil usar los métodos directamente en el objeto:

DateTime convertedDate = DateTime.Parse(date);
DateTime localDate = convertedDate.ToLocalTime();

¿Cómo nos ajustamos para la hora extra?

A menos que se especifique .net usará la configuración local de la PC. Leería: http://msdn.microsoft.com/en-us/library/system.globalization.daylighttime.aspx

Por lo que parece, el código podría verse más o menos así:

DaylightTime daylight = TimeZone.CurrentTimeZone.GetDaylightChanges( year );

Y como se mencionó anteriormente, verifique en qué configuración de zona horaria está su servidor. Hay artículos en la red sobre cómo afectar de manera segura los cambios en IIS.

Brendan Kowitz
fuente
El sistema se ocupará de esta complejidad por usted, siempre que le informe al sistema qué 'tipo' es su fecha (local / utc / unspecified).
Drew Noakes
2

En respuesta a la sugerencia de Dana:

El código de muestra ahora se ve así:

string date = "Web service date"..ToString("R", ci);
DateTime convertedDate = DateTime.Parse(date);            
DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(convertedDate);

La fecha original era 20/08/08; el tipo era UTC

Tanto "convertDate" como "dt" son iguales:

21/08/08 10:00:26; el tipo era local

rbrayb
fuente
Por favor vea mi respuesta para una explicación de esto.
Drew Noakes
1

Tuve el problema de que estaba en un conjunto de datos que se empujaba a través del cable (servicio web al cliente) que cambiaría automáticamente porque el campo DateType de DataColumn estaba establecido en local. Asegúrese de verificar cuál es el DateType si está presionando DataSets.

Si no desea que cambie, configúrelo como No especificado

Millas
fuente
1

Me encontré con esta pregunta porque tenía un problema con las fechas UTC en las que regresa a través de la API de Twitter (campo created_at en un estado); Necesito convertirlos a DateTime. Ninguna de las respuestas / ejemplos de código en las respuestas en esta página fueron suficientes para evitar que recibiera un error "La cadena no se reconoció como una fecha y hora válida" (pero es lo más cercano que he encontrado para encontrar la respuesta correcta en SO)

Publicar este enlace aquí en caso de que esto ayude a alguien más: la respuesta que necesitaba se encontró en esta publicación de blog: http://www.wduffy.co.uk/blog/parsing-dates-when-aspnets-datetimeparse-doesnt-work/ - básicamente use DateTime.ParseExact con una cadena de formato en lugar de DateTime.Parse

DannykPowell
fuente