Blog what I write

Calcular en MySQL la diferencia entre dos fechas dadas

post - - 7 replies -

El “problema”

Supongamos que tenemos una base de datos en la que los usuarios se encuentran almacenados en la tabla user. Y las cuentas de usuario deben expirar trascurrido un determinado tiempo (por ejemplo 30 días). Una situación bastante común para las suscripciones.

El tema de este artículo es: ¿Como podríamos calcular el tiempo que le queda a cada cuenta de usuario hasta que expire?.

Formato de fechas

Lo primero que debemos tener en cuenta, es la necesidad de trabajar con un formato adecuado y que nos posibilite el realizar fácilmente operaciones sobre las fechas.
El formato idóneo para esto es el llamado “unix timestamp”, una medida universal de tiempo usada en los sistemas operativos unix, y que indica en un número entero la cantidad de segundos transcurridos desde el Epoch Unix (Enero 1 1970 00:00:00 GMT).
Por tanto, con este dato podemos obtener la fecha y hora actual y realizar operaciones en el tiempo pasado o futuro.

Resolución

Podemos resolver este problema con ayuda de las funciones que nos provee cada lenguaje de programación (de nivel alto) y de esta forma no reinventar la rueda, al mismo tiempo que nos ahorramos muchos quebraderos de cabeza.

En el caso del lenguaje PHP, la función time(), date(), mktime(), pueden ayudarnos bastante con este tipo de problemas y operaciones. Pero pienso que es mejor delegar los problemas que derivan de la base de datos (subscripciones) en la capa de base de datos, mejorando así la abstracción y el nivel de capas de nuestro programa.

Supongamos que tenemos la siguiente consulta en MySQL, para obtener el identificador, nombre y fecha de registro de un usuario determinado (3).

select id, name, date from user where id = 3

Ahora necesitamos que en esa consulta también venga devuelto (por cada tupla) el número de días que falta para la expiración de cada cuenta de usuario.
Esto lo podremos hacer de la siguiente manera:

select
	id, name, date,
	floor(((date + (86400 * 30)) - unix_timestamp()) / 86400) as expire_days
from user
where id = 3

La única diferencia ha sido la incorporación de un nuevo campo (calculado) en select.

floor(((date + (86400 * 30)) - unix_timestamp()) / 86400) as expire_days

Vamos a desglosar esta operación:

  1. El campo [date] de la tabla user, devuelve un valor timestamp, por ejemplo 1190046588. Que expresa en una medida de tiempo universal (basada en segundos) el momento exacto del registro de usuario (dia/mes/año, hora:minuto:segundo).
  2. Al numero devuelto por el campo [date], le sumamos (86400 * 30), donde 86400 es el número de segundos que tiene un día entero. De esta forma le sumamos el número de segundos que tienen 30 días. Esta suma hará que avancemos al futuro situándonos 30 días en adelante respecto al registro del usuario.
  3. A la cantidad obtenida en el punto 1 y 2 le debemos restar el momento actual en el que nos encontramos, para esto usamos la función de MySQL unix_timestamp(). A partir de aquí ya tenemos la diferencia entre el futuro y el presente, expresada en un número entero de formato timestamp.
  4. Con el dato del punto 3 ya podemos dividirlo entre 86400 (segundos que tiene un día) y con ello obtener el número de días que faltan para la expiración.
  5. Como la división va a devolver un numero decimal, englobamos toda la operación dentro de la función floor() que se encarga de devolver el entero mas grande pero con la parte fraccionada eliminada.

Llegados aquí comento que existen una función en MySQL que resuelve todos estos pasos: datediff(). No obstante explico todo el proceso porque puede que nuestra versión de MySQL no soporte aún esta función y porque además tiene limitaciones.

Por ejemplo, supongamos que la expiración se produce hoy mismo, el valor devuelto por el campo [expire_days] va a ser 0, aunque en realidad aún no tiene porque expirar, puesto que le restan unas horas para hacerlo. ¿Cómo calcularlas?.

floor(((date + (86400 * 30)) - unix_timestamp()) / 3600) as expire_hours

Y si [expire_hours] devuelve 0, ¿como calcular los minutes que faltan?

floor(((date + (86400 * 30)) - unix_timestamp()) / 60) as expire_minutes

Al final todo se reduce a usar los segundos como unidad de medida:

  • 60 seg = 1 min
  • 3600 seg (60seg * 60 min) = 1 hora
  • 86400 (3600 seg * 24 horas) = 1 día

Existen otras dos funciones interesantes en MySQL: date_add() y date_sub(), y en este tutorial una explicación del trabajo con fechas en MySQL.

Consejo: aunque este muy bien obtener estos datos directamente de la consulta, sin tener que programar PHP para tal efecto, no debemos abusar de los campos calculados si la consulta es excesivamente grande, puesto que la hará más lenta.

  • Alvaro

    Muchisimas gracias, me sirvio de mucha ayuda el articulo, necesitaba restar dos fechas y tener como resultado los dias, basandome en tu sugerencia use lo siguiente: select floor((unix_timestamp() – unix_timestamp(‘2009-02-10’)) / 86400); y me funciono. Muchas gracias

  • Hola
    una forma mas fácil de hacerlo es con la función TIMESTAMPDIFF

    http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_timestampdiff

    a mi ame a servido 🙂

    • daniepusb

      esa ultima funcion… “TIMESTAMPDIFF” solo sirve con campos de tipo date? porque me interesa usar timestamp

  • Gracias muy buen articulo yen especial gracias a Onizukar TIMESTAMPDIFF es mucho mas facil.

  • Mi aporte, me interesaba en un momento determinado, obtener el tiempo trasncurrido entre dos fechas en un formato de Hora:Minutos:Segundos y lo logres obtener con la siguente instruccion.

    Select SEC_TO_TIME(TIMESTAMPDIFF(second,’2011-12-01 00:00:05′,’2011-12-02 12:05:55′)) as tiempo_trasncurrido_H_M_S;

    es de mencionar que en mi base lo probe con una mescla de campos dateTime y TimeStamp y corrio a lo vestia.

    saludos a todos, muy buenos aportes, de hecho mi aporte se basa en el contenido del link de -Onizukar-

  • Marcos

    Mil gracias por tu desgranamiento. Cuando el stress, la frustración y el cansancio aturdían mi mente tu acompañamiento ha resuelto mis problemas.

    GRACIAS 🙂