php dates

Trabajando con fechas en PHP y WordPress

Una de las cosas que más quebraderos de cabeza puede dar cuando estás desarrollando es trabajar con fechas. Esto se debe en gran medida a la cantidad de operaciones que puedes hacer con ellas y la cantidad diferente de formatos que existen.

A lo largo de los años he tenido que realizar muchos desarrollos en los que trabajaba con fechas, y he cometido muchos errores.

Este post me llevaba rondando mucho tiempo por la cabeza, y espero con él aclarar muchas dudas a todos aquellos y aquellas que puedan encontrarse con problemas a la hora de trabajar con fechas.

Formatos de fecha

Lo primero creo, es tener en cuenta los diferentes formatos de fecha que te puedes encontrar. A partir de aquí, voy a poner como ejemplo una fecha cualquiera 7 de Junio de 2022. Esta fecha la puedes encontrar definida de muchas maneras:

  • 07/06/2022 (Formato Europeo => día/mes/año)
  • 07/06/22 (Formato Europeo corto => día/mes/año)
  • 06-07-2022 (Formato Americano => mes/día/año
  • 2022-06-07 (ISO 8601 => año/mes/día)
  • 1654560000 (timestamp UNIX => número de segundos desde 00:00:00 UTC del 1 de enero de 1970)

Esto es sólo una muestra, si tienes curiosidad por saber cómo podrías encontrarte una fecha según cada país, te invito a visitar este artículo de la Wikipedia. Y con sólo una muestra, imagino que te vas haciendo a la idea del cacao que puedes tener a la hora de realizar operaciones.

Y más allá del cacao, el problema puede venir al interpretar la fecha como 6 de Julio en lugar de 7 de Junio.

Existe un formato internacional definido por la ISO 8601, que propone como estándar el formato AAAA-MM-DD, donde AAAA es el año completo con los 4 dígitos, MM es el número de mes representado con dos dígitos y DD es el número de día representado con dos dígitos. Para nuestro ejemplo, sería 2022-06-07.

Nomenclatura de fechas en PHP

Antes de entrar en las funciones para trabajar con fechas, conviene aclarar la nomenclatura de los parámetros que podemos utilizar en ellas. Aunque hay más opciones, las más comunes son las siguientes:

Formato Descripción Ejemplo
d Día del mes, 2 dígitos con ceros iniciales Desde 01 a 31
j Día del mes sin ceros iniciales Desde 1 a 31
t Número de días del mes dado 28, 29, 30 ó 31
D Representación textual del día, únicamente 3 caracteres Desde Mon hasta Sun
l Representación textual completa del día Desde Monday hasta Sunday
w Representación numérica del día de la semana Desde 0 para el lunes hasta 6 para domingo
N Representación numérica (ISO) del día de la semana Desde 1 para el lunes hasta 7 para domingo
z Representación numérica del día del año (comenzando por cero) Desde 0 a 365
W Representación numérica del número de semana del año Desde 1 hasta 52
m Representación numérica de un mes, con ceros iniciales Desde 01 hasta 12
n Representación numérica de un mes, sin ceros iniciales Desde 1 hasta 12
M Representación textual del mes, únicamente 3 caracteres Desde Jan hasta Dec
F Representación textual completa del mes Desde January hasta December
L Si es año bisiesto 1 si es bisiesto, 0 si no
y Representación numérica de dos dígitos de un año Ejemplo: 22
Y Representación numérica completa de un año, 4 dígitos Ejemplo: 2022
h Formato de 12 horas de una hora con ceros iniciales Desde 01 hasta 12
H Formato de 24 horas de una hora con ceros iniciales Desde 01 hasta 23
i Minutos con ceros iniciales Desde 00 hasta 59
s Segundos con ceros iniciales Desde 00 hasta 59

En este punto, simplemente apuntar que si queremos obtener el nombre del día de la semana o del mes, la respuesta por defecto será en inglés, más adelante veremos cómo obtenerlas en castellano u otro idioma.

Snippets para trabajar con fechas en PHP

Antes de entrar en harina, hasta aquí habrás podido notar la dificultad que puede conllevar trabajar con fechas, sobre todo si en BBDD guardas con un formato, en la web lo muestras con otro, etc…

Como ejemplo pondré una situación con la que me encontré hace años. En un sistema que almacenaba citas, las fechas de estas se guardaban en base de datos en formato UNIX, es decir, el número de segundos desde el 1 de Enero de 1970. El problema venía con los cambios de horario de verano/invierno, ya que esa hora de más o menos influía a la hora de «pintar» las citas en el calendario.

Funciones para trabajar con fechas hay muchas, puedes ver un listado aquí. Como verás, casi todos esas funciones son alias de DateTime. Por lo tanto, mi primera recomendación es trabajar con objetos DateTime.

A continuación vamos a ver varios ejemplos, en todos ellos vamos a tratar de llegar a la fecha con formato ISO para poder trabajar con un objeto DateTime:

Mostrar fecha en formato Europeo partiendo de una fecha UNIX

Recibimos una fecha en formato timestamp UNIX, y queremos mostrarla en formato Europeo (d/m/Y). Lo primero es transformar la fecha UNIX a fecha ISO. Después creamos el objeto y lo imprimimos en el formato que queremos.

Una vez tenemos el objeto podemos extraer cualquier información de la fecha y realizar operaciones (que veremos más adelante).

<?php

$origin_date = '1654560000';
$iso_date = date('Y-m-d', $origin_date);
$date = new DateTime($iso_date);

echo $date->format('d/m/Y'); // 07/06/2022

// Obtener el día del mes
echo $date->format('j'); // 7

// Obtener el día del año
echo $date->format('z'); // 157

// Obtener el nombre del mes
echo $date->format('F'); // June

Sumar o restar horas, días, semanas, meses o años a una fecha dada

Estas operaciones son muy comunes, afortunadamente el objeto DateTime posee un método add y otro sub que nos permitirá realizarlas fácilmente. Básicamente, a nuestro objeto de fecha le añadiremos un intervalo, que será un objeto DateInterval.

El periodo que queramos añadir o quitar de una fecha, será un string que comienza con la letra «P», seguido del número de años, meses, días y/o semanas que queramos sumar o restar. Si la duración contiene elementos de hora, esa parte de la especificación estará precedida por una letra «T». La tabla de cadenas disponibles es la siguiente:

Indicador de periodo Descripción
Y años
M meses
D días
W semanas
H horas
M minutos
S segundos

De este modo:

  • P1Y1W => 1 año y 1 semana
  • P1M10D => 1 mes y 10 días
  • P6M15D => 6 meses y 15 días
  • P1DT6H30M => 1 día, 6 horas y 30 minutos

Entendido esto, volvamos a nuestra fecha de ejemplo 07 de Junio de 2022. Vamos a sumar y restar los 4 ejemplos de antes:

<?php

$origin_date = '2022-06-07';
$date = new DateTime($origin_date);

echo $date->add(new DateInterval('P1Y1W'))->format('d/m/Y'); // 14/06/2023

echo $date->sub(new DateInterval('P1Y1W'))->format('d/m/Y'); // 31/05/2021

echo $date->add(new DateInterval('P1M10D'))->format('d/m/Y'); // 17/07/2022

echo $date->sub(new DateInterval('P1M10D'))->format('d/m/Y'); // 27/04/2022

echo $date->add(new DateInterval('P6M15D'))->format('d/m/Y'); // 22/12/2022

echo $date->sub(new DateInterval('P6M15D'))->format('d/m/Y'); // 22/11/2021

echo $date->add(new DateInterval('P1DT6H30M'))->format('d/m/Y H:i:s'); // 08/06/2022 06:30:00

echo $date->sub(new DateInterval('P1DT6H30M'))->format('d/m/Y H:i:s'); // 05/06/2022 17:30:00

¿Y qué pasa cuando sumas 1 mes a una fecha por ejemplo: 31/01/2022? Pues PHP hace varias cosas. Sumar un mes al 31 de Enero daría como resultado 31/02/2022. Evidentemente esta fecha no existe, ni tampoco el 30, ni el 29 ya que 2022 no es bisiesto. Por lo tanto tenemos un «gap» de 3 días, que los suma al último día de Febrero, dando como resultado 03/03/2022.

Probablemente no sea el resultado que esperas, para ello tendrías que desarrollar una función tipo:

<?php

$origin_date = '2022-01-31';

function get_next_month($origin_date)
{
    $date = new DateTime($origin_date);
    $day = $date->format('j');
    $date->modify('first day of +1 month');
    $date->modify('+' . (min($day, $date->format('t')) - 1) . ' days');

    return $date->format('d/m/Y');
}

echo get_next_month($origin_date); // 28/02/2022

¿Qué edad tienes?

Otra operación que se suele realizar muy a menudo es calcular tu edad. Para esto necesitamos dos fecha, la fecha de tu nacimiento y la fecha actual, y a partir de ahí podemos calcular tus años, tus meses, tus días, etc…

<?php

$origin_date = '1980-07-19';
$birthday = new DateTime($origin_date);
$today = new DateTime(date('Y-m-d'));

$interval = $birthday->diff($today);
echo $interval->format('Tengo %Y años, %M meses y %D días'); // Tengo 41 años, 05 meses y 10 días 

echo $interval->days; // 15138 (días que tengo)

Mostrar hace cuanto se ha publicado o ha sucedido algo

Habrás visto en muchas aplicaciones que cuando se publica una noticia, evento, artículo… aparece un texto tipo «publicado hace 2 min» o «publicado hace 1 año». Lo «interesante» de esta funcionalidad es:

  • Si el evento ocurrió hace unos minutos, mostraremos el número de minutos.
  • Si los minutos superan los 60, deberíamos mostrar las horas.
  • Si las horas superan las 24, deberíamos mostrar los días.
  • Si los días superan los 30, deberíamos mostrar los meses.
  • Y si los meses superan los 12, deberíamos mostrar los años.

Y para ir a por nota, tengamos en cuenta el singular y el plural, es decir, no es lo mismo 1 «hora» que 2 «horas». (En este ejemplo se utiliza una función de WordPress)

<?php


$publish_date = '2021-12-29 10:00:00';
$now_date = date('Y-m-d H:i:s');
$publish = new DateTime($publish_date);
$now = new DateTime($now_date);

echo time_ago($publish, $now);

function time_ago(DateTime $publish, DateTime $now): string
{
    $diff = $now->diff($publish);

    if ($diff->y > 0) {
        return sprintf('Publicado hace ' . _n('%s año', '%s años', $diff->y, 'text-domain'), $diff->y);
    }

    if ($diff->m > 0) {
        return sprintf('Publicado hace ' . _n('%s mes', '%s meses', $diff->m, 'text-domain'), $diff->m);
    }

    if ($diff->d > 0) {
        return sprintf('Publicado hace ' . _n('%s día', '%s días', $diff->d, 'text-domain'), $diff->d);
    }

    if ($diff->h > 0) {
        return sprintf('Publicado hace ' . _n('%s hora', '%s horas', $diff->h, 'text-domain'), $diff->h);
    }

    if ($diff->i > 0) {
        return sprintf('Publicado hace ' . _n('%s minuto', '%s minutos', $diff->i, 'text-domain'), $diff->i);
    }

    if ($diff->s > 0) {
        return sprintf('Publicado hace ' . _n('%s segundo', '%s segundos', $diff->s, 'text-domain'), $diff->s);
    }
}

Cuenta atrás

Otro caso muy típico de operaciones con fechas, el crear una cuenta atrás o ver el tiempo que queda hasta una determinada fecha.

<?php

$future_date = '2021-06-07 10:00:00';
$date = new DateTime($future_date);

echo time_remaining($date);

function time_remaining(DateTime $date): string
{
    $remain = $date->diff(new DateTime());

    return "Quedan {$remain->y} años, {$remain->m} meses, {$remain->d} días, {$remain->h} horas, {$remain->i} minutos y {$remain->s} segundos";
}

¿Y en WordPress qué?

WordPress tiene sus propias funciones para obtener las fechas de publicación de los posts. Pero el objetivo de este artículo es cómo trabajar y hacer operaciones con ellas. Una de ellas es cómo mostrar la fecha en tu propio idioma, es decir, en lugar de «Tuesday 7 de June de 2022» que se imprima en pantalla «Martes 7 de Junio de 2022«

Esto lo podemos realizar con PHP plano (con setlocale y strftime), o utilizando la función date_i18n de WordPress:

<?php

$origin_date = '2022-06-07 10:00:00';
$date = new DateTime($origin_date);

echo $date->format('l j \d\e F \d\e Y'); // Tuesday 7 de June de 2022

setlocale(LC_ALL, 'es_ES', 'Spanish_Spain', 'Spanish');
echo strftime('%A %e de %B de %Y', $date->getTimestamp()); // martes 7 de junio de 2022

echo date_i18n('l j \d\e F \d\e Y', $date->getTimestamp()); // martes 7 de junio de 2022

Hasta aquí este artículo. Te animo a que dejes en los comentarios si trabajas de otra manera o algún ejemplo adicional sobre alguna operación típica con fechas.

¿Te ha resultado útil esta información? 🍺

Si este post te ha resuelto un problema, invítame a un café o a una cerveza. Con este pequeño gesto me animas a seguir escribiendo.

Comentarios

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *:

  • El fin del tratamiento es únicamente la moderación de comentarios para evitar spam
  • La legitimación es tu consentimiento al comentar
  • No se comunicará ningún dato a terceros salvo por obligación legal
  • Tienes derecho al acceso, rectificación y eliminación de los comentarios