In SQL, CURRENT_DATE and CURRENT_TIME are not functions but special values: they return today's date and the current time of day without a single parenthesis. It sounds trivial, but the missing () and the result type are exactly what trip people up.
Special values, no parentheses
The SQL standard defines a handful of "special registers": CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, LOCALTIME, LOCALTIMESTAMP. You write them as a bare keyword — never CURRENT_DATE().
SELECT CURRENT_DATE, CURRENT_TIME;
CURRENT_DATE yields a date — year, month and day only.
CURRENT_TIME yields time with time zone (timetz for short) — a time of day plus a zone offset.
CURRENT_TIMESTAMP combines both: full date and time with zone (timestamptz).
All of them read the moment the transaction started, not the moment the row executes. Inside one transaction CURRENT_DATE will not "jump" past midnight between queries — that is standard behavior, not a bug.
Today in WHERE and reports
The most common use is selecting rows from today. You can compare created_at (a timestamptz) directly against CURRENT_DATE, but mind the boundaries: prefer a half-open range [today, tomorrow).
SELECT id, email, created_at
FROM users
WHERE created_at >= CURRENT_DATE
AND created_at < CURRENT_DATE + INTERVAL '1 day';
Orders from the last 30 days — arithmetic on CURRENT_DATE reads cleanly, no casts needed:
SELECT id, user_id, amount, status
FROM orders
WHERE status = 'paid'
AND created_at >= CURRENT_DATE - INTERVAL '30 days';
Gotcha: do not write DATE(created_at) = CURRENT_DATE over a large set. Wrapping DATE(...) around the column kills any index on created_at — the planner cannot use it and falls back to a seq scan. The >= ... AND < ... range keeps the index in play.
CURRENT_DATE as a DEFAULT
CURRENT_DATE is a perfect default for a date column: it is evaluated on every insert.
CREATE TABLE signups (
user_id bigint PRIMARY KEY,
signed_on date NOT NULL DEFAULT CURRENT_DATE
);
INSERT INTO signups (user_id) VALUES (101);
For "created at" fields you usually reach for CURRENT_TIMESTAMP (or now()) instead, to keep the time and zone too:
ALTER TABLE orders
ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP;
The key difference: CURRENT_DATE stores only the day and discards the time of day, while CURRENT_TIMESTAMP keeps the full moment. Pick the column type deliberately — date versus timestamptz.
CURRENT_TIME versus a full timestamp
CURRENT_TIME is the time of day, with no date attached. The timetz type carries a zone offset but not a date, and that is exactly where it gets error-prone.
SELECT CURRENT_TIME AS clock,
CURRENT_TIMESTAMP AS full_moment,
CURRENT_TIMESTAMP::time AS local_clock;
- You cannot reliably subtract one
timetz from another across a zone boundary — without a date, crossing midnight is ambiguous.
- For "business hours" logic you normally take
CURRENT_TIMESTAMP::time (a plain time, no zone), not CURRENT_TIME.
- When you need both date and time, the right answer is almost always
CURRENT_TIMESTAMP, not a CURRENT_DATE + CURRENT_TIME pair.
Example: count an order as daytime when it was created between 09:00 and 18:00 local time.
SELECT id, amount
FROM orders
WHERE created_at::time BETWEEN TIME '09:00' AND TIME '18:00';
Differences across engines
- PostgreSQL: fully standard.
CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP take no parentheses; now() is a synonym for CURRENT_TIMESTAMP.
- MySQL:
CURRENT_DATE and CURDATE() are equivalent, as are CURRENT_TIME and CURTIME(). MySQL's TIME type stores no zone, so there is no timetz here at all.
- ClickHouse: offers
today() and now(); CURRENT_TIMESTAMP is supported as an alias, but there is no standalone "time of day with zone" type.
If the code must be portable, stick to the parenthesis-free CURRENT_DATE/CURRENT_TIMESTAMP — the most widely understood form — and do not lean on timetz semantics outside PostgreSQL.
In SQL,
CURRENT_DATEandCURRENT_TIMEare not functions but special values: they return today's date and the current time of day without a single parenthesis. It sounds trivial, but the missing()and the result type are exactly what trip people up.Special values, no parentheses
The SQL standard defines a handful of "special registers":
CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,LOCALTIME,LOCALTIMESTAMP. You write them as a bare keyword — neverCURRENT_DATE().SELECT CURRENT_DATE, CURRENT_TIME; -- 2026-06-17 | 18:42:07.512+02CURRENT_DATEyields adate— year, month and day only.CURRENT_TIMEyieldstime with time zone(timetzfor short) — a time of day plus a zone offset.CURRENT_TIMESTAMPcombines both: full date and time with zone (timestamptz).All of them read the moment the transaction started, not the moment the row executes. Inside one transaction
CURRENT_DATEwill not "jump" past midnight between queries — that is standard behavior, not a bug.Today in WHERE and reports
The most common use is selecting rows from today. You can compare
created_at(atimestamptz) directly againstCURRENT_DATE, but mind the boundaries: prefer a half-open range[today, tomorrow).SELECT id, email, created_at FROM users WHERE created_at >= CURRENT_DATE AND created_at < CURRENT_DATE + INTERVAL '1 day';Orders from the last 30 days — arithmetic on
CURRENT_DATEreads cleanly, no casts needed:SELECT id, user_id, amount, status FROM orders WHERE status = 'paid' AND created_at >= CURRENT_DATE - INTERVAL '30 days';CURRENT_DATE as a DEFAULT
CURRENT_DATEis a perfect default for a date column: it is evaluated on every insert.CREATE TABLE signups ( user_id bigint PRIMARY KEY, signed_on date NOT NULL DEFAULT CURRENT_DATE ); INSERT INTO signups (user_id) VALUES (101); -- signed_on = today, filled automaticallyFor "created at" fields you usually reach for
CURRENT_TIMESTAMP(ornow()) instead, to keep the time and zone too:ALTER TABLE orders ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP;The key difference:
CURRENT_DATEstores only the day and discards the time of day, whileCURRENT_TIMESTAMPkeeps the full moment. Pick the column type deliberately —dateversustimestamptz.CURRENT_TIME versus a full timestamp
CURRENT_TIMEis the time of day, with no date attached. Thetimetztype carries a zone offset but not a date, and that is exactly where it gets error-prone.SELECT CURRENT_TIME AS clock, -- 18:42:07.512+02 (timetz) CURRENT_TIMESTAMP AS full_moment, -- 2026-06-17 18:42:07.512+02 CURRENT_TIMESTAMP::time AS local_clock; -- 18:42:07.512 (plain time)timetzfrom another across a zone boundary — without a date, crossing midnight is ambiguous.CURRENT_TIMESTAMP::time(a plaintime, no zone), notCURRENT_TIME.CURRENT_TIMESTAMP, not aCURRENT_DATE+CURRENT_TIMEpair.Example: count an order as daytime when it was created between 09:00 and 18:00 local time.
SELECT id, amount FROM orders WHERE created_at::time BETWEEN TIME '09:00' AND TIME '18:00';Differences across engines
CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMPtake no parentheses;now()is a synonym forCURRENT_TIMESTAMP.CURRENT_DATEandCURDATE()are equivalent, as areCURRENT_TIMEandCURTIME(). MySQL'sTIMEtype stores no zone, so there is notimetzhere at all.today()andnow();CURRENT_TIMESTAMPis supported as an alias, but there is no standalone "time of day with zone" type.If the code must be portable, stick to the parenthesis-free
CURRENT_DATE/CURRENT_TIMESTAMP— the most widely understood form — and do not lean ontimetzsemantics outside PostgreSQL.