mariochavez

mariochavez

Publicado
Junio 9, 2015

Próximos Eventos

Blog

Desmitificando el manejo de fechas en Ruby on Rails

Hace algunas semanas me tocó dar el Curso Profesional de Ruby on Rails a través de la plataforma Platzi. Una de las preguntas que surgieron durante el curso es sobre el manejo de las fechas y horas de forma correcta respetando las zonas horarias.

Durante el taller mencioné la posibilidad de crear un initializer donde configuráramos la zona horaria por omisión para nuestra aplicación de Ruby on Rails:

Rails.application.config.time_zone = 'Central Time (US & Canada)'

A manera de errata quiero aclarar que esto es incorrecto, ya que al momento en que Ruby on Rails carga los inicializadores la zona horaria ya fue definida en el servidor de Rails, por que queda en en ’UTC’. La zona horaria es uno de los valores de configuración que deben de definirse en el archivo application.rb para que tenga efecto en la aplicación.

¿Por qué definir una zona horaria en el servidor de Ruby on Rails?

Ruby on Rails independientemente de la zona horaria en que esté configurado siempre guarda la fechas y horas en ’UTC’ y al momento de desplegar una fecha realizar la conversión a la zona horaria en la aplicación de Ruby on Rails esté configurado.

Sin ésta configuración todas las fechas se desplegarían en ’UTC’:

Sun, 07 Jun 2015 22:00:00 UTC +00:00

Si tenemos configurada nuestra aplicación para la zona horaria del Pacífico la misma fecha se muestra de la siguiente forma:

Sun, 07 Jun 2015 15:00:00 PDT -07:00

Por ejemplo, es importante observar que Ruby on Rails entiende del horario del verano y automáticamente hace el ajuste cuando los cambios suceden, lo podemos notar porque la zona horaria se muestra como ’PDT’, donde la D indica Daylight saving.

Hay helpers como #datetime_select que también se benefician de tener configurada una zona horaria, que muestra la fecha y la hora de manera correcta.

Los valores que #time_zone puede aceptar los podemos obtener de ejecutar el siguiente comando de rake en la consola:

 $ rake time:zones:all

## Ruby on Rails parece ignorar mi zona horaria Cuando nuestra aplicación de Ruby on Rails está configurada con la misma zona horaria del servidor donde estamos afectando nuestra aplicación, el manejo de fechas y horas en Ruby on Rails es correcto.

Esto se debe a que en muchos de los casos hacemos uso de Time.now o DateTime.now pero dada la coincidencia de que la aplicación y el servidor están configurados en la misma zona horario todo se ve correcto. Pero si modificamos nuestra aplicación a una zona horaria diferente Time.now y DateTime.now ya no muestran la hora correcta.

Esto se debe a que Time y DateTime no entienden la configuración de la aplicación de Ruby on Rails y de hecho van y preguntas directamente al sistema operativo por fecha y hora.

Para trabajar de forma efectiva en Ruby on Rails con diferentes zonas horarias necesitamos de utilizar el soporte de ActiveSupport para el manejo de zonas horarias.

En el siguiente ejemplo el servidor está ejecutándose en la zona horaria ’CDT’ y la aplicación de Ruby on Rails en ’PDT’:

> DateTime.now
=> Tue, 09 Jun 2015 12:30:31 -0500
> Time.now
=> 2015-06-09 12:30:33 -0500

La forma efectiva de manejar fecha con Ruby on Rails es la siguiente:

> Time.zone.now
=> Tue, 09 Jun 2015 10:32:27 PDT -07:00
> DateTime.now.in_time_zone
=> Tue, 09 Jun 2015 10:32:43 PDT -07:00
> DateTime.current
=> Tue, 09 Jun 2015 10:32:47 -0700

Podemos observar que en el segundo ejemplo en todos los casos la fecha y hora se mostró de forma correcta para la zona horaria de la aplicación de Ruby on Rails.

Time cuenta con el método #zone con el cuál nos aseguramos de trabajar con las fechas de forma correcta. Por ejemplo si queremos parsear una fecha desde una cadena de texto lo hacemos de la siguiente forma:

> Time.zone.parse(str)

DateTime no cuenta con el método #zone pero sí con #in_time_zone que nos permite asegurarnos que las fechas y horas son manejadas adecuadamente.

¿Cómo puedo hacer para que Ruby on Rails muestra fechas y horas en la zona horaria de mis usuarios?

Es posible hacer que Ruby on Rails muestre la fecha y hora de manera diferente para cada usuario, por ejemplo en un país como México tenemos 5 diferentes zonas horarias. Si quisiéramos mostrar las fechas y horas de forma local para usuarios que se conectan desde estos diferentes lugares es necesario hacer un pequeño esfuerzo en Ruby on Rails.

Lo primero que tenemos que entender es que los navegadores no envían al servidor la zona horaria del usuario que está haciendo una solicitud, por lo que la única forma de mostrar la hora local a los usuarios es preguntándoles en que zona horaria están o bien mediante Geolocalización, por ejemplo tomar la IP desde donde se están conectado y resolverla a su ubicación. Esto es posible con una gema como geoip.

En la primera opción de preguntarle al usuario su zona horaria, podemos guardar esa información como parte de su perfil y así hacer uso de esa información para las siguientes ocasiones en que el usuario se conecte.

Pero bueno, ya tenemos la zona horaria del usuario y la cuál puede ser diferente a la zona horaria de la aplicación. Si tomamos los ejemplo previos, el hacer uso de #zone y #in_time_zone no nos ayuda mucho ya que siempre se mostrará la fecha y la hora resuelta para la configuración de la aplicación.

Lo debemos de hacer es pasar un parámetro a #in_time_zone:

> DateTime.now.in_time_zone('Eastern Time (US & Canada)')
=> Tue, 09 Jun 2015 13:47:50 EDT -04:00

De ésta forma Ruby on Rails desplegará la fecha y hora en la zona horaria del usuario. Otra forma de hacerlo es pasándole la zona horaria al método #zone= de la clase Time:

> Time.zone = "Eastern Time (US & Canada)"
=> "Eastern Time (US & Canada)"
> DateTime.now.in_time_zone
=> Tue, 09 Jun 2015 13:51:55 EDT -04:00

A nivel de un controlador de Ruby on Rails, podemos hacer uso de un #around_filter para configurar la zona horaria en cada solicitud de los usuarios:

class EventsController < ApplicactionController
  around_filter :set_time_zone

  private def set_time_zone
     if logged_in?
        Time.use_zone(current_user.time_zone) { yield }
     else
        yield
     end
  end 
end

De ésta forma cada que el usuario haga una petición al servidor la zona horaria se leerá del perfil del usuario o de un cookie, suponiendo que el perfil del usuario implementa el método #time_zone y en nuestras vistas sólo tenemos que preocuparnos por llamar al método #in_time_zone cada que despleguemos una fecha en nuestra aplicación.

Conclusiones

El manejo de zonas horarias de forma correcta en Ruby on Rails no es nada complicado, el error en que muchos caemos es el de utilizar Time y DateTime sin el soporte de ActiveSupport, el cuál entiende y nos ayuda a manejar las fechas de forma correcta en la aplicación.

Aprendiendo Ruby on Rails 4.

Libro Aprendiendo Ruby on Rails disponible en modo Early Access.