Curso de PHP
Fechas y horas en PHP: DateTime, formatos y zonas horarias
Por Víctor Peña · Publicado el
Hola, ¿cómo están? Cuarto tema complementario del curso de PHP. Las fechas parecen algo trivial hasta que aparecen las zonas horarias, los meses de distinta duración y los años bisiestos.
¡Empecemos!
Empieza por configurar la zona horaria
Lo primero de todo, al inicio de tu aplicación:
<?php
date_default_timezone_set('America/La_Paz');
Sin esto, PHP usa la zona configurada en el servidor, que rara vez es la tuya. Es el origen de la mitad de los problemas con fechas: la hora que ves en local no coincide con la del servidor de producción.
La función date
Para lo sencillo, date() alcanza:
<?php
echo date('Y-m-d'); // 2026-03-01
echo date('d/m/Y H:i'); // 01/03/2026 14:30
echo date('l, j \d\e F Y'); // Sunday, 1 de March 2026
Los formatos más usados:
| Letra | Significado | Ejemplo |
|---|---|---|
Y / y |
Año | 2026 / 26 |
m / n |
Mes con o sin cero | 03 / 3 |
d / j |
Día con o sin cero | 01 / 1 |
H / h |
Hora 24 o 12 | 14 / 02 |
i |
Minutos | 30 |
s |
Segundos | 45 |
l / D |
Día de la semana | Sunday / Sun |
F / M |
Mes en texto | March / Mar |
DateTimeImmutable: la forma correcta
Para todo lo que no sea imprimir la fecha actual, usa objetos. Y concretamente DateTimeImmutable, no DateTime.
<?php
$hoy = new DateTimeImmutable();
$fecha = new DateTimeImmutable('2026-03-15');
$otra = new DateTimeImmutable('2026-03-15 14:30:00');
echo $fecha->format('d/m/Y'); // 15/03/2026
Por qué inmutable
Esta diferencia causa errores muy difíciles de encontrar:
<?php
// DateTime: se modifica a sí mismo
$fecha = new DateTime('2026-03-01');
$vencimiento = $fecha->modify('+30 days');
echo $fecha->format('Y-m-d'); // 2026-03-31 ← ¡cambió!
echo $vencimiento->format('Y-m-d'); // 2026-03-31
<?php
// DateTimeImmutable: devuelve uno nuevo
$fecha = new DateTimeImmutable('2026-03-01');
$vencimiento = $fecha->modify('+30 days');
echo $fecha->format('Y-m-d'); // 2026-03-01 ← intacta
echo $vencimiento->format('Y-m-d'); // 2026-03-31
Con DateTime, pasar la fecha a una función que la modifica cambia tu variable original sin avisar. Usa siempre DateTimeImmutable salvo que tengas una razón muy concreta.
Fechas relativas
PHP entiende expresiones en inglés que resultan muy cómodas:
<?php
new DateTimeImmutable('tomorrow');
new DateTimeImmutable('+1 week');
new DateTimeImmutable('first day of next month');
new DateTimeImmutable('last day of this month');
new DateTimeImmutable('next monday');
Sumar y restar tiempo
<?php
$hoy = new DateTimeImmutable();
$en30dias = $hoy->modify('+30 days');
$haceUnAnio = $hoy->modify('-1 year');
// O con DateInterval, más explícito
$vencimiento = $hoy->add(new DateInterval('P30D')); // P = periodo, 30 D = días
$antes = $hoy->sub(new DateInterval('P1M')); // 1 mes
El formato de DateInterval es raro pero se aprende rápido: P inicia el periodo, T separa la parte de tiempo. P1Y2M3DT4H5M es un año, dos meses, tres días, cuatro horas y cinco minutos.
Calcular diferencias
<?php
$inicio = new DateTimeImmutable('2026-01-15');
$fin = new DateTimeImmutable('2026-03-01');
$diferencia = $inicio->diff($fin);
echo $diferencia->days; // 45 ← total de días
echo $diferencia->m; // 1 ← meses de la diferencia
echo $diferencia->d; // 14 ← días restantes tras los meses
echo $diferencia->format('%m meses y %d días');
Ojo con la distinción: days es el total, mientras que m y d son las partes descompuestas. Confundirlos es un error clásico.
Calcular una edad
<?php
function calcularEdad(string $nacimiento): int
{
$fecha = new DateTimeImmutable($nacimiento);
return $fecha->diff(new DateTimeImmutable())->y;
}
echo calcularEdad('1990-06-15');
diff() se encarga de los años bisiestos y de si ya pasó el cumpleaños este año. No calcules edades restando años a mano.
Comparar fechas
Los objetos de fecha se comparan directamente:
<?php
$vencimiento = new DateTimeImmutable('2026-03-15');
$hoy = new DateTimeImmutable();
if ($vencimiento < $hoy) {
echo "Vencido";
}
Fechas en español
format() devuelve los nombres en inglés. Para español, usa IntlDateFormatter:
<?php
$formateador = new IntlDateFormatter(
'es_ES',
IntlDateFormatter::LONG,
IntlDateFormatter::NONE
);
echo $formateador->format(new DateTimeImmutable());
// 1 de marzo de 2026
Requiere la extensión intl, que suele venir activada. Si no la tienes, la alternativa es un array de traducción, pero intl maneja además los formatos propios de cada región.
Fechas y base de datos
Guarda siempre en el formato que MySQL entiende:
<?php
$fecha = new DateTimeImmutable();
$sentencia = $pdo->prepare("INSERT INTO pedidos (creado_en) VALUES (:fecha)");
$sentencia->execute(['fecha' => $fecha->format('Y-m-d H:i:s')]);
Y al leer:
<?php
$pedido = $sentencia->fetch();
$creado = new DateTimeImmutable($pedido['creado_en']);
echo $creado->format('d/m/Y');
Nunca guardes fechas como texto en formato local. Un 15/03/2026 en la base de datos no se puede ordenar ni comparar correctamente, y además es ambiguo: en otros países eso sería el 3 de quince.
Zonas horarias
Si tu aplicación tiene usuarios en varios países, la práctica recomendada es guardar todo en UTC y convertir al mostrar:
<?php
// Guardar en UTC
$ahora = new DateTimeImmutable('now', new DateTimeZone('UTC'));
// Mostrar en la zona del usuario
$local = $ahora->setTimezone(new DateTimeZone('America/La_Paz'));
echo $local->format('d/m/Y H:i');
Validar una fecha
Cuando la fecha llega de un formulario, no confíes en que sea válida:
<?php
function esFechaValida(string $valor, string $formato = 'Y-m-d'): bool
{
$fecha = DateTimeImmutable::createFromFormat($formato, $valor);
return $fecha && $fecha->format($formato) === $valor;
}
var_dump(esFechaValida('2026-03-15')); // true
var_dump(esFechaValida('2026-02-30')); // false, febrero no tiene 30
La segunda comprobación es la importante: PHP acepta 2026-02-30 y lo convierte al 2 de marzo. Comparar el resultado con la entrada detecta esos casos.
Un ejemplo práctico
<?php
declare(strict_types=1);
date_default_timezone_set('America/La_Paz');
class Suscripcion
{
public function __construct(
private readonly DateTimeImmutable $inicio,
private readonly int $mesesDuracion,
) {}
public function vencimiento(): DateTimeImmutable
{
return $this->inicio->add(new DateInterval("P{$this->mesesDuracion}M"));
}
public function diasRestantes(): int
{
$hoy = new DateTimeImmutable();
$vence = $this->vencimiento();
if ($vence < $hoy) {
return 0;
}
return $hoy->diff($vence)->days;
}
public function estaVencida(): bool
{
return $this->vencimiento() < new DateTimeImmutable();
}
}
$sub = new Suscripcion(new DateTimeImmutable('2026-01-15'), 6);
echo $sub->vencimiento()->format('d/m/Y') . "\n"; // 15/07/2026
echo $sub->diasRestantes() . " días restantes\n";
var_dump($sub->estaVencida());
Errores comunes
- No configurar la zona horaria y encontrarte con horas desplazadas en producción.
- Usar
DateTimeen lugar deDateTimeImmutabley modificar sin querer la fecha original. - Guardar fechas como texto en formato local.
- Confundir
daysconden una diferencia. - Calcular edades restando años, sin considerar si ya pasó el cumpleaños.
- Confiar en que una fecha de formulario es válida.
Para cerrar
Las fechas dan más problemas de los que parece, y casi todos se evitan con tres hábitos: configurar la zona horaria, usar DateTimeImmutable y guardar siempre en formato Y-m-d H:i:s.
En el último artículo veremos cómo consumir APIs externas.
Saludos y éxitos.
Norvic Software
Desarrollamos el software que tu empresa necesita
Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.
- Chatbots con Inteligencia Artificial
- Creación de agentes de IA
- Integración de IA en tus sistemas
- Desarrollo de software a medida
- Aplicaciones móviles iOS y Android
- Consultoría y asesoramiento técnico
Cotización sin costo · Respuesta directa por WhatsApp