MySQLDate & Time Functions

MySQL Date and Time Functions

Date and time manipulation is one of the most common tasks in SQL — reporting periods, calculating age, scheduling jobs, or filtering by date ranges. MySQL provides a comprehensive set of functions that make this straightforward once you understand the key concepts.

TIMESTAMP vs DATETIME — Timezone Handling

The most important distinction in MySQL date handling is between TIMESTAMP and DATETIME:

  • TIMESTAMP stores the value as UTC internally. When you insert a value, MySQL converts from the current session timezone to UTC for storage. When you read it back, MySQL converts from UTC to the session timezone. TIMESTAMP is the right type for "when did this event happen" — it stays correct across timezone changes.

  • DATETIME stores the value literally as entered, with no timezone conversion. If you store '2024-07-15 14:30:00' it reads back as '2024-07-15 14:30:00' regardless of timezone settings. Use DATETIME for "calendar" values where timezone semantics do not apply (e.g., a scheduled appointment, a birth date with time).

SQL
-- TIMESTAMP: UTC storage, timezone-converted display
CREATE TABLE events_ts (
  id         INT       NOT NULL AUTO_INCREMENT,
  name       VARCHAR(100),
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id)
);

-- DATETIME: literal storage, no conversion
CREATE TABLE appointments (
  id         INT      NOT NULL AUTO_INCREMENT,
  patient_id INT      NOT NULL,
  appt_time  DATETIME NOT NULL,  -- stored as-is, no UTC conversion
  PRIMARY KEY (id)
);

-- Check session timezone
SELECT @@session.time_zone;

-- Change session timezone (affects TIMESTAMP display, not DATETIME)
SET time_zone = '+05:30';
SELECT created_at FROM events_ts;  -- shows time in IST now
Note
Application code should store and pass dates in UTC and convert to local time for display. Use TIMESTAMP columns and configure your database connection with SET time_zone = '+00:00' at connection time to avoid timezone confusion.
NOW() vs SYSDATE()

NOW() and SYSDATE() both return the current datetime, but they behave differently inside stored procedures and long-running queries:

  • NOW() is evaluated once when the statement starts executing. All calls to NOW() within the same statement return the same value.
  • SYSDATE() calls the system clock at the exact moment the function is evaluated — so two calls within the same statement may return different values.

This difference matters for replication: statement-based replication replays SQL statements. NOW() is deterministic per-statement (safe). SYSDATE() is non-deterministic and requires row-based replication or the --sysdate-is-now server option.

SQL
-- NOW() is fixed per statement
SELECT NOW(), SLEEP(2), NOW();
-- Both NOW() calls return the same time

-- SYSDATE() evaluates at call time
SELECT SYSDATE(), SLEEP(2), SYSDATE();
-- Second SYSDATE() is 2 seconds later

-- Practical: use NOW() for created_at defaults (deterministic)
INSERT INTO orders (customer_id, created_at) VALUES (42, NOW());
Tip
Prefer NOW() over SYSDATE() in production. It is deterministic per statement, safer for replication, and produces consistent results within a transaction.
Getting the Current Date and Time

SQL
SELECT NOW();           -- '2024-07-15 14:30:00' (date and time)
SELECT CURDATE();       -- '2024-07-15'          (date only)
SELECT CURRENT_DATE();  -- '2024-07-15'          (alias for CURDATE)
SELECT CURTIME();       -- '14:30:00'            (time only)
SELECT CURRENT_TIME();  -- '14:30:00'            (alias for CURTIME)
SELECT UTC_DATE();      -- '2024-07-15'          (UTC date)
SELECT UTC_TIMESTAMP(); -- '2024-07-15 18:30:00' (UTC datetime)
DATE_FORMAT — Format Specifier Reference

DATE_FORMAT(date, format) converts a date/datetime to a formatted string using % specifiers.

SQL
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d');          -- '2024-07-15'
SELECT DATE_FORMAT(NOW(), '%d/%m/%Y');          -- '15/07/2024'
SELECT DATE_FORMAT(NOW(), '%M %d, %Y');         -- 'July 15, 2024'
SELECT DATE_FORMAT(NOW(), '%W, %M %e, %Y');     -- 'Monday, July 15, 2024'
SELECT DATE_FORMAT(NOW(), '%h:%i %p');          -- '02:30 PM'
SELECT DATE_FORMAT(NOW(), '%Y-%m');             -- '2024-07' (year-month key)
SELECT DATE_FORMAT(NOW(), '%Y-Q%Q');            -- '2024-Q3' (fiscal quarter label)

-- Group sales by month for reporting
SELECT
  DATE_FORMAT(order_date, '%Y-%m') AS month,
  COUNT(*)                          AS order_count,
  SUM(total)                        AS revenue
FROM orders
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY month;

Specifier

Description

Example

%Y

Four-digit year

2024

%y

Two-digit year

24

%m

Month 01-12

07

%M

Month name

July

%b

Month abbreviation

Jul

%d

Day of month 01-31

15

%e

Day of month 1-31 (no padding)

15

%W

Weekday name

Monday

%a

Weekday abbreviation

Mon

%w

Day of week (0=Sunday)

1

%H

Hour 00-23

14

%h

Hour 01-12

02

%i

Minutes 00-59

30

%s

Seconds 00-59

00

%p

AM or PM

PM

%Q

Quarter 1-4

3

%U

Week number (Sunday start)

28

%V

ISO week number

29

STR_TO_DATE — Parsing User Input

STR_TO_DATE(string, format) parses a string into a DATE or DATETIME using the same format specifiers as DATE_FORMAT(). Essential when importing data with non-standard date formats.

SQL
SELECT STR_TO_DATE('15/07/2024', '%d/%m/%Y');
-- '2024-07-15'

SELECT STR_TO_DATE('July 15 2024', '%M %d %Y');
-- '2024-07-15'

SELECT STR_TO_DATE('07-15-2024 2:30 PM', '%m-%d-%Y %h:%i %p');
-- '2024-07-15 14:30:00'

-- Fix imported data with wrong date format
UPDATE orders
SET order_date = STR_TO_DATE(order_date_raw, '%d-%m-%Y')
WHERE order_date IS NULL AND order_date_raw IS NOT NULL;
DATE_ADD and DATE_SUB — INTERVAL Units

These functions add or subtract a time interval from a date or datetime value. MySQL supports all the following INTERVAL units:

SQL
SELECT DATE_ADD('2024-07-15', INTERVAL 30 DAY);       -- '2024-08-14'
SELECT DATE_ADD('2024-07-15', INTERVAL 3 MONTH);      -- '2024-10-15'
SELECT DATE_ADD('2024-07-15', INTERVAL 1 YEAR);       -- '2025-07-15'
SELECT DATE_ADD(NOW(),        INTERVAL 2 HOUR);        -- 2 hours from now
SELECT DATE_ADD(NOW(),        INTERVAL 30 MINUTE);
SELECT DATE_ADD(NOW(),        INTERVAL 45 SECOND);
SELECT DATE_ADD(NOW(),        INTERVAL '1 30' HOUR_MINUTE);  -- 1h 30m
SELECT DATE_ADD(NOW(),        INTERVAL '2 12:30:00' DAY_SECOND);

-- Find subscriptions expiring in the next 7 days
SELECT user_id, expires_at
FROM subscriptions
WHERE expires_at BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 7 DAY);
TIMESTAMPDIFF — Age and Duration Calculations

TIMESTAMPDIFF(unit, start, end) is more versatile than DATEDIFF() — it handles month and year boundaries correctly and supports YEAR, MONTH, WEEK, DAY, HOUR, MINUTE, and SECOND.

SQL
-- Age in years
SELECT TIMESTAMPDIFF(YEAR, '1990-03-15', CURDATE()) AS age_years;

-- Duration in months
SELECT TIMESTAMPDIFF(MONTH, '2024-01-01', '2024-07-15') AS months;  -- 6

-- Days until New Year
SELECT TIMESTAMPDIFF(DAY, NOW(), '2025-01-01 00:00:00') AS days_to_ny;

-- Subscription duration
SELECT
  user_id,
  TIMESTAMPDIFF(DAY, start_date, COALESCE(end_date, CURDATE())) AS active_days
FROM subscriptions;

-- Age verification with category
SELECT
  name,
  birth_date,
  TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) AS age,
  CASE
    WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) >= 18 THEN 'Adult'
    ELSE 'Minor'
  END AS category
FROM customers;
Business Day and Fiscal Quarter Calculations

MySQL has no built-in business-day function, but you can calculate working days by subtracting weekends. For fiscal year quarters (often not aligned with calendar quarters), use CASE expressions.

SQL
-- Rough business day count between two dates (excludes weekends)
-- Note: does not account for holidays
SELECT
  start_dt,
  end_dt,
  FLOOR(DATEDIFF(end_dt, start_dt) / 7) * 5
    + MID('0123444401233334012222340111123400001234000123440',
          WEEKDAY(start_dt) * 7 + WEEKDAY(end_dt) + 1, 1) AS business_days
FROM date_ranges;

-- Fiscal year quarter (Q1 = April-June, common in UK/India)
SELECT
  order_date,
  CASE
    WHEN MONTH(order_date) IN (4,5,6)   THEN 'Q1'
    WHEN MONTH(order_date) IN (7,8,9)   THEN 'Q2'
    WHEN MONTH(order_date) IN (10,11,12) THEN 'Q3'
    ELSE 'Q4'
  END AS fiscal_quarter
FROM orders;
Common Date Query Patterns

This week's orders:

SQL
SELECT * FROM orders
WHERE order_date >= DATE(NOW() - INTERVAL WEEKDAY(NOW()) DAY)
  AND order_date <  DATE(NOW() - INTERVAL WEEKDAY(NOW()) DAY) + INTERVAL 7 DAY;

Last month's revenue:

SQL
SELECT SUM(total) AS last_month_revenue
FROM orders
WHERE order_date >= DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 MONTH), '%Y-%m-01')
  AND order_date <  DATE_FORMAT(NOW(), '%Y-%m-01');

Rolling 30-day active users:

SQL
SELECT COUNT(DISTINCT user_id) AS active_users_30d
FROM user_activity
WHERE activity_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY);

Month-over-month revenue comparison:

SQL
WITH monthly AS (
  SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS month,
    SUM(total)                        AS revenue
  FROM orders
  GROUP BY DATE_FORMAT(order_date, '%Y-%m')
)
SELECT
  month,
  revenue,
  LAG(revenue) OVER (ORDER BY month)   AS prev_revenue,
  ROUND(
    (revenue - LAG(revenue) OVER (ORDER BY month))
    / LAG(revenue) OVER (ORDER BY month) * 100, 2
  ) AS pct_change
FROM monthly;
UNIX_TIMESTAMP and FROM_UNIXTIME

SQL
SELECT UNIX_TIMESTAMP();                       -- e.g. 1721051400
SELECT UNIX_TIMESTAMP('2024-07-15 14:30:00');  -- specific datetime to Unix
SELECT FROM_UNIXTIME(1721051400);              -- '2024-07-15 14:30:00'
SELECT FROM_UNIXTIME(1721051400, '%Y-%m-%d');  -- '2024-07-15' (formatted)

-- Convert legacy INT timestamps to readable dates
SELECT name, FROM_UNIXTIME(created_ts) AS created_at FROM legacy_events;
Warning
Prefer DATETIME or TIMESTAMP column types over storing Unix timestamps as integers. The 32-bit Unix timestamp rolls over in 2038, and MySQL's TIMESTAMP type handles timezone conversion automatically.
Quick Reference

Function

Returns

Notes

NOW()

Current datetime

Evaluated once per statement; use in production

SYSDATE()

Current datetime

Evaluated at call time; avoid in replicated setups

CURDATE()

Current date

Alias: CURRENT_DATE()

CURTIME()

Current time

Alias: CURRENT_TIME()

DATE_ADD(d, INTERVAL n u)

Date plus interval

Units: MICROSECOND to YEAR

DATEDIFF(d1, d2)

Days between (d1 - d2)

Integer result; use TIMESTAMPDIFF for other units

TIMESTAMPDIFF(u, d1, d2)

Difference in unit

Handles month/year boundaries correctly

DATE_FORMAT(d, fmt)

Formatted string

Uses % specifiers like %Y-%m-%d

STR_TO_DATE(s, fmt)

String to DATE/DATETIME

For parsing non-standard date imports

EXTRACT(unit FROM d)

Integer component

YEAR, MONTH, DAY, HOUR...

LAST_DAY(d)

Last day of the month

Handles leap years automatically

UNIX_TIMESTAMP(d)

Seconds since epoch

32-bit; use TIMESTAMP column type instead

FROM_UNIXTIME(n)

Datetime from epoch

Optional format string argument