Skip to content

Fechas y horas ​

Reservas, vencimientos de suscripciones, "publicado hace 3 horas", facturas del mes... Las fechas están en todas partes y son una fuente clásica de errores. Aquí aprenderás a manejarlas con la API moderna de PHP (DateTimeImmutable) y a evitar los problemas de zonas horarias.

Concepto ​

Internamente, una fecha es un instante en el tiempo. PHP puede representarlo de dos formas:

  • Timestamp Unix: un número entero con los segundos transcurridos desde el 1 de enero de 1970 a las 00:00 UTC. Ejemplo: 1760745600.
  • Objetos DateTime / DateTimeImmutable: objetos con métodos para sumar días, comparar, formatear y cambiar de zona horaria.

La zona horaria es como el "idioma" en que expresas la hora. El mismo instante es las 12:00 en Ciudad de México y las 20:00 en Madrid. Si no la configuras, PHP usa la de php.ini (o UTC) y tus horas saldrán desfasadas.

Configurar la zona horaria ​

php
<?php
// Al inicio de tu aplicación (por ejemplo en config.php)
date_default_timezone_set('America/Mexico_City');

// Otras comunes: 'America/Bogota', 'America/Lima', 'America/Argentina/Buenos_Aires',
// 'Europe/Madrid', 'America/Santiago', 'UTC'
echo date_default_timezone_get(); // America/Mexico_City

O en php.ini, para todo el servidor:

ini
date.timezone = "America/Mexico_City"

Funciones clásicas: date() y time() ​

php
<?php
echo time();                    // 1760745600  (timestamp actual)
echo date('Y-m-d');             // 2026-10-18
echo date('d/m/Y H:i:s');       // 18/10/2026 14:35:09
echo date('Y-m-d', 0);          // 1970-01-01 (timestamp 0)

// strtotime convierte texto en timestamp
$manana = strtotime('+1 day');
echo date('Y-m-d', $manana);

$proximoLunes = strtotime('next monday');
echo date('l d/m/Y', $proximoLunes); // Monday 19/10/2026

Caracteres de formato más usados ​

CarácterSignificadoEjemplo
YAño 4 dígitos2026
yAño 2 dígitos26
mMes con cero03
nMes sin cero3
dDía con cero07
jDía sin cero7
HHora 24h14
hHora 12h02
iMinutos05
sSegundos09
AAM/PMPM
NDía semana (1=lunes)1
tDías del mes31
cISO 86012026-10-18T14:35:09-06:00

La forma moderna: DateTimeImmutable ​

php
<?php
declare(strict_types=1);
date_default_timezone_set('America/Mexico_City');

// Fecha y hora actual
$ahora = new DateTimeImmutable();
echo $ahora->format('Y-m-d H:i'); // 2026-10-18 14:35

// Desde un texto
$cumple = new DateTimeImmutable('2000-05-21');

// Desde un formato específico (útil para fechas en formato latino)
$fecha = DateTimeImmutable::createFromFormat('d/m/Y', '25/12/2026');
echo $fecha->format('Y-m-d'); // 2026-12-25

// Sumar y restar con modify()
$vence = $ahora->modify('+30 days');
$ayer  = $ahora->modify('-1 day');
$finDeMes = $ahora->modify('last day of this month');

// Sumar con intervalos (formato ISO 8601: P = periodo, T = tiempo)
$enDosHoras = $ahora->add(new DateInterval('PT2H'));   // 2 horas
$enUnAnio   = $ahora->add(new DateInterval('P1Y'));    // 1 año
$en1m15d    = $ahora->add(new DateInterval('P1M15D')); // 1 mes y 15 días

¿Por qué Immutable y no DateTime? ​

php
<?php
// Con DateTime (mutable), modify() CAMBIA el objeto original
$inicio = new DateTime('2026-01-01');
$fin = $inicio->modify('+7 days');
echo $inicio->format('Y-m-d'); // 2026-01-08  ¡Se modificó el inicio!

// Con DateTimeImmutable, modify() devuelve un objeto NUEVO
$inicio = new DateTimeImmutable('2026-01-01');
$fin = $inicio->modify('+7 days');
echo $inicio->format('Y-m-d'); // 2026-01-01  intacto
echo $fin->format('Y-m-d');    // 2026-01-08

Buena práctica

Usa siempre DateTimeImmutable. Evita errores difíciles de encontrar donde una fecha cambia "sola" porque otra parte del código la modificó.

Comparar fechas y calcular diferencias ​

php
<?php
$hoy    = new DateTimeImmutable('today');
$evento = new DateTimeImmutable('2026-12-24');

// Los objetos se comparan directamente con < > ==
if ($evento > $hoy) {
    echo 'El evento aún no ocurre';
}

// diff() devuelve un DateInterval
$diferencia = $hoy->diff($evento);
echo $diferencia->days;      // días totales: 67
echo $diferencia->m;         // meses (parte): 2
echo $diferencia->d;         // días (parte): 6
echo $diferencia->invert;    // 1 si $evento es anterior a $hoy

// Formatear un intervalo
echo $diferencia->format('%m meses y %d días'); // 2 meses y 6 días

Calcular la edad ​

php
<?php
function calcularEdad(string $fechaNacimiento): int
{
    $nacimiento = new DateTimeImmutable($fechaNacimiento);
    $hoy = new DateTimeImmutable('today');
    return $nacimiento->diff($hoy)->y; // años completos
}

echo calcularEdad('2000-05-21'); // 26

Fechas en español ​

date() y format() siempre muestran los nombres en inglés. Para español usa la extensión intl (viene en XAMPP/Laragon; actívala en php.ini con extension=intl):

php
<?php
$fecha = new DateTimeImmutable('2026-10-18');

$formateador = new IntlDateFormatter(
    'es_MX',                       // idioma y país
    IntlDateFormatter::FULL,       // estilo de fecha
    IntlDateFormatter::NONE,       // sin hora
    'America/Mexico_City'
);
echo $formateador->format($fecha); // domingo, 18 de octubre de 2026

// Con un patrón personalizado (sintaxis ICU, distinta de date())
$corto = new IntlDateFormatter('es_MX', pattern: "d 'de' MMMM, yyyy");
echo $corto->format($fecha); // 18 de octubre, 2026

Si no tienes intl, una alternativa sencilla:

php
<?php
function fechaEnEspanol(DateTimeInterface $f): string
{
    $dias  = ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'];
    $meses = ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio',
              'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'];

    return sprintf(
        '%s %d de %s de %d',
        $dias[(int) $f->format('w')],      // w: 0 = domingo
        (int) $f->format('j'),
        $meses[(int) $f->format('n') - 1], // n: 1 = enero
        (int) $f->format('Y')
    );
}

echo fechaEnEspanol(new DateTimeImmutable('2026-10-18')); // domingo 18 de octubre de 2026

Caso real: "hace X tiempo" ​

php
<?php
declare(strict_types=1);

/**
 * Convierte una fecha en un texto relativo: "hace 5 minutos", "hace 2 días"...
 */
function haceCuanto(string $fecha): string
{
    $entonces = new DateTimeImmutable($fecha);
    $ahora    = new DateTimeImmutable();
    $segundos = $ahora->getTimestamp() - $entonces->getTimestamp();

    return match (true) {
        $segundos < 60        => 'hace un momento',
        $segundos < 3600      => 'hace ' . intdiv($segundos, 60) . ' min',
        $segundos < 86400     => 'hace ' . intdiv($segundos, 3600) . ' h',
        $segundos < 604800    => 'hace ' . intdiv($segundos, 86400) . ' días',
        default               => 'el ' . $entonces->format('d/m/Y'),
    };
}

echo haceCuanto('-3 hours'); // hace 3 h

Fechas y bases de datos ​

MySQL guarda fechas en formato Y-m-d H:i:s. Convierte siempre a ese formato antes de guardar:

php
<?php
// El usuario escribe 25/12/2026 en un input de texto
$entrada = $_POST['fecha'] ?? '';
$fecha = DateTimeImmutable::createFromFormat('!d/m/Y', $entrada);
// El "!" pone la hora en 00:00:00 en vez de la hora actual

// createFromFormat devuelve false si el formato no coincide
if ($fecha === false || $fecha->format('d/m/Y') !== $entrada) {
    // La segunda comprobación descarta fechas como 31/02/2026
    exit('Fecha inválida');
}

$paraMySQL = $fecha->format('Y-m-d'); // 2026-12-25

Un <input type="date"> en HTML ya envía el formato Y-m-d, lo que facilita todo:

php
<?php
$fecha = DateTimeImmutable::createFromFormat('!Y-m-d', $_POST['fecha'] ?? '');

Buena práctica

En aplicaciones con usuarios en varios países, guarda las fechas en UTC en la base de datos y conviértelas a la zona horaria del usuario sólo al mostrarlas:

php
$utc = new DateTimeImmutable('2026-10-18 20:00:00', new DateTimeZone('UTC'));
$local = $utc->setTimezone(new DateTimeZone('America/Bogota'));
echo $local->format('H:i'); // 15:00

Error común

Confundir m (mes) con i (minutos). date('H:m') muestra la hora y el mes, no los minutos. Lo correcto es date('H:i').

Error común

Validar una fecha sólo con strtotime(). Acepta cosas como "31 February" y las convierte silenciosamente al 3 de marzo. Usa createFromFormat + comparación, o checkdate($mes, $dia, $anio).

Ejercicios ​

  1. Escribe una función diasHasta(string $fecha): int que devuelva cuántos días faltan para una fecha (negativo si ya pasó).
  2. Genera un calendario simple: lista todos los días del mes actual con su nombre en español, marcando los fines de semana.
  3. Una suscripción empieza el 31 de enero de 2026 y se renueva cada mes. Calcula las 3 próximas fechas de renovación y descubre qué pasa con febrero. ¿Cómo lo arreglarías?
Ver solución
php
<?php
declare(strict_types=1);
date_default_timezone_set('America/Mexico_City');

// 1
function diasHasta(string $fecha): int
{
    $hoy = new DateTimeImmutable('today');
    $objetivo = new DateTimeImmutable($fecha);
    $diff = $hoy->diff($objetivo);
    return $diff->invert ? -$diff->days : $diff->days;
}
echo diasHasta('2026-12-31'), PHP_EOL;

// 2
$dias = ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'];
$inicio = new DateTimeImmutable('first day of this month');
$fin    = $inicio->modify('first day of next month');
// DatePeriod recorre fechas de un intervalo
$periodo = new DatePeriod($inicio, new DateInterval('P1D'), $fin);

foreach ($periodo as $dia) {
    $nombre = $dias[(int) $dia->format('w')];
    $finde  = in_array((int) $dia->format('w'), [0, 6], true) ? ' (fin de semana)' : '';
    echo $dia->format('d') . " $nombre$finde" . PHP_EOL;
}

// 3: el problema
$inicio = new DateTimeImmutable('2026-01-31');
echo $inicio->modify('+1 month')->format('Y-m-d'), PHP_EOL; // 2026-03-03 (¡se salta febrero!)

// Solución: calcular siempre desde la fecha original y limitar al último día del mes
function renovacion(DateTimeImmutable $inicio, int $meses): DateTimeImmutable
{
    $primerDia = $inicio->modify('first day of this month')->modify("+$meses months");
    $dia = min((int) $inicio->format('j'), (int) $primerDia->format('t'));
    return $primerDia->setDate((int) $primerDia->format('Y'), (int) $primerDia->format('n'), $dia);
}
for ($i = 1; $i <= 3; $i++) {
    echo renovacion($inicio, $i)->format('Y-m-d'), PHP_EOL;
}
// 2026-02-28, 2026-03-31, 2026-04-30

Resumen ​

  • Configura la zona horaria con date_default_timezone_set() o en php.ini.
  • date() y time() sirven para casos rápidos; para todo lo demás, DateTimeImmutable.
  • modify(), add() y DateInterval suman o restan tiempo; diff() calcula diferencias.
  • createFromFormat() convierte texto con formato conocido y permite validar.
  • Para nombres en español usa IntlDateFormatter o un arreglo propio.
  • Guarda en formato Y-m-d H:i:s en MySQL, idealmente en UTC.

Academia Web — aprende, copia y construye.