Cuando una fecha llega como numeros sueltos — un ano, un mes y un dia de un formulario o de columnas de un reporte — es tentador pegar una cadena y convertirla a timestamp. PostgreSQL ofrece una alternativa honesta: make_timestamp y make_interval construyen el valor directamente desde numeros, sin cadenas de formato ni adivinanzas de locale.
Una fecha a partir de partes numericas
make_timestamp recibe ano, mes, dia, hora, minuto y segundos como numeros normales y devuelve un timestamp. Al no parsear cadenas no hay sorpresas con el orden MM/DD frente a DD/MM.
SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS ts;
Los segundos son double precision, asi que tambien valen los decimales: make_timestamp(2024, 3, 15, 14, 30, 7.5). Hay parientes cercanos: make_date(2024, 3, 15) para una fecha pura y make_time(14, 30, 0) para una hora del dia.
Comparalo con la concatenacion habitual de cadenas, fragil y dependiente de la sesion:
SELECT (y || '-' || m || '-' || d)::date FROM (SELECT 2024 y, 3 m, 15 d) s;
Si m es 3 y no 03, o DateStyle esta en DMY, ese codigo devuelve en silencio la fecha equivocada o falla. make_timestamp elimina ambos fallos de una vez.
Un intervalo a partir de argumentos con nombre
make_interval construye un interval con campos con nombre: years, months, weeks, days, hours, mins, secs. Pasa solo lo que necesites; el resto vale cero por defecto.
SELECT make_interval(days => 10, hours => 2) AS shipping_window;
La gran ventaja es la parametrizacion. Cuando la cantidad es un numero variable, no puedes meterla en un literal INTERVAL de cadena, pero si pasarla directo a make_interval:
SELECT o.id,
o.created_at + make_interval(days => 14) AS grace_until
FROM orders o
WHERE o.status = 'paid';
Ese 14 podria ser una columna o un marcador $1. Esta es la diferencia clave con un intervalo de cadena: INTERVAL '$1 days' no funciona porque el marcador quedaria dentro del literal, mientras que make_interval(days => $1) se enlaza correctamente.
SELECT u.id, u.email,
u.created_at + make_interval(days => 30) AS trial_ends
FROM users u
WHERE u.country = 'DE';
make_timestamptz y la zona horaria
make_timestamp tiene un hermano con zona, make_timestamptz. Un argumento de texto extra nombra la zona en la que se interpretan los numeros entregados; el resultado es un timestamptz.
SELECT make_timestamptz(2024, 3, 15, 14, 30, 0, 'Europe/Berlin') AS ts_tz;
Sin el ultimo argumento los numeros se leen en la zona de la sesion actual (TimeZone). Es comodo para fechas limite ancladas a una region concreta:
SELECT u.id,
make_timestamptz(2024, 12, 31, 23, 59, 59, 'America/Sao_Paulo') AS cutoff
FROM users u
WHERE u.country = 'BR';
Cuidado: los argumentos de make_* no se desbordan hacia adelante. make_timestamp(2024, 13, 1, 0, 0, 0) no pasa a enero de 2025 — lanza field value out of range. Lo mismo con make_date(2024, 2, 30). Si las entradas son numeros no confiables, validalas antes o captura la excepcion.
Diferencias en otros motores
make_timestamp, make_interval y compania son de PostgreSQL. Otros motores siguen otro camino:
- MySQL: arma la fecha con
MAKEDATE/MAKETIME o STR_TO_DATE('2024-03-15','%Y-%m-%d'). Los intervalos usan la sintaxis INTERVAL 10 DAY, y para una cantidad variable escribes INTERVAL n DAY, donde n puede ser una expresion.
- ClickHouse: usa
makeDateTime(2024, 3, 15, 14, 30, 0) y makeDateTime64(...) para precision de fraccion de segundo; la zona es un argumento aparte. Construye un intervalo con toIntervalDay(n), toIntervalHour(n) y sumalos.
Si el codigo debe ser portable, aisla el armado de fechas en una sola capa: la familia make_* en PostgreSQL, funciones nativas en el resto. El principio central es el mismo en todas partes: no pegues fechas desde cadenas cuando el motor sabe construirlas desde numeros.
Cuando una fecha llega como numeros sueltos — un ano, un mes y un dia de un formulario o de columnas de un reporte — es tentador pegar una cadena y convertirla a
timestamp. PostgreSQL ofrece una alternativa honesta:make_timestampymake_intervalconstruyen el valor directamente desde numeros, sin cadenas de formato ni adivinanzas de locale.Una fecha a partir de partes numericas
make_timestamprecibe ano, mes, dia, hora, minuto y segundos como numeros normales y devuelve untimestamp. Al no parsear cadenas no hay sorpresas con el ordenMM/DDfrente aDD/MM.SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS ts; -- 2024-03-15 14:30:00Los segundos son
double precision, asi que tambien valen los decimales:make_timestamp(2024, 3, 15, 14, 30, 7.5). Hay parientes cercanos:make_date(2024, 3, 15)para una fecha pura ymake_time(14, 30, 0)para una hora del dia.Comparalo con la concatenacion habitual de cadenas, fragil y dependiente de la sesion:
-- fragile: depends on DateStyle and zero-padding SELECT (y || '-' || m || '-' || d)::date FROM (SELECT 2024 y, 3 m, 15 d) s;Si
mes3y no03, oDateStyleesta enDMY, ese codigo devuelve en silencio la fecha equivocada o falla.make_timestampelimina ambos fallos de una vez.Un intervalo a partir de argumentos con nombre
make_intervalconstruye unintervalcon campos con nombre:years,months,weeks,days,hours,mins,secs. Pasa solo lo que necesites; el resto vale cero por defecto.SELECT make_interval(days => 10, hours => 2) AS shipping_window; -- 10 days 02:00:00La gran ventaja es la parametrizacion. Cuando la cantidad es un numero variable, no puedes meterla en un literal
INTERVALde cadena, pero si pasarla directo amake_interval:-- give every paid order a grace period of N days SELECT o.id, o.created_at + make_interval(days => 14) AS grace_until FROM orders o WHERE o.status = 'paid';Ese
14podria ser una columna o un marcador$1. Esta es la diferencia clave con un intervalo de cadena:INTERVAL '$1 days'no funciona porque el marcador quedaria dentro del literal, mientras quemake_interval(days => $1)se enlaza correctamente.-- per-row interval driven by data, not a constant string SELECT u.id, u.email, u.created_at + make_interval(days => 30) AS trial_ends FROM users u WHERE u.country = 'DE';make_timestamptz y la zona horaria
make_timestamptiene un hermano con zona,make_timestamptz. Un argumento de texto extra nombra la zona en la que se interpretan los numeros entregados; el resultado es untimestamptz.SELECT make_timestamptz(2024, 3, 15, 14, 30, 0, 'Europe/Berlin') AS ts_tz; -- stored as UTC, shown in your session zoneSin el ultimo argumento los numeros se leen en la zona de la sesion actual (
TimeZone). Es comodo para fechas limite ancladas a una region concreta:SELECT u.id, make_timestamptz(2024, 12, 31, 23, 59, 59, 'America/Sao_Paulo') AS cutoff FROM users u WHERE u.country = 'BR';Diferencias en otros motores
make_timestamp,make_intervaly compania son de PostgreSQL. Otros motores siguen otro camino:MAKEDATE/MAKETIMEoSTR_TO_DATE('2024-03-15','%Y-%m-%d'). Los intervalos usan la sintaxisINTERVAL 10 DAY, y para una cantidad variable escribesINTERVAL n DAY, dondenpuede ser una expresion.makeDateTime(2024, 3, 15, 14, 30, 0)ymakeDateTime64(...)para precision de fraccion de segundo; la zona es un argumento aparte. Construye un intervalo contoIntervalDay(n),toIntervalHour(n)y sumalos.Si el codigo debe ser portable, aisla el armado de fechas en una sola capa: la familia
make_*en PostgreSQL, funciones nativas en el resto. El principio central es el mismo en todas partes: no pegues fechas desde cadenas cuando el motor sabe construirlas desde numeros.