Think of date functions as the calendar tools in your SQL toolbox: they help you ask when, not just what. Interviewers love this because tiny time mistakes cause big bugs in reports, billing, and alerts.
Question: What are date functions in SQL, and how do you use them for filtering and reporting?
Answer: Date functions let you get the current date or timestamp, pull out parts like year or month, and do calendar math with intervals. They are also used to group rows into days or months, or to find records from today, this week, or last month. The key idea is to keep time handling consistent, especially around time zones.
Interview-Ready Answer: I use SQL date functions to read the current date or time, extract parts like year and month, and shift values with intervals. For filtering, I usually prefer a range such as created_at >= CURRENT_DATE and created_at < CURRENT_DATE + INTERVAL '1 day' because it is clearer and often index-friendly. In PostgreSQL, I also watch out for time zones, since a timestamp can fall on a different calendar day depending on how it is interpreted.
Date functions turn a stored date or timestamp into something useful for logic: a day number, a month bucket, a shifted deadline, or the current moment. The exact names vary by database, but the ideas are the same across SQL engines.
DATE '2024-02-29' or a timestamp literal and converts it into the database's internal format.CURRENT_DATE, EXTRACT, or date_trunc runs. Some values are fixed for the statement or transaction, while others are computed for every row.EXTRACT gives you a number, date_trunc snaps a timestamp down to a boundary like the start of the day, and interval arithmetic adds or subtracts calendar time.WHERE, the optimizer checks whether it can use an index. A predicate that wraps the column is often non-sargable (meaning the database cannot easily seek with an index).timestamptz or similar type, the session time zone can change which calendar day you see.| Function | Returns | Best for | Gotcha |
|---|---|---|---|
CURRENT_DATE | Date | Today's day only | No time part |
CURRENT_TIMESTAMP | Timestamp | Current moment | Includes time zone context |
EXTRACT(...) | Number | Year, month, day, hour | Not a date object |
date_trunc(...) | Timestamp | Bucketing to day/month | Often not ideal in WHERE |
| Intervals | Duration | Add/subtract time | Months vary in length |
Under the hood mindset: a date function is usually cheap by itself, about O(1) for one value. The expensive part is usually the scan. On a 50 million row table, created_at >= ... AND created_at < ... can let a B-tree index jump straight to the matching slice, while created_at::date = ... may force the engine to examine far more rows. That is why interviewers care about how you write the filter, not just whether it returns the right answer.
| Pattern | Use | Pros | Cons |
|---|---|---|---|
created_at::date = CURRENT_DATE | Quick readability | Short and easy | Can be timezone-sensitive and may hide an index |
created_at >= CURRENT_DATE AND created_at < CURRENT_DATE + INTERVAL '1 day' | Exact day filter | Precise and index-friendly | A little longer |
date_trunc('day', created_at) | Grouping by day | Great for buckets | Often less efficient in WHERE |
Practical rule: use functions to transform values for display or grouping, but use raw-column range predicates for filtering whenever possible. If you truly need the exact same expression often, consider an expression index such as one on (created_at::date) in PostgreSQL.
Two common traps are midnight and month boundaries. A timestamp that is tomorrow in UTC might still be today in New York, and adding one month to January 31 lands at the end of February, not on a neat 31st that does not exist. Those are normal calendar rules, not bugs in SQL.
Version and dialect note: this example uses PostgreSQL-style functions like date_trunc and INTERVAL. Other databases use different names, but the same ideas apply: current time, extract parts, bucket, and do calendar math.
Real-World Example: Imagine a checkout service that produces a daily revenue report. The team wants all payments made on a customer's local day, but the table stores timestamps in UTC. One developer writes WHERE paid_at::date = CURRENT_DATE and assumes that means 'today' everywhere. Around midnight, orders start landing in the wrong report because app servers, analysts, and customers are not all using the same time zone.
What goes wrong shows up fast: the dashboard is off by one day, finance sees revenue gaps, and support tickets mention missing receipts near midnight. Logs often reveal the pattern: rows around 23:30 UTC or 00:15 UTC appear in the wrong bucket. The fix is to choose one explicit time zone for reporting and use a half-open range on the raw timestamp, so the query is exact and index-friendly.
-- PostgreSQL example: show current-date/time tools, time-zone effects, and safer day filtering.
-- This script is runnable as-is in PostgreSQL.
CREATE TEMP TABLE event_log (
id integer PRIMARY KEY,
created_at timestamptz NOT NULL
);
INSERT INTO event_log (id, created_at) VALUES
(1, TIMESTAMPTZ '2024-02-29 23:30:00+00'),
(2, TIMESTAMPTZ '2024-03-01 00:15:00+00'),
(3, TIMESTAMPTZ '2024-03-15 12:00:00+00');
-- Make the time-zone effect visible.
SET TIME ZONE 'America/New_York';
-- Compare how the same instant can land on different calendar days.
SELECT
id,
created_at,
created_at::date AS session_date, -- local date in the current session time zone
(created_at AT TIME ZONE 'UTC')::date AS utc_date, -- explicit UTC date, independent of session time zone
date_trunc('day', created_at) AS day_bucket, -- useful for grouping by day
EXTRACT(MONTH FROM created_at) AS month_num,
created_at + INTERVAL '7 days' AS follow_up_at -- calendar arithmetic
FROM event_log
ORDER BY id;
-- Safer filtering for one UTC day: half-open range keeps the column "bare" so an index can help.
SELECT id, created_at
FROM event_log
WHERE created_at >= TIMESTAMPTZ '2024-02-29 00:00:00+00'
AND created_at < TIMESTAMPTZ '2024-03-01 00:00:00+00'
ORDER BY id;
-- Edge case / common mistake: casting in WHERE still works, but it can be less index-friendly
-- and depends on the session time zone for timestamptz values.
SELECT id, created_at
FROM event_log
WHERE created_at::date = DATE '2024-02-29'
ORDER BY id;
-- If you need month-end logic, remember that intervals are calendar-aware.
-- For example, adding one month to January 31 lands at the end of February.
SELECT
DATE '2024-01-31' + INTERVAL '1 month' AS jan31_plus_one_month,
DATE_TRUNC('month', DATE '2024-01-31') + INTERVAL '1 month' - INTERVAL '1 day' AS jan31_month_end;
Follow-up & Tricky Questions:
DATE, TIMESTAMP, and TIMESTAMPTZ? DATE stores only a calendar day, TIMESTAMP stores date plus time, and TIMESTAMPTZ stores a moment in time with time zone handling. For reporting across regions, TIMESTAMPTZ is usually safer.date_trunc('month', some_ts) gives the first instant of that month. If you only need a date, cast or convert the result appropriately.date_trunc('month', some_date) + INTERVAL '1 month' - INTERVAL '1 day'. This works because you jump to the next month and step back one day.column::date = ...? The range filter usually keeps the column raw, which lets an index on the timestamp help. The cast version is readable, but it may slow down large tables and can change meaning with time zones.CURRENT_TIMESTAMP and CLOCK_TIMESTAMP() in PostgreSQL? CURRENT_TIMESTAMP is fixed at the start of the transaction, while clock_timestamp() returns the actual wall-clock time at the moment it is called. That matters inside long transactions or slow queries.CURRENT_DATE change while a query is running? No, in PostgreSQL it is stable for the transaction, so one statement sees one consistent date. That stability is useful for repeatable reports.EXTRACT(MONTH FROM ts) a date? No, it returns a number, often a floating-point value in PostgreSQL. Use it for comparisons or grouping, not for storing a calendar date.BETWEEN safe for timestamp ranges? Only if you really want both endpoints included. For day filters, a half-open range like >= start and < next_start is safer because it avoids double-counting midnight.Tricky / gotcha questions:
created_at::date = CURRENT_DATE always mean the same day for all users? No. With timestamptz, the cast depends on the session time zone, so two users can see different dates for the same moment.date_trunc('day', created_at) good in a WHERE clause? It is fine for readability, but it often blocks index use on the original column. Use it for grouping, and use a range predicate for filtering.Common Mistakes:
WHERE. Fix: prefer a raw-column range predicate so the index can be used.BETWEEN for timestamp days. Fix: use a half-open range to avoid double-counting the end boundary.EXTRACT and date_trunc for logic, and TO_CHAR only for presentation.Memory Hook: Picture a train station clock: first choose the station time zone, then read the exact minute, then group trains into morning or afternoon bins. If you mix those steps up, you send the right train to the wrong platform.
Cheat Sheet:
CURRENT_DATE = today, no time part.CURRENT_TIMESTAMP = current moment.EXTRACT = pull out year, month, day, hour.date_trunc = snap to day/month boundary.>= start and < next_start.Practice Tasks: