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.
// UTC - Universal Time, Coordinated
// 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)
DateTime localDateTime = DateTime.Now;
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:
// Obtenemos la lista de ids de TimeZoneInfo
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.
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
// 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).
// Mostamos el formato de fecha en formato USA (MM/dd/yyyy...)
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.
Referencias:
Acceso a la wikipedia para saber más sobre UTC.