See artikkel on praegu venekeelne — ingliskeelne tõlge on töös.
make_timestamp собирает значение timestamp из чисел года, месяца, дня, часа, минуты и секунд, а make_interval строит interval из именованных аргументов вроде days и hours. Эти функции PostgreSQL нужны там, где компоненты даты приходят раздельно — из полей формы, колонок отчёта, генератора календаря или параметра тарифа, — и склеивать из них строку с последующим кастом к timestamp рискованно. Сборка прямо из чисел не зависит от форматных строк, локали и настройки DateStyle.
Разница видна на параметризации. Срок в днях, который приходит переменной или плейсхолдером $1, нельзя вставить внутрь строкового литерала INTERVAL '... days', а в make_interval(days => $1) он подставляется как обычный аргумент. Точно так же make_timestamp(2024, 3, 15, ...) отдаёт тип timestamp без промежуточной строки, поэтому порядок MM/DD против DD/MM и нулевое дополнение перестают влиять на результат. Дальше разберём сборку даты, интервала и варианта с часовым поясом по очереди.
Дата из числовых частей
make_timestamp принимает год, месяц, день, час, минуту и секунды как обычные числа и возвращает timestamp. Никакого парсинга строк — а значит, никаких сюрпризов с порядком MM/DD против DD/MM.
SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS ts;
Секунды — это double precision, так что дробная часть тоже работает: make_timestamp(2024, 3, 15, 14, 30, 7.5). Есть и родственники: make_date(2024, 3, 15) для чистой даты и make_time(14, 30, 0) для времени без даты.
Сравните с типичной склейкой строк — она хрупкая и зависит от настроек сессии:
SELECT (y || '-' || m || '-' || d)::date FROM (SELECT 2024 y, 3 m, 15 d) s;
Если m равно 3, а не 03, или DateStyle настроен на DMY, такой код тихо вернёт не ту дату либо упадёт. make_timestamp решает обе проблемы разом.
Интервал из именованных аргументов
make_interval строит interval из именованных полей: years, months, weeks, days, hours, mins, secs. Передавайте только то, что нужно, — остальное по умолчанию ноль.
SELECT make_interval(days => 10, hours => 2) AS shipping_window;
Главный выигрыш — параметризация. Когда срок задан переменным числом, его нельзя подставить в строковый литерал INTERVAL, зато легко передать в make_interval:
SELECT o.id,
o.created_at + make_interval(days => 14) AS grace_until
FROM orders o
WHERE o.status = 'paid';
Число 14 здесь может быть колонкой или плейсхолдером $1. Это ключевое отличие от строкового интервала: INTERVAL '$1 days' не работает, потому что плейсхолдер оказался бы внутри литерала, а make_interval(days => $1) — корректно. Альтернатива со склейкой и кастом (($1 || ' days')::interval) тоже сработает, но именованные аргументы читаются яснее и не зависят от того, как локаль печатает число.
SELECT u.id, u.email,
u.created_at + make_interval(days => 30) AS trial_ends
FROM users u
WHERE u.country = 'DE';
make_timestamptz и часовой пояс
У make_timestamp есть версия с поясом — make_timestamptz. Дополнительный текстовый аргумент задаёт зону, в которой трактуются переданные числа; результат имеет тип timestamptz.
SELECT make_timestamptz(2024, 3, 15, 14, 30, 0, 'Europe/Berlin') AS ts_tz;
Без последнего аргумента числа трактуются в часовом поясе текущей сессии (TimeZone). Это удобно для дедлайнов, привязанных к конкретному региону:
SELECT u.id,
make_timestamptz(2024, 12, 31, 23, 59, 59, 'America/Sao_Paulo') AS cutoff
FROM users u
WHERE u.country = 'BR';
Ловушка: аргументы make_* не «оборачиваются» автоматически. make_timestamp(2024, 13, 1, 0, 0, 0) не станет январём 2025 года — будет ошибка field value out of range. То же с make_date(2024, 2, 30). Если на входе непроверенные числа, валидируйте их заранее или ловите исключение.
Различия в других СУБД
make_timestamp, make_interval и компания — это PostgreSQL. В других движках подход иной:
- MySQL: соберите дату через
MAKEDATE/MAKETIME либо STR_TO_DATE('2024-03-15','%Y-%m-%d'). Интервалы задаются синтаксисом INTERVAL 10 DAY, а для переменного количества — INTERVAL n DAY, где n может быть выражением.
- ClickHouse: используйте
makeDateTime(2024, 3, 15, 14, 30, 0) и makeDateTime64(...) для долей секунды; зона передаётся отдельным аргументом. Интервал собирается через toIntervalDay(n), toIntervalHour(n) и их сложение.
При переносе между движками сходится не всё, и расходятся именно крайние значения. make_timestamp отдаёт timestamp без зоны, make_timestamptz — timestamptz в UTC, тогда как makeDateTime в ClickHouse и MAKEDATE в MySQL трактуют зону по-своему; дробные секунды в PostgreSQL живут в аргументе secs, а в ClickHouse требуют отдельной makeDateTime64. Прежде чем полагаться на формулу, прогоните пограничные входы: 13-й месяц, 30 февраля, отрицательный аргумент интервала и пустую зону — make_* на них кидает field value out of range, а соседний движок может молча нормализовать.
Если сборка даты влияет на ключ, биллинг или дедлайн, держите её в одном слое и описывайте рядом, в какой зоне трактуются числа: одно и то же make_timestamptz(..., 'Europe/Berlin') и make_timestamp(...) дают разные абсолютные моменты. И помните про индексы: make_interval(days => $1) поверх колонки created_at в WHERE превращает условие в вычисляемое и может закрыть путь к индексу — переносите сборку интервала на сторону константы или диапазона, а не оборачивайте в неё индексируемую колонку.
Если код переносимый, изолируйте сборку в одном слое: на PostgreSQL — семейство make_*, на остальных — родные функции (makeDateTime, STR_TO_DATE, INTERVAL n DAY). Принцип один: не клейте даты из строк, когда движок умеет собирать timestamp и interval прямо из чисел.
make_timestampсобирает значениеtimestampиз чисел года, месяца, дня, часа, минуты и секунд, аmake_intervalстроитintervalиз именованных аргументов вродеdaysиhours. Эти функции PostgreSQL нужны там, где компоненты даты приходят раздельно — из полей формы, колонок отчёта, генератора календаря или параметра тарифа, — и склеивать из них строку с последующим кастом кtimestampрискованно. Сборка прямо из чисел не зависит от форматных строк, локали и настройкиDateStyle.Разница видна на параметризации. Срок в днях, который приходит переменной или плейсхолдером
$1, нельзя вставить внутрь строкового литералаINTERVAL '... days', а вmake_interval(days => $1)он подставляется как обычный аргумент. Точно так жеmake_timestamp(2024, 3, 15, ...)отдаёт типtimestampбез промежуточной строки, поэтому порядокMM/DDпротивDD/MMи нулевое дополнение перестают влиять на результат. Дальше разберём сборку даты, интервала и варианта с часовым поясом по очереди.Дата из числовых частей
make_timestampпринимает год, месяц, день, час, минуту и секунды как обычные числа и возвращаетtimestamp. Никакого парсинга строк — а значит, никаких сюрпризов с порядкомMM/DDпротивDD/MM.SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS ts; -- 2024-03-15 14:30:00Секунды — это
double precision, так что дробная часть тоже работает:make_timestamp(2024, 3, 15, 14, 30, 7.5). Есть и родственники:make_date(2024, 3, 15)для чистой даты иmake_time(14, 30, 0)для времени без даты.Сравните с типичной склейкой строк — она хрупкая и зависит от настроек сессии:
-- fragile: depends on DateStyle and zero-padding SELECT (y || '-' || m || '-' || d)::date FROM (SELECT 2024 y, 3 m, 15 d) s;Если
mравно3, а не03, илиDateStyleнастроен наDMY, такой код тихо вернёт не ту дату либо упадёт.make_timestampрешает обе проблемы разом.Интервал из именованных аргументов
make_intervalстроитintervalиз именованных полей:years,months,weeks,days,hours,mins,secs. Передавайте только то, что нужно, — остальное по умолчанию ноль.SELECT make_interval(days => 10, hours => 2) AS shipping_window; -- 10 days 02:00:00Главный выигрыш — параметризация. Когда срок задан переменным числом, его нельзя подставить в строковый литерал
INTERVAL, зато легко передать вmake_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';Число
14здесь может быть колонкой или плейсхолдером$1. Это ключевое отличие от строкового интервала:INTERVAL '$1 days'не работает, потому что плейсхолдер оказался бы внутри литерала, аmake_interval(days => $1)— корректно. Альтернатива со склейкой и кастом (($1 || ' days')::interval) тоже сработает, но именованные аргументы читаются яснее и не зависят от того, как локаль печатает число.-- 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 и часовой пояс
У
make_timestampесть версия с поясом —make_timestamptz. Дополнительный текстовый аргумент задаёт зону, в которой трактуются переданные числа; результат имеет типtimestamptz.SELECT make_timestamptz(2024, 3, 15, 14, 30, 0, 'Europe/Berlin') AS ts_tz; -- stored as UTC, shown in your session zoneБез последнего аргумента числа трактуются в часовом поясе текущей сессии (
TimeZone). Это удобно для дедлайнов, привязанных к конкретному региону:SELECT u.id, make_timestamptz(2024, 12, 31, 23, 59, 59, 'America/Sao_Paulo') AS cutoff FROM users u WHERE u.country = 'BR';Различия в других СУБД
make_timestamp,make_intervalи компания — это PostgreSQL. В других движках подход иной:MAKEDATE/MAKETIMEлибоSTR_TO_DATE('2024-03-15','%Y-%m-%d'). Интервалы задаются синтаксисомINTERVAL 10 DAY, а для переменного количества —INTERVAL n DAY, гдеnможет быть выражением.makeDateTime(2024, 3, 15, 14, 30, 0)иmakeDateTime64(...)для долей секунды; зона передаётся отдельным аргументом. Интервал собирается черезtoIntervalDay(n),toIntervalHour(n)и их сложение.При переносе между движками сходится не всё, и расходятся именно крайние значения.
make_timestampотдаётtimestampбез зоны,make_timestamptz—timestamptzв UTC, тогда какmakeDateTimeв ClickHouse иMAKEDATEв MySQL трактуют зону по-своему; дробные секунды в PostgreSQL живут в аргументеsecs, а в ClickHouse требуют отдельнойmakeDateTime64. Прежде чем полагаться на формулу, прогоните пограничные входы: 13-й месяц, 30 февраля, отрицательный аргумент интервала и пустую зону —make_*на них кидаетfield value out of range, а соседний движок может молча нормализовать.Если сборка даты влияет на ключ, биллинг или дедлайн, держите её в одном слое и описывайте рядом, в какой зоне трактуются числа: одно и то же
make_timestamptz(..., 'Europe/Berlin')иmake_timestamp(...)дают разные абсолютные моменты. И помните про индексы:make_interval(days => $1)поверх колонкиcreated_atвWHEREпревращает условие в вычисляемое и может закрыть путь к индексу — переносите сборку интервала на сторону константы или диапазона, а не оборачивайте в неё индексируемую колонку.Если код переносимый, изолируйте сборку в одном слое: на PostgreSQL — семейство
make_*, на остальных — родные функции (makeDateTime,STR_TO_DATE,INTERVAL n DAY). Принцип один: не клейте даты из строк, когда движок умеет собиратьtimestampиintervalпрямо из чисел.