En PostgreSQL puedes sumar y restar fechas y marcas de tiempo como numeros, pero las reglas dependen del tipo: un date no se comporta como un timestamp. Entender la diferencia te ahorra los errores clasicos al calcular vencimientos, antiguedad y "cuanto hace que ocurrio" un evento.
Sumar dias a una fecha
El caso mas comun es sumar un entero a un date. PostgreSQL lo trata como dias:
SELECT DATE '2024-03-01' + 7 AS next_week;
SELECT DATE '2024-03-01' - 30 AS month_ago;
Es comodo para fechas de vencimiento: una factura suele pagarse N dias despues de crear el pedido.
SELECT id,
created_at::date AS placed_on,
created_at::date + 14 AS due_date
FROM orders
WHERE status = 'pending';
- Sumar un entero a un
date suma dias.
- No puedes sumar un entero a un
timestamp — es un error de tipos, necesitas un interval.
- Para "un mes despues" no escribas
+ 30: los meses tienen distinta longitud, usa INTERVAL '1 month'.
Diferencia de fechas frente a diferencia de timestamp
Aqui esta la trampa clave. Restar dos valores date da un numero entero de dias, mientras que restar dos valores timestamp da un interval.
SELECT DATE '2024-03-01' - DATE '2024-01-15' AS days;
SELECT TIMESTAMP '2024-03-01 09:00'
- TIMESTAMP '2024-01-15 18:30' AS span;
Por eso la misma consulta se comporta distinto segun el tipo de las columnas. Para garantizar dias completos, convierte ambos operandos a date:
SELECT id,
(NOW()::date - created_at::date) AS days_open
FROM orders
WHERE status = 'pending';
Cuidado: NOW() - created_at sobre dos timestamp devuelve un interval como 45 days 14:30:00, y compararlo con > 30 falla con un error de tipos. O conviertes a date, o comparas contra un intervalo: NOW() - created_at > INTERVAL '30 days'.
Intervalos para timestamps
A un timestamp le sumas un interval, no un numero. Los intervalos respetan el calendario: + INTERVAL '1 month' sobre el 31 de enero cae en el 28 (o 29) de febrero, no en "30 dias despues".
SELECT created_at + INTERVAL '7 days' AS reminder_at,
created_at + INTERVAL '1 month' AS renew_at,
created_at + INTERVAL '36 hours' AS grace_until
FROM users;
Los intervalos se suman y escalan, asi que una ventana de "ultimos seis meses" queda compacta:
SELECT id, email
FROM users
WHERE created_at >= NOW() - INTERVAL '6 months';
Antiguedad y edad en dias frente a meses
Para la antiguedad de un empleado a menudo quieres dias en bruto — entonces restas valores date. Para una edad legible en anos y meses usa AGE, que normaliza el intervalo por el calendario.
SELECT name, dept,
NOW()::date - created_at::date AS tenure_days,
AGE(NOW(), created_at) AS tenure_human
FROM employees
ORDER BY tenure_days DESC;
Recuerda: 365 dias no siempre es "un ano". Si necesitas anos cumplidos, calculalos con AGE y EXTRACT(YEAR ...) en lugar de dividir los dias entre 365.
Diferencias en otros motores
La sintaxis cambia bastante entre motores:
- MySQL: restar fechas no te da dias. Usa
DATEDIFF(end, start) para dias y DATE_ADD(d, INTERVAL 7 DAY) / DATE_SUB para desplazar. Un simple d + 7 no hace lo que esperas.
- ClickHouse: usa
date + 7 (dias) y las funciones dateDiff('day', start, end), addDays, addMonths; siempre indicas la unidad de forma explicita.
Si el codigo debe ser portable, no dependas de date - date: envuelve la logica en DATEDIFF/dateDiff y usa intervalos con nombre.
En PostgreSQL puedes sumar y restar fechas y marcas de tiempo como numeros, pero las reglas dependen del tipo: un
dateno se comporta como untimestamp. Entender la diferencia te ahorra los errores clasicos al calcular vencimientos, antiguedad y "cuanto hace que ocurrio" un evento.Sumar dias a una fecha
El caso mas comun es sumar un entero a un
date. PostgreSQL lo trata como dias:SELECT DATE '2024-03-01' + 7 AS next_week; -- 2024-03-08 SELECT DATE '2024-03-01' - 30 AS month_ago; -- 2024-01-31Es comodo para fechas de vencimiento: una factura suele pagarse N dias despues de crear el pedido.
SELECT id, created_at::date AS placed_on, created_at::date + 14 AS due_date FROM orders WHERE status = 'pending';datesuma dias.timestamp— es un error de tipos, necesitas uninterval.+ 30: los meses tienen distinta longitud, usaINTERVAL '1 month'.Diferencia de fechas frente a diferencia de timestamp
Aqui esta la trampa clave. Restar dos valores
dateda un numero entero de dias, mientras que restar dos valorestimestampda uninterval.SELECT DATE '2024-03-01' - DATE '2024-01-15' AS days; -- 46 (integer) SELECT TIMESTAMP '2024-03-01 09:00' - TIMESTAMP '2024-01-15 18:30' AS span; -- 45 days 14:30:00Por eso la misma consulta se comporta distinto segun el tipo de las columnas. Para garantizar dias completos, convierte ambos operandos a
date:SELECT id, (NOW()::date - created_at::date) AS days_open FROM orders WHERE status = 'pending';Intervalos para timestamps
A un
timestample sumas uninterval, no un numero. Los intervalos respetan el calendario:+ INTERVAL '1 month'sobre el 31 de enero cae en el 28 (o 29) de febrero, no en "30 dias despues".SELECT created_at + INTERVAL '7 days' AS reminder_at, created_at + INTERVAL '1 month' AS renew_at, created_at + INTERVAL '36 hours' AS grace_until FROM users;Los intervalos se suman y escalan, asi que una ventana de "ultimos seis meses" queda compacta:
SELECT id, email FROM users WHERE created_at >= NOW() - INTERVAL '6 months';Antiguedad y edad en dias frente a meses
Para la antiguedad de un empleado a menudo quieres dias en bruto — entonces restas valores
date. Para una edad legible en anos y meses usaAGE, que normaliza el intervalo por el calendario.SELECT name, dept, NOW()::date - created_at::date AS tenure_days, AGE(NOW(), created_at) AS tenure_human FROM employees ORDER BY tenure_days DESC;Recuerda: 365 dias no siempre es "un ano". Si necesitas anos cumplidos, calculalos con
AGEyEXTRACT(YEAR ...)en lugar de dividir los dias entre 365.Diferencias en otros motores
La sintaxis cambia bastante entre motores:
DATEDIFF(end, start)para dias yDATE_ADD(d, INTERVAL 7 DAY)/DATE_SUBpara desplazar. Un simpled + 7no hace lo que esperas.date + 7(dias) y las funcionesdateDiff('day', start, end),addDays,addMonths; siempre indicas la unidad de forma explicita.Si el codigo debe ser portable, no dependas de
date - date: envuelve la logica enDATEDIFF/dateDiffy usa intervalos con nombre.