[{"data":1,"prerenderedAt":107},["ShallowReactive",2],{"qa-\u002Fsql\u002Ffunctions\u002Fdate-functions":3},{"page":4,"siblings":94,"blog":104},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":12,"path":20,"questions":21,"questionsCount":84,"related":85,"seo":86,"seoDescription":87,"stem":88,"subtopic":89,"topic":90,"topicSlug":91,"updated":92,"__hash__":93},"qa\u002Fsql\u002Ffunctions\u002Fdate-functions.md","Date Functions",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","SQL","sql",{},true,"\u002Fsql\u002Ffunctions\u002Fdate-functions",[22,27,31,35,39,43,47,51,56,60,64,68,72,76,80],{"id":23,"difficulty":24,"q":25,"a":26},"current-date-time","easy","How do you get the current date and time in SQL?","Each database provides standard and proprietary functions for the current\ntimestamp.\n\n```sql\n-- ANSI standard (all databases)\nSELECT CURRENT_DATE;        -- date only: '2026-06-20'\nSELECT CURRENT_TIME;        -- time only: '14:30:00.000000+00'\nSELECT CURRENT_TIMESTAMP;   -- date + time: '2026-06-20 14:30:00.000000+00'\n\n-- Postgres: preferred shorthands\nSELECT now();               -- same as CURRENT_TIMESTAMP (tz-aware)\nSELECT CURRENT_DATE;\nSELECT CURRENT_TIME;\n\n-- MySQL\nSELECT NOW();               -- '2026-06-20 14:30:00'\nSELECT CURDATE();           -- '2026-06-20'\nSELECT CURTIME();           -- '14:30:00'\nSELECT UTC_TIMESTAMP();     -- always UTC\n\n-- SQL Server\nSELECT GETDATE();           -- local server time\nSELECT GETUTCDATE();        -- UTC\nSELECT SYSDATETIMEOFFSET(); -- with timezone offset\n```\n\n**Rule of thumb:** always store and compare timestamps in UTC. In Postgres,\nuse `now()` (returns `TIMESTAMPTZ` in UTC) rather than `LOCALTIMESTAMP`\n(returns naive timestamp in the server timezone).\n",{"id":28,"difficulty":24,"q":29,"a":30},"date-arithmetic","How do you add or subtract time from a date?","Date arithmetic is done with intervals or database-specific functions.\n\n```sql\n-- Postgres: interval arithmetic\nSELECT now() + INTERVAL '7 days';             -- 7 days from now\nSELECT now() - INTERVAL '1 month';            -- 1 month ago\nSELECT '2026-06-20'::DATE + INTERVAL '1 year 3 months';\nSELECT '2026-06-20'::DATE - '2026-01-01'::DATE;  -- → 170 (days as integer)\n\n-- MySQL: DATE_ADD \u002F DATE_SUB\nSELECT DATE_ADD(NOW(), INTERVAL 7 DAY);\nSELECT DATE_SUB(NOW(), INTERVAL 1 MONTH);\nSELECT DATEDIFF('2026-06-20', '2026-01-01');  -- → 170\n\n-- SQL Server: DATEADD \u002F DATEDIFF\nSELECT DATEADD(day, 7, GETDATE());\nSELECT DATEADD(month, -1, GETDATE());\nSELECT DATEDIFF(day, '2026-01-01', '2026-06-20');  -- → 170\n```\n\n**Rule of thumb:** in Postgres, prefer the `+`\u002F`-` operator with `INTERVAL`\nliterals — it is readable and handles month-end edge cases correctly\n(`'2026-01-31'::DATE + INTERVAL '1 month'` → `'2026-02-28'`).\n",{"id":32,"difficulty":14,"q":33,"a":34},"date-trunc","What does DATE_TRUNC do and how is it used for grouping?","`DATE_TRUNC(unit, timestamp)` truncates a timestamp to the specified\nprecision — zeroing out all smaller units. This is the standard way to\ngroup time-series data by day, week, month, etc.\n\n```sql\n-- Postgres DATE_TRUNC\nSELECT DATE_TRUNC('month', now());           -- → '2026-06-01 00:00:00+00'\nSELECT DATE_TRUNC('week',  now());           -- → '2026-06-16 00:00:00+00' (Monday)\nSELECT DATE_TRUNC('hour',  now());           -- → '2026-06-20 14:00:00+00'\n\n-- Group orders by month\nSELECT DATE_TRUNC('month', created_at) AS month,\n       SUM(total)                      AS revenue,\n       COUNT(*)                        AS orders\nFROM   orders\nGROUP  BY 1\nORDER  BY 1;\n\n-- MySQL equivalent: DATE_FORMAT\nSELECT DATE_FORMAT(created_at, '%Y-%m-01') AS month,\n       SUM(total) AS revenue\nFROM   orders\nGROUP  BY 1;\n\n-- SQL Server: DATETRUNC (SQL Server 2022+) or DATEFROMPARTS workaround\nSELECT DATEFROMPARTS(YEAR(created_at), MONTH(created_at), 1) AS month,\n       SUM(total) AS revenue\nFROM   orders\nGROUP  BY DATEFROMPARTS(YEAR(created_at), MONTH(created_at), 1);\n```\n\n**Rule of thumb:** `DATE_TRUNC` is the idiomatic Postgres way to bucket\ntime-series data. Pair it with an index on the timestamp column for\nefficient range scans per bucket.\n",{"id":36,"difficulty":24,"q":37,"a":38},"extract","How do you extract a specific part of a date (year, month, day)?","`EXTRACT` (standard SQL) and database-specific functions pull individual\ncomponents out of a date or timestamp.\n\n```sql\n-- ANSI EXTRACT (all databases)\nSELECT EXTRACT(YEAR  FROM now());   -- → 2026\nSELECT EXTRACT(MONTH FROM now());   -- → 6\nSELECT EXTRACT(DAY   FROM now());   -- → 20\nSELECT EXTRACT(DOW   FROM now());   -- → 6 (0=Sunday … 6=Saturday, Postgres)\nSELECT EXTRACT(WEEK  FROM now());   -- → ISO week number\n\n-- Postgres shorthand\nSELECT DATE_PART('year', now());    -- equivalent to EXTRACT\n\n-- MySQL\nSELECT YEAR(now());    -- → 2026\nSELECT MONTH(now());   -- → 6\nSELECT DAY(now());     -- → 20\nSELECT DAYOFWEEK(now()); -- 1=Sunday … 7=Saturday\n\n-- SQL Server\nSELECT YEAR(GETDATE());\nSELECT DATEPART(WEEKDAY, GETDATE());\n```\n\n**Rule of thumb:** use `EXTRACT` for portable code. In Postgres, `DATE_PART`\nis equivalent but returns `DOUBLE PRECISION` — use `EXTRACT` when you need\nan integer result for arithmetic.\n",{"id":40,"difficulty":14,"q":41,"a":42},"age-datediff","How do you calculate the difference between two dates?","```sql\n-- Postgres: AGE() returns a human-readable interval\nSELECT AGE('2026-06-20', '1993-03-15');\n-- → '33 years 3 mons 5 days'\n\nSELECT AGE(now(), birth_date) FROM users;\n-- → age as interval; use EXTRACT to get the years component\nSELECT EXTRACT(YEAR FROM AGE(now(), birth_date)) AS age_years FROM users;\n\n-- Postgres: simple subtraction → interval (days)\nSELECT '2026-06-20'::DATE - '2026-01-01'::DATE;  -- → 170 (integer days)\n\n-- MySQL\nSELECT DATEDIFF('2026-06-20', '2026-01-01');      -- → 170 (days)\nSELECT TIMESTAMPDIFF(YEAR, birth_date, NOW())  AS age FROM users;\nSELECT TIMESTAMPDIFF(MONTH, '2026-01-01', '2026-06-20');  -- → 5 months\n\n-- SQL Server\nSELECT DATEDIFF(day,  '2026-01-01', '2026-06-20');  -- → 170\nSELECT DATEDIFF(year, birth_date, GETDATE()) AS age FROM users;\n```\n\n**Rule of thumb:** use `TIMESTAMPDIFF` (MySQL) or `DATEDIFF` (SQL Server)\nfor simple numeric differences. In Postgres, subtract dates directly for\ninteger days; use `AGE()` when you need a human-readable breakdown.\n",{"id":44,"difficulty":14,"q":45,"a":46},"timezone-handling","How do you handle timezones when storing and querying timestamps?","The fundamental rule: **store in UTC, display in the user's timezone**.\nUse timezone-aware column types so conversions are unambiguous.\n\n```sql\n-- Postgres: TIMESTAMPTZ stores in UTC; AT TIME ZONE converts for display\nCREATE TABLE events (\n  id         BIGSERIAL PRIMARY KEY,\n  occurred   TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\n-- Convert to a specific timezone for display\nSELECT occurred AT TIME ZONE 'America\u002FNew_York' AS local_time FROM events;\nSELECT occurred AT TIME ZONE 'Asia\u002FKolkata'     AS ist_time   FROM events;\n\n-- Set the session timezone (affects display, not storage)\nSET TIME ZONE 'America\u002FNew_York';\nSELECT now();  -- displayed in ET, still stored as UTC internally\n\n-- MySQL: CONVERT_TZ\nSELECT CONVERT_TZ(occurred, 'UTC', 'America\u002FNew_York') FROM events;\n\n-- SQL Server: AT TIME ZONE (SQL Server 2016+)\nSELECT occurred AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time' FROM events;\n```\n\n**Rule of thumb:** always use `TIMESTAMPTZ` (Postgres) or an equivalent\nUTC-storing type. Never store local times without an explicit timezone\noffset — daylight-saving transitions will corrupt historical data.\n",{"id":48,"difficulty":24,"q":49,"a":50},"date-format","How do you format a date as a string?","```sql\n-- Postgres: TO_CHAR\nSELECT TO_CHAR(now(), 'YYYY-MM-DD');            -- → '2026-06-20'\nSELECT TO_CHAR(now(), 'DD\u002FMM\u002FYYYY');            -- → '20\u002F06\u002F2026'\nSELECT TO_CHAR(now(), 'Month DD, YYYY');        -- → 'June     20, 2026'\nSELECT TO_CHAR(now(), 'FMMonth DD, YYYY');      -- → 'June 20, 2026' (FM removes padding)\nSELECT TO_CHAR(now(), 'YYYY-MM-DD HH24:MI:SS'); -- → '2026-06-20 14:30:00'\n\n-- MySQL: DATE_FORMAT\nSELECT DATE_FORMAT(NOW(), '%Y-%m-%d');          -- → '2026-06-20'\nSELECT DATE_FORMAT(NOW(), '%d\u002F%m\u002F%Y %H:%i:%s'); -- → '20\u002F06\u002F2026 14:30:00'\n\n-- SQL Server: FORMAT \u002F CONVERT\nSELECT FORMAT(GETDATE(), 'yyyy-MM-dd');         -- → '2026-06-20'\nSELECT CONVERT(VARCHAR, GETDATE(), 23);         -- → '2026-06-20' (style 23)\n```\n\n**Rule of thumb:** format dates as strings in the application layer when\npossible, since formatting functions are not portable and locale handling\nvaries. Use `TO_CHAR` in SQL only for exports or reports generated entirely\nin the database.\n",{"id":52,"difficulty":53,"q":54,"a":55},"generate-series","hard","How do you generate a date series to fill gaps in time-series data?","`GENERATE_SERIES` (Postgres) produces a set of dates or timestamps, making\nit easy to build a complete date spine and `LEFT JOIN` to fill in zero\nvalues for missing days.\n\n```sql\n-- Postgres: daily date spine for June 2026\nSELECT generate_series(\n  '2026-06-01'::DATE,\n  '2026-06-30'::DATE,\n  INTERVAL '1 day'\n) AS day;\n\n-- Fill gaps in daily revenue (days with no orders show 0)\nSELECT day::DATE,\n       COALESCE(SUM(o.total), 0) AS revenue\nFROM generate_series('2026-06-01'::DATE, '2026-06-30'::DATE, '1 day') AS day\nLEFT JOIN orders o ON o.created_at::DATE = day::DATE\nGROUP BY 1\nORDER BY 1;\n```\n\nMySQL and SQL Server do not have `GENERATE_SERIES` natively; the common\nworkaround is a **numbers\u002Fcalendar table** or a recursive CTE.\n\n**Rule of thumb:** always use a date spine (`GENERATE_SERIES` or a calendar\ntable) when charting time-series data — direct `GROUP BY` on timestamps\nsilently omits days with no records, creating misleading gaps in charts.\n",{"id":57,"difficulty":14,"q":58,"a":59},"interval-type","What is the INTERVAL type and how do you use it?","An **interval** represents a duration (not a point in time). Postgres has\na rich `INTERVAL` type; MySQL and SQL Server use numeric + unit keywords\ninstead.\n\n```sql\n-- Postgres INTERVAL literals\nSELECT INTERVAL '1 year 2 months 3 days';\nSELECT INTERVAL '90 minutes';\nSELECT INTERVAL '2 hours 30 minutes';\n\n-- Arithmetic with intervals\nSELECT now() - INTERVAL '1 week';\nSELECT now() + INTERVAL '90 days';\n\n-- Storing intervals (Postgres)\nCREATE TABLE subscriptions (\n  id         SERIAL PRIMARY KEY,\n  started_at TIMESTAMPTZ NOT NULL,\n  duration   INTERVAL NOT NULL DEFAULT '1 month'\n);\nSELECT started_at + duration AS expires_at FROM subscriptions;\n\n-- Extract numeric parts from an interval\nSELECT EXTRACT(DAYS FROM INTERVAL '1 year 3 days');    -- → 3 (only the days part)\nSELECT EXTRACT(EPOCH FROM INTERVAL '2 hours');         -- → 7200 (total seconds)\n```\n\n**Rule of thumb:** use `EXTRACT(EPOCH FROM interval_col)` to convert an\ninterval to a total number of seconds for arithmetic or comparison — it is\nthe most reliable way to compare durations of mixed units.\n",{"id":61,"difficulty":53,"q":62,"a":63},"now-vs-clock-timestamp","What is the difference between now() and clock_timestamp() in Postgres?","In Postgres, `now()` (and `CURRENT_TIMESTAMP`) returns the timestamp at\nthe **start of the current transaction** — it does not change within the\nsame transaction. `clock_timestamp()` returns the **actual wall-clock time**\nat the moment it is called.\n\n```sql\nBEGIN;\n  SELECT now();             -- → '2026-06-20 14:30:00.000'\n  PERFORM pg_sleep(2);\n  SELECT now();             -- → '2026-06-20 14:30:00.000'  (same! start of txn)\n  SELECT clock_timestamp(); -- → '2026-06-20 14:30:02.005'  (actual time now)\nCOMMIT;\n```\n\nThis matters for:\n- **Audit columns**: `DEFAULT now()` is fine — all rows in the same\n  transaction get the same \"created_at\", which is expected.\n- **Benchmarking \u002F profiling**: use `clock_timestamp()` to measure elapsed\n  time within a transaction.\n- **Rate limiting**: if checking \"last request time\" within a long transaction,\n  use `clock_timestamp()` to get the real current time.\n\n**Rule of thumb:** use `now()` for audit timestamps (consistent within a\ntransaction is a feature, not a bug). Use `clock_timestamp()` when you need\nthe actual wall time, such as for elapsed-time calculations or timeouts\nchecked inside a transaction.\n",{"id":65,"difficulty":53,"q":66,"a":67},"date-trunc-index","How do you efficiently query by time period without slowing down writes?","Range queries on timestamp columns are common (`WHERE created_at >= X AND\ncreated_at \u003C Y`). The key to making them fast is a **B-tree index on the\nraw timestamp**, combined with using range predicates rather than wrapping\nthe column in a function.\n\n```sql\n-- GOOD: range predicate — index on created_at is used\nCREATE INDEX idx_orders_created ON orders (created_at);\n\nSELECT * FROM orders\nWHERE created_at >= '2026-06-01'\n  AND created_at \u003C  '2026-07-01';\n\n-- BAD: function applied to the column — index NOT used\nSELECT * FROM orders WHERE DATE_TRUNC('month', created_at) = '2026-06-01';\n\n-- Fix: use a range instead of DATE_TRUNC in the WHERE clause\nSELECT * FROM orders\nWHERE created_at >= DATE_TRUNC('month', now())\n  AND created_at \u003C  DATE_TRUNC('month', now()) + INTERVAL '1 month';\n-- OR: functional index on DATE_TRUNC\nCREATE INDEX idx_orders_month ON orders (DATE_TRUNC('month', created_at));\n```\n\n**Rule of thumb:** never wrap a timestamp column in a function in a\n`WHERE` clause if an index on the raw column exists. Rewrite the predicate\nas a range (`col >= start AND col \u003C end`) so the index is used.\n",{"id":69,"difficulty":53,"q":70,"a":71},"lag-lead-dates","How do you calculate time between consecutive events per user?","Use the `LAG` window function to access the previous row's timestamp within\na partition, then subtract to get the gap.\n\n```sql\n-- Time between consecutive logins per user\nSELECT user_id,\n       logged_in_at,\n       LAG(logged_in_at) OVER (\n         PARTITION BY user_id\n         ORDER BY logged_in_at\n       ) AS prev_login,\n       logged_in_at - LAG(logged_in_at) OVER (\n         PARTITION BY user_id\n         ORDER BY logged_in_at\n       ) AS gap\nFROM   user_logins\nORDER  BY user_id, logged_in_at;\n\n-- Average gap between sessions per user\nSELECT user_id,\n       AVG(gap) AS avg_session_gap\nFROM (\n  SELECT user_id,\n         logged_in_at - LAG(logged_in_at) OVER (\n           PARTITION BY user_id ORDER BY logged_in_at\n         ) AS gap\n  FROM user_logins\n) sub\nWHERE gap IS NOT NULL  -- first row per user has no previous\nGROUP BY user_id;\n```\n\n**Rule of thumb:** `LAG` with a date subtraction is the standard SQL\npattern for inter-event time calculations. The first event per partition\nreturns NULL for `LAG` — always filter or handle that case explicitly.\n",{"id":73,"difficulty":24,"q":74,"a":75},"make-date","How do you construct a date from year, month, and day components?","```sql\n-- Postgres: MAKE_DATE \u002F MAKE_TIMESTAMP\nSELECT MAKE_DATE(2026, 6, 20);                        -- → '2026-06-20'\nSELECT MAKE_TIMESTAMP(2026, 6, 20, 14, 30, 0);        -- → '2026-06-20 14:30:00'\nSELECT MAKE_TIMESTAMPTZ(2026, 6, 20, 14, 30, 0, 'UTC'); -- tz-aware\n\n-- MySQL: MAKEDATE \u002F MAKETIME \u002F STR_TO_DATE\nSELECT MAKEDATE(2026, 171);            -- → '2026-06-20' (day 171 of 2026)\nSELECT STR_TO_DATE('20\u002F06\u002F2026', '%d\u002F%m\u002F%Y');  -- → '2026-06-20'\n\n-- SQL Server: DATEFROMPARTS \u002F DATETIMEFROMPARTS\nSELECT DATEFROMPARTS(2026, 6, 20);    -- → '2026-06-20'\nSELECT DATETIMEFROMPARTS(2026, 6, 20, 14, 30, 0, 0);\n\n-- Practical: reconstruct a date from separate year\u002Fmonth columns\nSELECT MAKE_DATE(order_year, order_month, 1) AS period_start\nFROM   monthly_summaries;\n```\n\n**Rule of thumb:** use `MAKE_DATE` (Postgres) or `DATEFROMPARTS` (SQL\nServer) rather than string concatenation + casting — they are cleaner and\ncorrectly reject invalid dates (e.g., February 30) instead of silently\ncoercing them.\n",{"id":77,"difficulty":24,"q":78,"a":79},"age-function","How do you calculate a person's age or the time elapsed between two dates?","```sql\n-- Postgres: AGE() returns an interval\nSELECT AGE(birthdate)                   AS age       FROM persons;  -- age from today\nSELECT AGE(NOW(), birthdate)            AS age       FROM persons;  -- explicit end date\nSELECT EXTRACT(YEAR FROM AGE(birthdate)) AS age_years FROM persons;\n\n-- MySQL: TIMESTAMPDIFF\nSELECT TIMESTAMPDIFF(YEAR, birthdate, CURDATE()) AS age_years FROM persons;\nSELECT TIMESTAMPDIFF(MONTH, start_date, end_date) AS months_elapsed FROM projects;\n\n-- SQL Server: DATEDIFF\nSELECT DATEDIFF(YEAR,  birthdate, GETDATE()) AS age_years   FROM persons;\nSELECT DATEDIFF(DAY,   start_dt,  end_dt)    AS days_open   FROM tickets;\nSELECT DATEDIFF(MONTH, start_dt,  end_dt)    AS months_open FROM projects;\n```\n\nNote: `DATEDIFF(YEAR, …)` in SQL Server counts year boundaries crossed,\nnot full years lived — a birthday on December 31 would add 1 year on\nJanuary 1 of the next year. Use `TIMESTAMPDIFF` (MySQL) or `AGE` (Postgres)\nfor more accurate age calculation.\n\n**Rule of thumb:** for precise age-in-years, use `TIMESTAMPDIFF(YEAR, …)`\n(MySQL) or `EXTRACT(YEAR FROM AGE(…))` (Postgres). Never subtract year\nnumbers directly — they don't account for the month\u002Fday portion.\n",{"id":81,"difficulty":53,"q":82,"a":83},"date-range-overlap","How do you find rows where two date ranges overlap?","Two date ranges `[A_start, A_end]` and `[B_start, B_end]` overlap when\n`A_start \u003C B_end AND A_end > B_start`. This is the standard overlap\npredicate — all seven overlap cases satisfy it.\n\n```sql\n-- Find all bookings that overlap with a requested range\nSELECT *\nFROM   bookings\nWHERE  start_date \u003C '2026-07-01'   -- booking starts before requested end\n  AND  end_date   > '2026-06-15';  -- booking ends after requested start\n\n-- Find overlapping pairs within the same table (self-join)\nSELECT a.id AS booking_a, b.id AS booking_b\nFROM   bookings a\nJOIN   bookings b ON a.id \u003C b.id          -- avoid duplicate pairs\n       AND a.start_date \u003C b.end_date\n       AND a.end_date   > b.start_date;\n\n-- Postgres: use the built-in OVERLAPS operator (inclusive bounds)\nSELECT *\nFROM   bookings\nWHERE  (start_date, end_date) OVERLAPS ('2026-06-15'::DATE, '2026-07-01'::DATE);\n```\n\n**Rule of thumb:** the overlap condition is `A.start \u003C B.end AND A.end >\nB.start`. Make sure to use `\u003C` \u002F `>` (exclusive) vs `\u003C=` \u002F `>=` (inclusive)\nconsistently with your data model — half-open intervals `[start, end)` are\ngenerally easiest to reason about.\n",15,null,{"description":11},"SQL date and time function interview questions — NOW, CURRENT_DATE, DATE_TRUNC, EXTRACT, date arithmetic, interval types, timezone handling, and formatting across Postgres, MySQL, and SQL Server.","sql\u002Ffunctions\u002Fdate-functions","Date & Time Functions","Built-in Functions","functions","2026-06-20","W1E2_wYmKlkz2SVIgqmzxIqqc-UnOTEwm6BLBI9gDlU",[95,99,100],{"subtopic":96,"path":97,"order":98},"String & Numeric Functions","\u002Fsql\u002Ffunctions\u002Fstring-numeric-functions",1,{"subtopic":89,"path":20,"order":12},{"subtopic":101,"path":102,"order":103},"Conditional & NULL Functions","\u002Fsql\u002Ffunctions\u002Fconditional-null-functions",3,{"path":105,"title":106},"\u002Fblog\u002Fsql-date-time-functions","SQL Date & Time Functions — NOW, DATE_TRUNC, EXTRACT, and Intervals",1782244107458]