CURRENT_DATE и CURRENT_TIME в SQL возвращают текущую дату и текущее время. Выглядят они необычно: без скобок, как будто это не функции, а специальные ключевые слова.
SELECT
CURRENT_DATE AS today,
CURRENT_TIME AS clock_time;
Результат может быть таким:
today | clock_time
-----------+----------------
2026-06-17 | 18:42:07.512+02
На практике рядом с ними почти всегда всплывает ещё одно значение — CURRENT_TIMESTAMP. Оно возвращает не просто дату или время, а полный момент: дату, время и часовой пояс.
Эти три значения легко перепутать:
CURRENT_DATE — только дата;
CURRENT_TIME — только время суток с часовым поясом;
CURRENT_TIMESTAMP — дата и время с часовым поясом.
Путаница кажется мелкой, но из неё рождаются неприятные ошибки: фильтр «за сегодня» захватывает не те строки, колонка created_at теряет время суток, а длинная транзакция после полуночи продолжает жить со «вчерашней» датой.
Разберём всё спокойно и по-человечески.
Это не обычные функции
В PostgreSQL CURRENT_DATE, CURRENT_TIME и CURRENT_TIMESTAMP пишутся без скобок.
Правильно:
SELECT
CURRENT_DATE,
CURRENT_TIME,
CURRENT_TIMESTAMP;
Не нужно писать так:
SELECT
CURRENT_DATE(),
CURRENT_TIME();
В обычной работе новичка это один из первых странных моментов: почти все функции в SQL вызываются со скобками, а эти значения — нет.
Причина в том, что это специальные значения SQL. Их можно воспринимать как встроенные «переменные текущего времени», которые база данных умеет подставлять сама.
Что именно возвращает каждое значение
Главная разница между ними — в типе результата.
| Значение |
Что возвращает |
Тип в PostgreSQL |
CURRENT_DATE |
текущую дату |
date |
CURRENT_TIME |
текущее время суток с поясом |
time with time zone |
CURRENT_TIMESTAMP |
текущую дату и время с поясом |
timestamp with time zone |
Посмотрим на примере:
SELECT
CURRENT_DATE AS current_date_value,
CURRENT_TIME AS current_time_value,
CURRENT_TIMESTAMP AS current_timestamp_value;
Результат может выглядеть так:
current_date_value | current_time_value | current_timestamp_value
-------------------+--------------------+-------------------------------
2026-06-17 | 18:42:07.512+02 | 2026-06-17 18:42:07.512+02
CURRENT_DATE отвечает на вопрос: «Какое сегодня число?»
CURRENT_TIME отвечает на вопрос: «Который сейчас час?»
CURRENT_TIMESTAMP отвечает на вопрос: «Какой сейчас полный момент времени?»
В реальных таблицах для событий чаще нужен именно CURRENT_TIMESTAMP, потому что событие обычно произошло не просто «17 июня», а в конкретный момент: 2026-06-17 18:42:07.
CURRENT_DATE: когда нужна только дата
CURRENT_DATE возвращает только дату: год, месяц и день.
Например:
SELECT CURRENT_DATE AS today;
Результат:
today
----------
2026-06-17
Время суток в этом значении отсутствует. Нет ни часов, ни минут, ни секунд.
Это удобно для вещей, где время действительно не важно:
- дата регистрации в отдельной колонке;
- дата отчёта;
- дата рождения;
- дата списания;
- календарный день события.
Например, можно создать таблицу регистраций, где дата заполняется автоматически:
CREATE TABLE signups (
user_id bigint PRIMARY KEY,
signed_on date NOT NULL DEFAULT CURRENT_DATE
);
Теперь при вставке строки можно не указывать signed_on:
INSERT INTO signups (user_id)
VALUES (101);
PostgreSQL сам подставит текущую дату.
Это хороший вариант, если вам важно только число в календаре. Но если нужно знать ещё и точное время регистрации, CURRENT_DATE уже не подходит.
CURRENT_TIMESTAMP: когда нужен полный момент
Для колонок вроде created_at, updated_at, paid_at, sent_at почти всегда нужен полный момент времени.
То есть не просто дата, а дата плюс время.
CREATE TABLE orders (
id bigint PRIMARY KEY,
amount numeric(10, 2) NOT NULL,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Теперь при создании заказа база сохранит момент вставки:
INSERT INTO orders (id, amount)
VALUES (1, 990.00);
В created_at попадёт значение вроде:
2026-06-17 18:42:07.512+02
Разница между CURRENT_DATE и CURRENT_TIMESTAMP здесь принципиальная.
Если поставить для created_at значение по умолчанию CURRENT_DATE, время суток потеряется:
CREATE TABLE bad_orders (
id bigint PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT CURRENT_DATE
);
Так делать обычно не стоит. База всё равно приведёт дату к типу timestamptz, но время будет началом суток. В аналитике потом может оказаться, что все заказы будто созданы ровно в полночь.
Для даты — CURRENT_DATE.
Для точного момента — CURRENT_TIMESTAMP.
CURRENT_TIME: только время суток
CURRENT_TIME возвращает время суток с часовым поясом.
SELECT CURRENT_TIME AS clock_time;
Результат:
clock_time
----------------
18:42:07.512+02
Здесь есть часы, минуты, секунды и смещение часового пояса. Но нет даты.
Это важное ограничение. Без даты нельзя надёжно понять, что было раньше или позже, если события пересекают полночь.
Например, смена началась в 23:00, а закончилась в 02:00. По одному только времени суток непонятно, это два часа ночи того же дня или уже следующего.
Поэтому CURRENT_TIME используют реже, чем кажется. Для бизнес-логики часто удобнее взять время из полного момента:
SELECT CURRENT_TIMESTAMP::time AS local_clock;
Так мы получаем обычное локальное время без часового пояса:
local_clock
--------------
18:42:07.512
Для условий вроде «сейчас рабочее время» такой вариант часто проще.
Пример: проверить рабочие часы
Допустим, бизнес считает рабочим временем промежуток с 09:00 до 18:00.
SELECT
CURRENT_TIMESTAMP::time BETWEEN TIME '09:00' AND TIME '18:00' AS is_work_time;
Результат:
is_work_time
------------
true
Здесь мы берём полный текущий момент, приводим его к типу time и сравниваем только время суток.
А вот для хранения события лучше всё равно сохранять полный timestamptz, а не одно время:
CREATE TABLE support_tickets (
id bigint PRIMARY KEY,
subject text NOT NULL,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Сохранить только время — всё равно что записать в журнале: «событие случилось в 14:30», но забыть день.
Как выбрать между CURRENT_DATE, CURRENT_TIME и CURRENT_TIMESTAMP
Самый простой способ выбрать — спросить себя, что именно вы хотите сохранить или сравнить.
| Задача |
Лучше выбрать |
| Нужно сегодняшнее число |
CURRENT_DATE |
| Нужно время суток |
CURRENT_TIME или CURRENT_TIMESTAMP::time |
| Нужно сохранить момент создания строки |
CURRENT_TIMESTAMP |
Нужно поле created_at |
CURRENT_TIMESTAMP |
Нужно поле signed_on или report_date |
CURRENT_DATE |
| Нужно отфильтровать строки за сегодня |
диапазон от CURRENT_DATE до завтрашнего дня |
| Нужно посчитать последние 30 дней |
CURRENT_DATE - INTERVAL '30 days' или CURRENT_TIMESTAMP - INTERVAL '30 days' |
В большинстве продуктовых таблиц created_at и updated_at должны быть timestamptz с DEFAULT CURRENT_TIMESTAMP.
А date подходит, когда время действительно не имеет смысла.
Строки за сегодня: правильный фильтр
Очень частая задача: выбрать пользователей, которые зарегистрировались сегодня.
Предположим, в таблице users есть колонка created_at типа timestamptz.
Хороший фильтр выглядит так:
SELECT
id,
email,
created_at
FROM users
WHERE created_at >= CURRENT_DATE
AND created_at < CURRENT_DATE + INTERVAL '1 day';
Это называется полуинтервал:
- нижняя граница включается;
- верхняя граница не включается.
То есть мы берём всё начиная с сегодняшней полуночи и строго до завтрашней полуночи.
Такой подход лучше, чем пытаться сравнивать дату через преобразование колонки.
Плохой вариант для большой таблицы:
SELECT
id,
email,
created_at
FROM users
WHERE created_at::date = CURRENT_DATE;
Он читается приятно, но может быть медленнее на больших данных. Почему?
Потому что мы оборачиваем колонку created_at в преобразование ::date. Если на created_at есть обычный индекс, базе сложнее использовать его напрямую. Ей приходится вычислять дату для строк, а не просто идти по диапазону значений.
Лучше писать так:
SELECT
id,
email,
created_at
FROM users
WHERE created_at >= CURRENT_DATE
AND created_at < CURRENT_DATE + INTERVAL '1 day';
Диапазон хорошо дружит с индексом по created_at.
Заказы за последние 30 дней
Для отчёта за последние 30 дней можно использовать арифметику дат.
SELECT
id,
user_id,
amount,
status,
created_at
FROM orders
WHERE status = 'paid'
AND created_at >= CURRENT_DATE - INTERVAL '30 days';
Такой запрос берёт оплаченные заказы, созданные начиная с даты 30 дней назад.
Если нужна точность до текущей секунды, используйте CURRENT_TIMESTAMP:
SELECT
id,
user_id,
amount,
status,
created_at
FROM orders
WHERE status = 'paid'
AND created_at >= CURRENT_TIMESTAMP - INTERVAL '30 days';
Разница тонкая, но важная.
CURRENT_DATE - INTERVAL '30 days' даёт начало календарного дня 30 дней назад.
CURRENT_TIMESTAMP - INTERVAL '30 days' даёт момент ровно 30 дней назад от текущего времени.
Например, если сейчас 2026-06-17 18:42, то:
CURRENT_DATE - INTERVAL '30 days' -> 2026-05-18 00:00
CURRENT_TIMESTAMP - INTERVAL '30 days' -> 2026-05-18 18:42
Для календарных отчётов чаще подходит CURRENT_DATE. Для точных скользящих окон — CURRENT_TIMESTAMP.
Почему CURRENT_DATE не меняется внутри транзакции
В PostgreSQL CURRENT_DATE, CURRENT_TIME и CURRENT_TIMESTAMP берут момент начала текущей транзакции.
Это значит: внутри одной транзакции они остаются стабильными.
BEGIN;
SELECT CURRENT_TIMESTAMP;
SELECT CURRENT_TIMESTAMP;
COMMIT;
Оба запроса внутри транзакции вернут один и тот же текущий момент с точки зрения транзакции.
На первый взгляд это странно: время-то в реальном мире идёт. Но для базы данных такое поведение полезно. Если один большой запрос вставляет тысячу строк, у всех строк будет одинаковое значение created_at, а не чуть-чуть разное время на каждой строке.
Например:
INSERT INTO events (name, created_at)
SELECT
name,
CURRENT_TIMESTAMP
FROM imported_events;
Все вставленные строки получат одну и ту же отметку времени.
Это не баг, а нормальное поведение.
Если вам нужно именно «реальное время прямо сейчас» в момент вызова, в PostgreSQL есть другие функции, например clock_timestamp(). Но для created_at чаще как раз нужна стабильность CURRENT_TIMESTAMP.
Полночь и длинные транзакции
Из-за стабильности внутри транзакции есть важная ловушка.
Представьте, транзакция началась в 23:59.
BEGIN;
SELECT CURRENT_DATE;
Результат:
2026-06-17
Потом наступила полночь, но транзакция всё ещё открыта.
SELECT CURRENT_DATE;
Результат всё равно будет:
2026-06-17
Хотя на календаре уже 2026-06-18.
Это особенно важно для долгих фоновых задач, миграций, импортов и больших транзакций. Если значение «сегодня» должно обновляться после полуночи, не держите транзакцию открытой слишком долго или используйте подходящие функции времени осознанно.
Часовой пояс: почему сегодня может быть разным
CURRENT_DATE зависит от часового пояса сессии.
Один и тот же абсолютный момент может быть разной календарной датой в разных часовых поясах.
Например, в одном поясе уже наступило 18 июня, а в другом ещё 17 июня.
В PostgreSQL часовой пояс сессии можно поменять:
SET TIME ZONE 'UTC';
SELECT CURRENT_DATE;
SET TIME ZONE 'Europe/Berlin';
SELECT CURRENT_DATE;
В некоторых ситуациях дата может отличаться.
Это важно для отчётов «за сегодня». Если продукт международный, нужно заранее решить, что значит «сегодня»:
- сегодня по UTC;
- сегодня по Москве;
- сегодня по часовому поясу пользователя;
- сегодня по часовому поясу компании.
Без этого отчёт может быть формально правильным, но бизнес будет видеть «съехавшие» сутки.
Аккуратный фильтр по дню в нужном часовом поясе
Допустим, все события хранятся в timestamptz, а отчёт нужно строить по дню в UTC.
Можно привести момент к нужному поясу и сравнивать с календарной датой:
SELECT
id,
created_at
FROM events
WHERE (created_at AT TIME ZONE 'UTC')::date = CURRENT_DATE;
Такой вариант понятен, но для больших таблиц снова может быть неидеален из-за преобразования колонки в WHERE.
Для производительности часто лучше заранее посчитать границы дня в нужном поясе и сравнить created_at диапазоном. Главное — не смешивать разные часовые пояса в одном условии.
Базовая идея такая: если отчёт «за сегодня» важен для бизнеса, явно фиксируйте часовой пояс, а не надейтесь на настройки подключения по умолчанию.
DEFAULT: что ставить в таблицах
Разберём типичные колонки.
Дата регистрации без времени
Если нужна только дата:
CREATE TABLE signups (
user_id bigint PRIMARY KEY,
signed_on date NOT NULL DEFAULT CURRENT_DATE
);
Хорошие имена для таких колонок:
signed_on;
report_date;
birth_date;
billing_date.
Суффикс _on часто намекает, что хранится дата без времени.
Момент создания строки
Если нужен точный момент:
CREATE TABLE orders (
id bigint PRIMARY KEY,
amount numeric(10, 2) NOT NULL,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Хорошие имена:
created_at;
updated_at;
paid_at;
sent_at;
deleted_at.
Суффикс _at обычно намекает, что хранится дата и время.
Это не строгое правило SQL, но хороший стиль схемы. Он помогает читать таблицы без лишних вопросов.
CURRENT_TIMESTAMP и now()
В PostgreSQL часто встречается функция now().
SELECT now();
По смыслу в PostgreSQL она соответствует CURRENT_TIMESTAMP: возвращает текущий момент на начало транзакции.
Поэтому такие варианты часто взаимозаменяемы:
CREATE TABLE messages (
id bigint PRIMARY KEY,
body text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE messages (
id bigint PRIMARY KEY,
body text NOT NULL,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Если хочется писать ближе к стандартному SQL, используйте CURRENT_TIMESTAMP.
Если вы работаете в PostgreSQL-проекте и команда привыкла к now(), это тоже нормальный вариант.
CURRENT_TIME и timetz: почему с ним осторожно
В PostgreSQL CURRENT_TIME возвращает тип time with time zone, или коротко timetz.
На бумаге это выглядит полезно: время суток плюс смещение часового пояса.
На практике timetz часто неудобен, потому что в нём нет даты. А без даты нельзя корректно учитывать многие вещи:
- переход через полночь;
- переход на летнее или зимнее время;
- длительность между двумя событиями;
- реальный порядок событий.
Например, само по себе значение 02:30+02 не говорит, в какой день оно произошло. А значит, для событий, логов, заказов и платежей оно почти всегда слишком бедное.
Если важен момент события — храните timestamptz.
Если важно только локальное время в расписании — часто достаточно time.
Например, расписание работы магазина:
CREATE TABLE store_hours (
store_id bigint NOT NULL,
opens_at time NOT NULL,
closes_at time NOT NULL
);
А событие создания заказа:
CREATE TABLE orders (
id bigint PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Не стоит хранить событие как один CURRENT_TIME: потом вы не поймёте, в какой день оно случилось.
CURRENT_DATE в арифметике
С датой удобно делать простую арифметику.
Завтра:
SELECT CURRENT_DATE + INTERVAL '1 day' AS tomorrow_start;
Неделю назад:
SELECT CURRENT_DATE - INTERVAL '7 days' AS week_ago;
Начало сегодняшнего дня:
SELECT CURRENT_DATE::timestamp AS today_start;
Начало завтрашнего дня:
SELECT (CURRENT_DATE + INTERVAL '1 day') AS tomorrow_start;
Это часто используется в фильтрах:
SELECT
id,
created_at
FROM orders
WHERE created_at >= CURRENT_DATE
AND created_at < CURRENT_DATE + INTERVAL '1 day';
Такой запрос читается почти как обычная фраза: «создано сегодня или позже, но раньше завтрашнего дня».
Граница BETWEEN: почему лучше полуинтервал
Иногда хочется написать так:
SELECT
id,
created_at
FROM orders
WHERE created_at BETWEEN CURRENT_DATE
AND CURRENT_DATE + INTERVAL '1 day';
Но для временных диапазонов лучше привыкнуть к форме:
SELECT
id,
created_at
FROM orders
WHERE created_at >= CURRENT_DATE
AND created_at < CURRENT_DATE + INTERVAL '1 day';
Почему?
BETWEEN включает обе границы. Значит, значение ровно в завтрашнюю полночь тоже попадёт в результат. А оно уже относится к следующему дню.
Полуинтервал >= и < аккуратнее:
- сегодняшняя полночь входит;
- все моменты сегодняшнего дня входят;
- завтрашняя полночь уже не входит.
Это маленькая привычка, которая спасает много отчётов.
MySQL: похожие значения и функции
В MySQL есть похожие значения и функции.
Например:
SELECT
CURRENT_DATE,
CURRENT_TIME,
CURRENT_TIMESTAMP;
Также часто используют функции:
SELECT
CURDATE(),
CURTIME(),
NOW();
Обычно:
CURDATE() возвращает текущую дату;
CURTIME() возвращает текущее время;
NOW() возвращает текущие дату и время.
Но есть отличие от PostgreSQL: в MySQL нет такого же типа timetz, как в PostgreSQL. Тип TIME не хранит часовой пояс как отдельную часть значения.
Если вы пишете переносимый SQL, лучше не строить логику вокруг timetz.
ClickHouse: today и now
В ClickHouse часто используют функции:
SELECT
today(),
now();
today() возвращает текущую дату.
now() возвращает текущие дату и время.
Также в ClickHouse поддерживается CURRENT_TIMESTAMP как привычный SQL-вариант.
Например:
SELECT
today() AS current_day,
now() AS current_moment;
Для аналитических запросов в ClickHouse обычно явно выбирают функцию под задачу: дата для дневных отчётов, полный момент для точного времени.
Сравнение PostgreSQL, MySQL и ClickHouse
| Задача |
PostgreSQL |
MySQL |
ClickHouse |
| Текущая дата |
CURRENT_DATE |
CURRENT_DATE или CURDATE() |
today() |
| Текущее время |
CURRENT_TIME |
CURRENT_TIME или CURTIME() |
обычно извлекают из now() |
| Текущий момент |
CURRENT_TIMESTAMP или now() |
CURRENT_TIMESTAMP или NOW() |
now() или CURRENT_TIMESTAMP |
| Тип с временем и поясом |
timestamptz |
зависит от типа колонки и настроек |
DateTime с настройками пояса |
Главная идея переносимости простая: CURRENT_DATE и CURRENT_TIMESTAMP понятны многим СУБД, но детали типов, часовых поясов и точности отличаются.
Частые ошибки
Поставить CURRENT_DATE в created_at
Плохо:
CREATE TABLE orders (
id bigint PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT CURRENT_DATE
);
Лучше:
CREATE TABLE orders (
id bigint PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);
created_at обычно должен хранить полный момент, а не только дату.
Фильтровать день через приведение колонки
Может быть медленно на большой таблице:
SELECT
id,
created_at
FROM orders
WHERE created_at::date = CURRENT_DATE;
Лучше диапазоном:
SELECT
id,
created_at
FROM orders
WHERE created_at >= CURRENT_DATE
AND created_at < CURRENT_DATE + INTERVAL '1 day';
Использовать BETWEEN для всего дня
Опасный вариант:
SELECT
id,
created_at
FROM orders
WHERE created_at BETWEEN CURRENT_DATE
AND CURRENT_DATE + INTERVAL '1 day';
Лучше:
SELECT
id,
created_at
FROM orders
WHERE created_at >= CURRENT_DATE
AND created_at < CURRENT_DATE + INTERVAL '1 day';
Так завтрашняя полночь не попадёт в сегодняшний отчёт.
Хранить событие как CURRENT_TIME
Плохо для событий:
CREATE TABLE events (
id bigint PRIMARY KEY,
created_time timetz NOT NULL DEFAULT CURRENT_TIME
);
Лучше:
CREATE TABLE events (
id bigint PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Время без даты редко достаточно для события.
Забыть про часовой пояс
Запрос «за сегодня» зависит от того, в каком часовом поясе база считает текущую дату.
SELECT CURRENT_DATE;
Для международных продуктов заранее решайте, чей день вы считаете: UTC, день пользователя или день бизнеса.
Главное из статьи
CURRENT_DATE, CURRENT_TIME и CURRENT_TIMESTAMP — специальные значения SQL для текущей даты и времени.
Их пишут без скобок:
SELECT
CURRENT_DATE,
CURRENT_TIME,
CURRENT_TIMESTAMP;
Разница такая:
CURRENT_DATE возвращает только дату;
CURRENT_TIME возвращает только время суток с часовым поясом;
CURRENT_TIMESTAMP возвращает полный момент: дату, время и часовой пояс.
Для колонок created_at, updated_at, paid_at обычно нужен CURRENT_TIMESTAMP.
Для колонок с одной календарной датой подходит CURRENT_DATE.
Для фильтра «за сегодня» лучше использовать диапазон:
SELECT
id,
created_at
FROM orders
WHERE created_at >= CURRENT_DATE
AND created_at < CURRENT_DATE + INTERVAL '1 day';
Главные тонкости:
- не ставьте
CURRENT_DATE туда, где нужно сохранить точное время;
- не оборачивайте большую индексированную колонку в
created_at::date, если можно написать диапазон;
- не забывайте про часовой пояс;
- внутри одной транзакции текущая дата и время стабильны;
CURRENT_TIME без даты редко подходит для хранения событий;
- для полного момента в PostgreSQL можно использовать
CURRENT_TIMESTAMP или now().
Если говорить совсем просто: CURRENT_DATE — это «какой сегодня день», CURRENT_TIME — «который сейчас час», а CURRENT_TIMESTAMP — «какой сейчас точный момент».
CURRENT_DATEиCURRENT_TIMEв SQL возвращают текущую дату и текущее время. Выглядят они необычно: без скобок, как будто это не функции, а специальные ключевые слова.SELECT CURRENT_DATE AS today, CURRENT_TIME AS clock_time;Результат может быть таким:
На практике рядом с ними почти всегда всплывает ещё одно значение —
CURRENT_TIMESTAMP. Оно возвращает не просто дату или время, а полный момент: дату, время и часовой пояс.Эти три значения легко перепутать:
CURRENT_DATE— только дата;CURRENT_TIME— только время суток с часовым поясом;CURRENT_TIMESTAMP— дата и время с часовым поясом.Путаница кажется мелкой, но из неё рождаются неприятные ошибки: фильтр «за сегодня» захватывает не те строки, колонка
created_atтеряет время суток, а длинная транзакция после полуночи продолжает жить со «вчерашней» датой.Разберём всё спокойно и по-человечески.
Это не обычные функции
В PostgreSQL
CURRENT_DATE,CURRENT_TIMEиCURRENT_TIMESTAMPпишутся без скобок.Правильно:
SELECT CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP;Не нужно писать так:
SELECT CURRENT_DATE(), CURRENT_TIME();В обычной работе новичка это один из первых странных моментов: почти все функции в SQL вызываются со скобками, а эти значения — нет.
Причина в том, что это специальные значения SQL. Их можно воспринимать как встроенные «переменные текущего времени», которые база данных умеет подставлять сама.
Что именно возвращает каждое значение
Главная разница между ними — в типе результата.
CURRENT_DATEdateCURRENT_TIMEtime with time zoneCURRENT_TIMESTAMPtimestamp with time zoneПосмотрим на примере:
SELECT CURRENT_DATE AS current_date_value, CURRENT_TIME AS current_time_value, CURRENT_TIMESTAMP AS current_timestamp_value;Результат может выглядеть так:
CURRENT_DATEотвечает на вопрос: «Какое сегодня число?»CURRENT_TIMEотвечает на вопрос: «Который сейчас час?»CURRENT_TIMESTAMPотвечает на вопрос: «Какой сейчас полный момент времени?»В реальных таблицах для событий чаще нужен именно
CURRENT_TIMESTAMP, потому что событие обычно произошло не просто «17 июня», а в конкретный момент:2026-06-17 18:42:07.CURRENT_DATE: когда нужна только дата
CURRENT_DATEвозвращает только дату: год, месяц и день.Например:
SELECT CURRENT_DATE AS today;Результат:
Время суток в этом значении отсутствует. Нет ни часов, ни минут, ни секунд.
Это удобно для вещей, где время действительно не важно:
Например, можно создать таблицу регистраций, где дата заполняется автоматически:
CREATE TABLE signups ( user_id bigint PRIMARY KEY, signed_on date NOT NULL DEFAULT CURRENT_DATE );Теперь при вставке строки можно не указывать
signed_on:INSERT INTO signups (user_id) VALUES (101);PostgreSQL сам подставит текущую дату.
Это хороший вариант, если вам важно только число в календаре. Но если нужно знать ещё и точное время регистрации,
CURRENT_DATEуже не подходит.CURRENT_TIMESTAMP: когда нужен полный момент
Для колонок вроде
created_at,updated_at,paid_at,sent_atпочти всегда нужен полный момент времени.То есть не просто дата, а дата плюс время.
CREATE TABLE orders ( id bigint PRIMARY KEY, amount numeric(10, 2) NOT NULL, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP );Теперь при создании заказа база сохранит момент вставки:
INSERT INTO orders (id, amount) VALUES (1, 990.00);В
created_atпопадёт значение вроде:Разница между
CURRENT_DATEиCURRENT_TIMESTAMPздесь принципиальная.Если поставить для
created_atзначение по умолчаниюCURRENT_DATE, время суток потеряется:CREATE TABLE bad_orders ( id bigint PRIMARY KEY, created_at timestamptz NOT NULL DEFAULT CURRENT_DATE );Так делать обычно не стоит. База всё равно приведёт дату к типу
timestamptz, но время будет началом суток. В аналитике потом может оказаться, что все заказы будто созданы ровно в полночь.Для даты —
CURRENT_DATE.Для точного момента —
CURRENT_TIMESTAMP.CURRENT_TIME: только время суток
CURRENT_TIMEвозвращает время суток с часовым поясом.SELECT CURRENT_TIME AS clock_time;Результат:
Здесь есть часы, минуты, секунды и смещение часового пояса. Но нет даты.
Это важное ограничение. Без даты нельзя надёжно понять, что было раньше или позже, если события пересекают полночь.
Например, смена началась в
23:00, а закончилась в02:00. По одному только времени суток непонятно, это два часа ночи того же дня или уже следующего.Поэтому
CURRENT_TIMEиспользуют реже, чем кажется. Для бизнес-логики часто удобнее взять время из полного момента:SELECT CURRENT_TIMESTAMP::time AS local_clock;Так мы получаем обычное локальное время без часового пояса:
Для условий вроде «сейчас рабочее время» такой вариант часто проще.
Пример: проверить рабочие часы
Допустим, бизнес считает рабочим временем промежуток с 09:00 до 18:00.
SELECT CURRENT_TIMESTAMP::time BETWEEN TIME '09:00' AND TIME '18:00' AS is_work_time;Результат:
Здесь мы берём полный текущий момент, приводим его к типу
timeи сравниваем только время суток.А вот для хранения события лучше всё равно сохранять полный
timestamptz, а не одно время:CREATE TABLE support_tickets ( id bigint PRIMARY KEY, subject text NOT NULL, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP );Сохранить только время — всё равно что записать в журнале: «событие случилось в 14:30», но забыть день.
Как выбрать между CURRENT_DATE, CURRENT_TIME и CURRENT_TIMESTAMP
Самый простой способ выбрать — спросить себя, что именно вы хотите сохранить или сравнить.
CURRENT_DATECURRENT_TIMEилиCURRENT_TIMESTAMP::timeCURRENT_TIMESTAMPcreated_atCURRENT_TIMESTAMPsigned_onилиreport_dateCURRENT_DATECURRENT_DATEдо завтрашнего дняCURRENT_DATE - INTERVAL '30 days'илиCURRENT_TIMESTAMP - INTERVAL '30 days'В большинстве продуктовых таблиц
created_atиupdated_atдолжны бытьtimestamptzсDEFAULT CURRENT_TIMESTAMP.А
dateподходит, когда время действительно не имеет смысла.Строки за сегодня: правильный фильтр
Очень частая задача: выбрать пользователей, которые зарегистрировались сегодня.
Предположим, в таблице
usersесть колонкаcreated_atтипаtimestamptz.Хороший фильтр выглядит так:
SELECT id, email, created_at FROM users WHERE created_at >= CURRENT_DATE AND created_at < CURRENT_DATE + INTERVAL '1 day';Это называется полуинтервал:
То есть мы берём всё начиная с сегодняшней полуночи и строго до завтрашней полуночи.
Такой подход лучше, чем пытаться сравнивать дату через преобразование колонки.
Плохой вариант для большой таблицы:
SELECT id, email, created_at FROM users WHERE created_at::date = CURRENT_DATE;Он читается приятно, но может быть медленнее на больших данных. Почему?
Потому что мы оборачиваем колонку
created_atв преобразование::date. Если наcreated_atесть обычный индекс, базе сложнее использовать его напрямую. Ей приходится вычислять дату для строк, а не просто идти по диапазону значений.Лучше писать так:
SELECT id, email, created_at FROM users WHERE created_at >= CURRENT_DATE AND created_at < CURRENT_DATE + INTERVAL '1 day';Диапазон хорошо дружит с индексом по
created_at.Заказы за последние 30 дней
Для отчёта за последние 30 дней можно использовать арифметику дат.
SELECT id, user_id, amount, status, created_at FROM orders WHERE status = 'paid' AND created_at >= CURRENT_DATE - INTERVAL '30 days';Такой запрос берёт оплаченные заказы, созданные начиная с даты 30 дней назад.
Если нужна точность до текущей секунды, используйте
CURRENT_TIMESTAMP:SELECT id, user_id, amount, status, created_at FROM orders WHERE status = 'paid' AND created_at >= CURRENT_TIMESTAMP - INTERVAL '30 days';Разница тонкая, но важная.
CURRENT_DATE - INTERVAL '30 days'даёт начало календарного дня 30 дней назад.CURRENT_TIMESTAMP - INTERVAL '30 days'даёт момент ровно 30 дней назад от текущего времени.Например, если сейчас
2026-06-17 18:42, то:Для календарных отчётов чаще подходит
CURRENT_DATE. Для точных скользящих окон —CURRENT_TIMESTAMP.Почему CURRENT_DATE не меняется внутри транзакции
В PostgreSQL
CURRENT_DATE,CURRENT_TIMEиCURRENT_TIMESTAMPберут момент начала текущей транзакции.Это значит: внутри одной транзакции они остаются стабильными.
BEGIN; SELECT CURRENT_TIMESTAMP; SELECT CURRENT_TIMESTAMP; COMMIT;Оба запроса внутри транзакции вернут один и тот же текущий момент с точки зрения транзакции.
На первый взгляд это странно: время-то в реальном мире идёт. Но для базы данных такое поведение полезно. Если один большой запрос вставляет тысячу строк, у всех строк будет одинаковое значение
created_at, а не чуть-чуть разное время на каждой строке.Например:
INSERT INTO events (name, created_at) SELECT name, CURRENT_TIMESTAMP FROM imported_events;Все вставленные строки получат одну и ту же отметку времени.
Это не баг, а нормальное поведение.
Если вам нужно именно «реальное время прямо сейчас» в момент вызова, в PostgreSQL есть другие функции, например
clock_timestamp(). Но дляcreated_atчаще как раз нужна стабильностьCURRENT_TIMESTAMP.Полночь и длинные транзакции
Из-за стабильности внутри транзакции есть важная ловушка.
Представьте, транзакция началась в
23:59.BEGIN; SELECT CURRENT_DATE;Результат:
Потом наступила полночь, но транзакция всё ещё открыта.
SELECT CURRENT_DATE;Результат всё равно будет:
Хотя на календаре уже
2026-06-18.Это особенно важно для долгих фоновых задач, миграций, импортов и больших транзакций. Если значение «сегодня» должно обновляться после полуночи, не держите транзакцию открытой слишком долго или используйте подходящие функции времени осознанно.
Часовой пояс: почему сегодня может быть разным
CURRENT_DATEзависит от часового пояса сессии.Один и тот же абсолютный момент может быть разной календарной датой в разных часовых поясах.
Например, в одном поясе уже наступило 18 июня, а в другом ещё 17 июня.
В PostgreSQL часовой пояс сессии можно поменять:
SET TIME ZONE 'UTC'; SELECT CURRENT_DATE;SET TIME ZONE 'Europe/Berlin'; SELECT CURRENT_DATE;В некоторых ситуациях дата может отличаться.
Это важно для отчётов «за сегодня». Если продукт международный, нужно заранее решить, что значит «сегодня»:
Без этого отчёт может быть формально правильным, но бизнес будет видеть «съехавшие» сутки.
Аккуратный фильтр по дню в нужном часовом поясе
Допустим, все события хранятся в
timestamptz, а отчёт нужно строить по дню в UTC.Можно привести момент к нужному поясу и сравнивать с календарной датой:
SELECT id, created_at FROM events WHERE (created_at AT TIME ZONE 'UTC')::date = CURRENT_DATE;Такой вариант понятен, но для больших таблиц снова может быть неидеален из-за преобразования колонки в
WHERE.Для производительности часто лучше заранее посчитать границы дня в нужном поясе и сравнить
created_atдиапазоном. Главное — не смешивать разные часовые пояса в одном условии.Базовая идея такая: если отчёт «за сегодня» важен для бизнеса, явно фиксируйте часовой пояс, а не надейтесь на настройки подключения по умолчанию.
DEFAULT: что ставить в таблицах
Разберём типичные колонки.
Дата регистрации без времени
Если нужна только дата:
CREATE TABLE signups ( user_id bigint PRIMARY KEY, signed_on date NOT NULL DEFAULT CURRENT_DATE );Хорошие имена для таких колонок:
signed_on;report_date;birth_date;billing_date.Суффикс
_onчасто намекает, что хранится дата без времени.Момент создания строки
Если нужен точный момент:
CREATE TABLE orders ( id bigint PRIMARY KEY, amount numeric(10, 2) NOT NULL, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP );Хорошие имена:
created_at;updated_at;paid_at;sent_at;deleted_at.Суффикс
_atобычно намекает, что хранится дата и время.Это не строгое правило SQL, но хороший стиль схемы. Он помогает читать таблицы без лишних вопросов.
CURRENT_TIMESTAMP и now()
В PostgreSQL часто встречается функция
now().SELECT now();По смыслу в PostgreSQL она соответствует
CURRENT_TIMESTAMP: возвращает текущий момент на начало транзакции.Поэтому такие варианты часто взаимозаменяемы:
CREATE TABLE messages ( id bigint PRIMARY KEY, body text NOT NULL, created_at timestamptz NOT NULL DEFAULT now() );CREATE TABLE messages ( id bigint PRIMARY KEY, body text NOT NULL, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP );Если хочется писать ближе к стандартному SQL, используйте
CURRENT_TIMESTAMP.Если вы работаете в PostgreSQL-проекте и команда привыкла к
now(), это тоже нормальный вариант.CURRENT_TIME и timetz: почему с ним осторожно
В PostgreSQL
CURRENT_TIMEвозвращает типtime with time zone, или короткоtimetz.На бумаге это выглядит полезно: время суток плюс смещение часового пояса.
На практике
timetzчасто неудобен, потому что в нём нет даты. А без даты нельзя корректно учитывать многие вещи:Например, само по себе значение
02:30+02не говорит, в какой день оно произошло. А значит, для событий, логов, заказов и платежей оно почти всегда слишком бедное.Если важен момент события — храните
timestamptz.Если важно только локальное время в расписании — часто достаточно
time.Например, расписание работы магазина:
CREATE TABLE store_hours ( store_id bigint NOT NULL, opens_at time NOT NULL, closes_at time NOT NULL );А событие создания заказа:
CREATE TABLE orders ( id bigint PRIMARY KEY, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP );Не стоит хранить событие как один
CURRENT_TIME: потом вы не поймёте, в какой день оно случилось.CURRENT_DATE в арифметике
С датой удобно делать простую арифметику.
Завтра:
SELECT CURRENT_DATE + INTERVAL '1 day' AS tomorrow_start;Неделю назад:
SELECT CURRENT_DATE - INTERVAL '7 days' AS week_ago;Начало сегодняшнего дня:
SELECT CURRENT_DATE::timestamp AS today_start;Начало завтрашнего дня:
SELECT (CURRENT_DATE + INTERVAL '1 day') AS tomorrow_start;Это часто используется в фильтрах:
SELECT id, created_at FROM orders WHERE created_at >= CURRENT_DATE AND created_at < CURRENT_DATE + INTERVAL '1 day';Такой запрос читается почти как обычная фраза: «создано сегодня или позже, но раньше завтрашнего дня».
Граница BETWEEN: почему лучше полуинтервал
Иногда хочется написать так:
SELECT id, created_at FROM orders WHERE created_at BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '1 day';Но для временных диапазонов лучше привыкнуть к форме:
SELECT id, created_at FROM orders WHERE created_at >= CURRENT_DATE AND created_at < CURRENT_DATE + INTERVAL '1 day';Почему?
BETWEENвключает обе границы. Значит, значение ровно в завтрашнюю полночь тоже попадёт в результат. А оно уже относится к следующему дню.Полуинтервал
>=и<аккуратнее:Это маленькая привычка, которая спасает много отчётов.
MySQL: похожие значения и функции
В MySQL есть похожие значения и функции.
Например:
SELECT CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP;Также часто используют функции:
SELECT CURDATE(), CURTIME(), NOW();Обычно:
CURDATE()возвращает текущую дату;CURTIME()возвращает текущее время;NOW()возвращает текущие дату и время.Но есть отличие от PostgreSQL: в MySQL нет такого же типа
timetz, как в PostgreSQL. ТипTIMEне хранит часовой пояс как отдельную часть значения.Если вы пишете переносимый SQL, лучше не строить логику вокруг
timetz.ClickHouse: today и now
В ClickHouse часто используют функции:
SELECT today(), now();today()возвращает текущую дату.now()возвращает текущие дату и время.Также в ClickHouse поддерживается
CURRENT_TIMESTAMPкак привычный SQL-вариант.Например:
SELECT today() AS current_day, now() AS current_moment;Для аналитических запросов в ClickHouse обычно явно выбирают функцию под задачу: дата для дневных отчётов, полный момент для точного времени.
Сравнение PostgreSQL, MySQL и ClickHouse
CURRENT_DATECURRENT_DATEилиCURDATE()today()CURRENT_TIMECURRENT_TIMEилиCURTIME()now()CURRENT_TIMESTAMPилиnow()CURRENT_TIMESTAMPилиNOW()now()илиCURRENT_TIMESTAMPtimestamptzDateTimeс настройками поясаГлавная идея переносимости простая:
CURRENT_DATEиCURRENT_TIMESTAMPпонятны многим СУБД, но детали типов, часовых поясов и точности отличаются.Частые ошибки
Поставить CURRENT_DATE в created_at
Плохо:
CREATE TABLE orders ( id bigint PRIMARY KEY, created_at timestamptz NOT NULL DEFAULT CURRENT_DATE );Лучше:
CREATE TABLE orders ( id bigint PRIMARY KEY, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP );created_atобычно должен хранить полный момент, а не только дату.Фильтровать день через приведение колонки
Может быть медленно на большой таблице:
SELECT id, created_at FROM orders WHERE created_at::date = CURRENT_DATE;Лучше диапазоном:
SELECT id, created_at FROM orders WHERE created_at >= CURRENT_DATE AND created_at < CURRENT_DATE + INTERVAL '1 day';Использовать BETWEEN для всего дня
Опасный вариант:
SELECT id, created_at FROM orders WHERE created_at BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '1 day';Лучше:
SELECT id, created_at FROM orders WHERE created_at >= CURRENT_DATE AND created_at < CURRENT_DATE + INTERVAL '1 day';Так завтрашняя полночь не попадёт в сегодняшний отчёт.
Хранить событие как CURRENT_TIME
Плохо для событий:
CREATE TABLE events ( id bigint PRIMARY KEY, created_time timetz NOT NULL DEFAULT CURRENT_TIME );Лучше:
CREATE TABLE events ( id bigint PRIMARY KEY, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP );Время без даты редко достаточно для события.
Забыть про часовой пояс
Запрос «за сегодня» зависит от того, в каком часовом поясе база считает текущую дату.
SELECT CURRENT_DATE;Для международных продуктов заранее решайте, чей день вы считаете: UTC, день пользователя или день бизнеса.
Главное из статьи
CURRENT_DATE,CURRENT_TIMEиCURRENT_TIMESTAMP— специальные значения SQL для текущей даты и времени.Их пишут без скобок:
SELECT CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP;Разница такая:
CURRENT_DATEвозвращает только дату;CURRENT_TIMEвозвращает только время суток с часовым поясом;CURRENT_TIMESTAMPвозвращает полный момент: дату, время и часовой пояс.Для колонок
created_at,updated_at,paid_atобычно нуженCURRENT_TIMESTAMP.Для колонок с одной календарной датой подходит
CURRENT_DATE.Для фильтра «за сегодня» лучше использовать диапазон:
SELECT id, created_at FROM orders WHERE created_at >= CURRENT_DATE AND created_at < CURRENT_DATE + INTERVAL '1 day';Главные тонкости:
CURRENT_DATEтуда, где нужно сохранить точное время;created_at::date, если можно написать диапазон;CURRENT_TIMEбез даты редко подходит для хранения событий;CURRENT_TIMESTAMPилиnow().Если говорить совсем просто:
CURRENT_DATE— это «какой сегодня день»,CURRENT_TIME— «который сейчас час», аCURRENT_TIMESTAMP— «какой сейчас точный момент».