Trabajando con fechas y horas locales y fechas y horas UTC en .NET
Cuando trabajamos con fechas, nos vemos muchas veces empujados a trabajar con la fecha/hora del sistema local. Sin embargo, en muchas ocasiones, nos podemos encontrar con la necesidad de trabajar con la fecha/hora UTC partiendo de nuestra fecha/hora local.
Mucha gente, prefiere trabajar con fechas/horas UTC en lugar de las fechas/horas locales, y no les falta razón.
Imaginemos una aplicación Software distribuida internacionalmente en Ecuador y Madrid. La diferencia horaria es de 7 horas en horario verano. ¿Qué hacemos o qué podemos hacer con ciertas transacciones internas que a lo mejor se actualizan contra una base de datos compartida que está en Londres?.
Si imaginamos el escenario (en los blog de Geeks.ms dentro de las entradas se puede ver claramente esto que comento), un usuario que ha realizado una transacción a 7 horas de lejanía como por ejemplo Ecuador, puede tener un DateTime más temprano que el usuario que se encuentra 7 horas después en el orden horario en Madrid. ¿Y porqué debe ser primero Madrid que Ecuador en el ejemplo?, ¿sólo por que está más cerca del UTC que Ecuador?, ¿y porqué no utilizar como referencia el punto central UTC?.
Esto que a priori puede parecer trivial, se puede complicar mucho más.
¿Cómo resolver el problema?. Posiblemente tal y como hemos adelantado, utilizando las horas UTC (Coordinated Universal Time). Así, las zonas horarias quedan repartidas a lo largo de todo el mundo, y es fácil determinar que un evento se puede producir a una hora u otra a la derecha o izquierda del centro horario UTC, pero solo habrá una hora para todos, la hora UTC con independencia de la hora local en la que se produjo un determinado evento.
Quiero que sirva esta entrada, únicamente como reflexión de como podríamos trabajar con fechas/horas en nuestras aplicaciones Software y cómo .NET nos facilita esta tarea.
Para ello, empezaré por el final… es decir, por el resultado que quiero obtener y que es este:
En esta aplicación de Windows escrita en Visual Studio 2008 con C# como lenguaje de programación (y fácilmente traducible a VB.NET), en la que nos encontramos con 13 controles label y con 1 control listBox, dispuestos de forma ordenada dentro del formulario.
Los objetivos marcados son varios (partiendo de una fecha/hora local que en mi caso es la de mi ordenador de España):
- Mostrar la fecha/hora UTC, que en el caso de España es UTC+2 en verano (aún estamos con ese horario hasta el cambio de fecha/hora que se producirá en breve), y UTC+1 para el resto del año.
- Mostrar todas las zonas horarias en un control listBox. Cuando seleccionemos un elemento de la lista, se mostrará la fecha/hora del país o zona horaria a partir de la fecha/hora UTC calculada anteriormente.
- Finalmente y como agregado al proyecto de ejemplo, mostraremos la fecha/hora en formato cultural de Estados Unidos y de Alemania.
Ahora… manos a la obra que empieza lo divertido…
- Lo primero que hacemos es recuperar la fecha/hora local de España tal y como decía. Si estás en otra parte del mundo, tendrás la fecha/hora de donde te encuentres, siempre y cuando lo tengas así configurado en tu sistema.
// http://en.wikipedia.org/wiki/Coordinated_Universal_Time
// Extraemos la fecha/hora de acuerdo al sistema local (situado en España)
// (UTC+2 en España en verano)
// (UTC+1 en España el resto del año)
localDateTime = localDateTime.ToLocalTime();
this.lblLocalDateTime.Text = localDateTime.ToString();
Nada que destacar aquí, ya que se trata de recoger los datos de fecha y hora actual. Lo único a comentar es el método ToLocalTime(), que lo hemos utilizado para convertir el objeto DateTime a la fecha/hora local. Poco que destacar, pero lo pongo para que se sepa que existe ese método.
- Lo siguiente que haremos será convertir la fecha/hora local a su fecha UTC.
// Convertimos de fecha/hora local a su UTC correspondiente.
DateTime utcDateTime = localDateTime.ToUniversalTime();
this.lblUtcDateTime.Text = utcDateTime.ToString();
Aquí podemos ver que utilizamos el método ToUniversalTime(), que lo utilizamos para lograr nuestro propósito, convertir una fecha/hora local a su fecha/hora UTC.
- La siguiente acción a acometer es rellenar el control listBox con los identificadores de las zonas horarias, para que podamos seleccionar uno de ellos y podamos así obtener la fecha/hora de la zona horaria seleccionada:
ReadOnlyCollection<TimeZoneInfo> timeZoneInfoCollection = TimeZoneInfo.GetSystemTimeZones();
this.listBox1.ValueMember = «TimeZoneInfo.Id»;
this.listBox1.DisplayMember = «TimeZoneInfo.BaseUtcOffset»;
this.listBox1.DataSource = timeZoneInfoCollection.ToList();
- Evidentemente, deberemos hacer algo para que al seleccionar la zona horaria, seleccionemos la fecha/hora correspondiente a partir de la fecha/hora UTC (lógicamente).
- Recordemos que la fecha/hora UTC es la que se situaría en el centro teórico a partir del cual están repartidas las zonas horarias, por lo que para seleccionar una fecha/hora de una zona horaria, deberemos tomar como referencia la fecha/hora UTC.
{
// De acuerdo a la seleccide la zona horaria, cambiamos la
// fecha/hora con respecto a la fecha/hora UTC
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(this.listBox1.SelectedValue.ToString());
DateTime worldDateTime = TimeZoneInfo.ConvertTimeFromUtc(Convert.ToDateTime(this.lblUtcDateTime.Text), timeZoneInfo);
this.lblWorldDateTime.Text = worldDateTime.ToString();
this.lblWordPlace.Text = this.listBox1.SelectedValue.ToString();
} // listBox1_SelectedIndexChanged
- Para concluir, seleccionaremos la fecha/hora en formato USA y formato de Alemania, a partir de la fecha/hora local (España).
CultureInfo usaCulture = CultureInfo.CreateSpecificCulture(«en-US»);
DateTime usaFormatDateTime = Convert.ToDateTime(this.lblLocalDateTime.Text);
this.lblUsaFormatDateTime.Text = usaFormatDateTime.ToString(usaCulture.DateTimeFormat); // Mostramos el formato de fecha en formato Germany (dd.MM.yyyy…)
CultureInfo germanyCulture = CultureInfo.CreateSpecificCulture(«de-DE»);
this.lblGermanyFormatDateTime.Text = Convert.ToDateTime(this.lblLocalDateTime.Text).ToString(germanyCulture.DateTimeFormat);
-
Como podemos observar, trabajar con fechas/horas en .NET es realmente fácil, y trabajar con fechas/horas UTC como referencia es no solo fácil, sino también recomendado.
-
Espero que este sencillo ejemplo/demostración sirva para que reflexionemos a la hora de trabajar con fechas/horas en nuestras aplicaciones Software y cuales podrían ser las mejores recomendaciones de cara al trabajo con aplicaciones geográficamente separadas.
6 Responsesso far
preguntoncojonero… el código como proyecto VS 2008 no está disponible, pero es exactamente igual que el que he publicado en la entrada.
En el evento Load del formulario he escrito todo el código menos el del evento del control listBox.
Luego he hecho doble clic sobre el control listBox y he puesto el código del evento que se indica.
Espero que sirva.
Un saludo,
Jorge
En .NET 3.5 aparece DateTimeOffset para facilitar el manejo de DateTimes «universales».
Creo que parte del problema con usar DateTime es que vas a poder transformar a UTC y después a local… pero nunca vas a poder recibir un DateTime sin más y saber a qué Huso Horario está asociado… por lo que ya puede estar todo bien definido e implementado en todas las capas.
Buen apunte Pablo.
Debemos lógicamente definir o implementar adecuadamente y en todas las capas el como usarlo.
Espero haberte entendido bien, pero si transformamos una fecha local en UTC y esa es la fecha que pasamos a las capas, estaremos seguros de que la fecha que «viaja» es la UTC.
Entonces,
Para poder hacer buen uso de las fechas en aplicaciones distribuidas, ¿debo almacenar en mi base de datos además de la fecha, la zona horaria (UTC)?
o como haria para saber en que zona horaria (UTC)fue almacenada?
Edison, si siempre almacenas una fecha y hora UTC, siempre podrás convertirla a la fecha y hora local de tu región, por lo que en el caso de España por ejemplo, si ocurre algo a las 17:50:10 del 17/02/2010, sabremos que en mi región serán las 18:50:10 de ese mismo día.
El evento se habrá dado en España que es el ejemplo que estoy usando y se habrá grabado con fecha UTC, sin embargo, a la hora de saber cuando ocurrió exactamente, sabremos que ocurrió a las 17:50:10 UTC y las 18:50:10 hora de España.
No sé si así queda más claro o te estoy liando más…
Después de pelar con las fechas y aún que estaba ocupando TimeZoneInfo, me di cuenta de los errores que cometía, el primero fue pasar una fecha que hora local y la segunda no tomar la fecha universal.
Muchas gracias.