Quando uma data chega como numeros soltos — um ano, um mes e um dia de um formulario ou de colunas de relatorio — e tentador colar uma string e converte-la para timestamp. O PostgreSQL oferece uma alternativa honesta: make_timestamp e make_interval constroem o valor direto a partir de numeros, sem strings de formato e sem adivinhar locale.
Uma data a partir de partes numericas
make_timestamp recebe ano, mes, dia, hora, minuto e segundos como numeros comuns e devolve um timestamp. Sem parsear strings, nao ha surpresa com a ordem MM/DD contra DD/MM.
SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS ts;
Os segundos sao double precision, entao valores fracionarios tambem funcionam: make_timestamp(2024, 3, 15, 14, 30, 7.5). Ha parentes proximos: make_date(2024, 3, 15) para uma data pura e make_time(14, 30, 0) para uma hora do dia.
Compare com a concatenacao usual de strings, fragil e dependente da sessao:
SELECT (y || '-' || m || '-' || d)::date FROM (SELECT 2024 y, 3 m, 15 d) s;
Se m for 3 em vez de 03, ou se DateStyle estiver em DMY, esse codigo devolve em silencio a data errada ou falha. make_timestamp elimina os dois modos de falha de uma vez.
Um intervalo a partir de argumentos nomeados
make_interval constroi um interval com campos nomeados: years, months, weeks, days, hours, mins, secs. Passe apenas o que precisar; o resto e zero por padrao.
SELECT make_interval(days => 10, hours => 2) AS shipping_window;
O grande ganho e a parametrizacao. Quando a quantidade e um numero variavel, voce nao consegue coloca-la dentro de um literal INTERVAL de string, mas consegue passa-la direto para make_interval:
SELECT o.id,
o.created_at + make_interval(days => 14) AS grace_until
FROM orders o
WHERE o.status = 'paid';
Esse 14 pode ser uma coluna ou um marcador $1. Essa e a diferenca chave em relacao a um intervalo de string: INTERVAL '$1 days' nao funciona porque o marcador ficaria dentro do literal, enquanto make_interval(days => $1) faz o bind corretamente.
SELECT u.id, u.email,
u.created_at + make_interval(days => 30) AS trial_ends
FROM users u
WHERE u.country = 'DE';
make_timestamptz e o fuso horario
make_timestamp tem um irmao com fuso, make_timestamptz. Um argumento de texto extra nomeia o fuso em que os numeros fornecidos sao interpretados; o resultado e um timestamptz.
SELECT make_timestamptz(2024, 3, 15, 14, 30, 0, 'Europe/Berlin') AS ts_tz;
Sem o ultimo argumento os numeros sao lidos no fuso da sessao atual (TimeZone). Isso e pratico para prazos ancorados a uma regiao especifica:
SELECT u.id,
make_timestamptz(2024, 12, 31, 23, 59, 59, 'America/Sao_Paulo') AS cutoff
FROM users u
WHERE u.country = 'BR';
Pegadinha: os argumentos de make_* nao transbordam para frente. make_timestamp(2024, 13, 1, 0, 0, 0) nao vira janeiro de 2025 — ele levanta field value out of range. O mesmo vale para make_date(2024, 2, 30). Se as entradas sao numeros nao confiaveis, valide antes ou capture a excecao.
Diferencas em outros bancos
make_timestamp, make_interval e companhia sao do PostgreSQL. Outros bancos seguem caminho diferente:
- MySQL: monte a data com
MAKEDATE/MAKETIME ou STR_TO_DATE('2024-03-15','%Y-%m-%d'). Intervalos usam a sintaxe INTERVAL 10 DAY, e para uma quantidade variavel voce escreve INTERVAL n DAY, onde n pode ser uma expressao.
- ClickHouse: use
makeDateTime(2024, 3, 15, 14, 30, 0) e makeDateTime64(...) para precisao de fracao de segundo; o fuso e um argumento separado. Construa um intervalo com toIntervalDay(n), toIntervalHour(n) e some-os.
Se o codigo precisa ser portavel, isole a montagem de datas em uma unica camada: a familia make_* no PostgreSQL, funcoes nativas no resto. O principio central e o mesmo em todo lugar: nao cole datas a partir de strings quando o banco sabe construi-las a partir de numeros.
Quando uma data chega como numeros soltos — um ano, um mes e um dia de um formulario ou de colunas de relatorio — e tentador colar uma string e converte-la para
timestamp. O PostgreSQL oferece uma alternativa honesta:make_timestampemake_intervalconstroem o valor direto a partir de numeros, sem strings de formato e sem adivinhar locale.Uma data a partir de partes numericas
make_timestamprecebe ano, mes, dia, hora, minuto e segundos como numeros comuns e devolve umtimestamp. Sem parsear strings, nao ha surpresa com a ordemMM/DDcontraDD/MM.SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS ts; -- 2024-03-15 14:30:00Os segundos sao
double precision, entao valores fracionarios tambem funcionam:make_timestamp(2024, 3, 15, 14, 30, 7.5). Ha parentes proximos:make_date(2024, 3, 15)para uma data pura emake_time(14, 30, 0)para uma hora do dia.Compare com a concatenacao usual de strings, fragil e dependente da sessao:
-- fragile: depends on DateStyle and zero-padding SELECT (y || '-' || m || '-' || d)::date FROM (SELECT 2024 y, 3 m, 15 d) s;Se
mfor3em vez de03, ou seDateStyleestiver emDMY, esse codigo devolve em silencio a data errada ou falha.make_timestampelimina os dois modos de falha de uma vez.Um intervalo a partir de argumentos nomeados
make_intervalconstroi umintervalcom campos nomeados:years,months,weeks,days,hours,mins,secs. Passe apenas o que precisar; o resto e zero por padrao.SELECT make_interval(days => 10, hours => 2) AS shipping_window; -- 10 days 02:00:00O grande ganho e a parametrizacao. Quando a quantidade e um numero variavel, voce nao consegue coloca-la dentro de um literal
INTERVALde string, mas consegue passa-la direto paramake_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';Esse
14pode ser uma coluna ou um marcador$1. Essa e a diferenca chave em relacao a um intervalo de string:INTERVAL '$1 days'nao funciona porque o marcador ficaria dentro do literal, enquantomake_interval(days => $1)faz o bind corretamente.-- 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 e o fuso horario
make_timestamptem um irmao com fuso,make_timestamptz. Um argumento de texto extra nomeia o fuso em que os numeros fornecidos sao interpretados; o resultado e umtimestamptz.SELECT make_timestamptz(2024, 3, 15, 14, 30, 0, 'Europe/Berlin') AS ts_tz; -- stored as UTC, shown in your session zoneSem o ultimo argumento os numeros sao lidos no fuso da sessao atual (
TimeZone). Isso e pratico para prazos ancorados a uma regiao especifica:SELECT u.id, make_timestamptz(2024, 12, 31, 23, 59, 59, 'America/Sao_Paulo') AS cutoff FROM users u WHERE u.country = 'BR';Diferencas em outros bancos
make_timestamp,make_intervale companhia sao do PostgreSQL. Outros bancos seguem caminho diferente:MAKEDATE/MAKETIMEouSTR_TO_DATE('2024-03-15','%Y-%m-%d'). Intervalos usam a sintaxeINTERVAL 10 DAY, e para uma quantidade variavel voce escreveINTERVAL n DAY, ondenpode ser uma expressao.makeDateTime(2024, 3, 15, 14, 30, 0)emakeDateTime64(...)para precisao de fracao de segundo; o fuso e um argumento separado. Construa um intervalo comtoIntervalDay(n),toIntervalHour(n)e some-os.Se o codigo precisa ser portavel, isole a montagem de datas em uma unica camada: a familia
make_*no PostgreSQL, funcoes nativas no resto. O principio central e o mesmo em todo lugar: nao cole datas a partir de strings quando o banco sabe construi-las a partir de numeros.