Skip to main content

Date Arithmetic and Difference

Learning Focus

Use this lesson to understand date arithmetic — how to add/subtract intervals and calculate differences between dates. These operations power billing cycles, SLA tracking, retention policies, and every time-aware feature in your application.

Concept Overview

What Is Date Arithmetic?

Date arithmetic means performing calculations on date and time values:

  • Adding time: "What date is 30 days from now?"
  • Subtracting time: "What was the date 7 days ago?"
  • Measuring differences: "How many hours between order and delivery?"

Key Functions

FunctionPurposeReturns
DATE_ADD(date, INTERVAL n unit)Add an interval to a dateDATE or DATETIME
DATE_SUB(date, INTERVAL n unit)Subtract an interval from a dateDATE or DATETIME
DATEDIFF(date1, date2)Difference in daysINT
TIMESTAMPDIFF(unit, start, end)Difference in any unitINT

Available Interval Units

UnitExample
SECONDINTERVAL 30 SECOND
MINUTEINTERVAL 15 MINUTE
HOURINTERVAL 2 HOUR
DAYINTERVAL 7 DAY
WEEKINTERVAL 1 WEEK
MONTHINTERVAL 3 MONTH
QUARTERINTERVAL 1 QUARTER
YEARINTERVAL 1 YEAR

Basic Syntax & Rules

Adding and Subtracting Intervals

-- Add 30 days
SELECT DATE_ADD('2026-02-10', INTERVAL 30 DAY) AS future_date;
-- Result: 2026-03-12

-- Subtract 7 days
SELECT DATE_SUB(NOW(), INTERVAL 7 DAY) AS week_ago;

-- You can also use + and - operators
SELECT '2026-02-10' + INTERVAL 1 MONTH AS next_month;
-- Result: 2026-03-10

SELECT NOW() - INTERVAL 24 HOUR AS yesterday;

Measuring Differences

-- DATEDIFF: always returns days
SELECT DATEDIFF('2026-12-31', '2026-01-01') AS days_in_year;
-- Result: 364

-- TIMESTAMPDIFF: returns difference in your chosen unit
SELECT TIMESTAMPDIFF(HOUR, '2026-02-10 08:00:00', '2026-02-11 14:30:00') AS hours_diff;
-- Result: 30

SELECT TIMESTAMPDIFF(MONTH, '2025-06-15', '2026-02-10') AS months_diff;
-- Result: 7

Important: DATEDIFF vs. TIMESTAMPDIFF

FeatureDATEDIFF()TIMESTAMPDIFF()
PrecisionDays onlyAny unit (seconds to years)
Argument order(end, start)(unit, start, end) — reversed!
Use caseSimple date comparisonsPrecise time measurements

Watch out: DATEDIFF(end, start) but TIMESTAMPDIFF(unit, start, end) — the argument order is different.


Step-by-Step Examples

Example 1: Subscription Renewal Dates

Scenario: Calculate when each customer's subscription renews.

SELECT
customer_id,
plan_type,
start_date,
CASE plan_type
WHEN 'monthly' THEN DATE_ADD(start_date, INTERVAL 1 MONTH)
WHEN 'quarterly' THEN DATE_ADD(start_date, INTERVAL 3 MONTH)
WHEN 'annual' THEN DATE_ADD(start_date, INTERVAL 1 YEAR)
END AS renewal_date
FROM subscriptions
WHERE status = 'active';
customer_idplan_typestart_daterenewal_date
101monthly2026-01-152026-02-15
102quarterly2026-01-012026-04-01
103annual2025-06-102026-06-10

Example 2: Delivery Time Tracking

Scenario: Calculate how long each order took to deliver.

SELECT
order_id,
created_at,
delivered_at,
TIMESTAMPDIFF(HOUR, created_at, delivered_at) AS delivery_hours,
TIMESTAMPDIFF(MINUTE, created_at, delivered_at) AS delivery_minutes
FROM shipments
WHERE delivered_at IS NOT NULL
ORDER BY delivery_hours DESC
LIMIT 10;
order_idcreated_atdelivered_atdelivery_hoursdelivery_minutes
90012026-02-08 10:00:002026-02-09 04:00:00181080
90022026-02-07 09:00:002026-02-08 12:30:00271650

Example 3: SLA Compliance Check

Scenario: Support tickets must be resolved within 24 hours. Find overdue tickets.

SELECT
ticket_id,
created_at,
TIMESTAMPDIFF(HOUR, created_at, NOW()) AS hours_open,
CASE
WHEN TIMESTAMPDIFF(HOUR, created_at, NOW()) > 24
THEN 'SLA BREACHED'
ELSE 'Within SLA'
END AS sla_status
FROM support_tickets
WHERE resolved_at IS NULL
ORDER BY created_at ASC;
ticket_idcreated_athours_opensla_status
5012026-02-08 10:00:0052SLA BREACHED
5022026-02-10 08:00:006Within SLA

Example 4: Data Retention — Purge Old Records

Scenario: Delete audit logs older than 90 days.

-- First, check what would be deleted
SELECT COUNT(*) AS rows_to_delete
FROM audit_logs
WHERE created_at < NOW() - INTERVAL 90 DAY;

-- Then delete
DELETE FROM audit_logs
WHERE created_at < NOW() - INTERVAL 90 DAY
LIMIT 10000; -- Batch to avoid long locks

Example 5: Age Calculation

SELECT
user_id,
full_name,
birth_date,
TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) AS age
FROM users
WHERE TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) >= 18;
user_idfull_namebirth_dateage
1Alice Rahman1998-05-2027
2Bob Santoso2000-11-0325

Practical Use Cases

1. Billing and Invoicing

Calculate due dates: invoice_date + INTERVAL 30 DAY. Flag overdue invoices where due_date < CURDATE().

2. SLA Monitoring

Track response times and flag breaches: TIMESTAMPDIFF(HOUR, opened_at, NOW()) > sla_hours.

3. Retention Policies

Purge old data: DELETE WHERE created_at < NOW() - INTERVAL 1 YEAR. Batch deletes to avoid locking.

4. Subscription Management

Calculate renewal dates, trial expiry, and grace periods using DATE_ADD().

5. Analytics and Reporting

"Orders in the last 30 days", "revenue this quarter", "month-over-month growth" — all require date arithmetic.


Common Mistakes & Troubleshooting

MistakeWhat HappensHow to Fix
Comparing datetimes from mixed timezonesWrong interval results (e.g., 7 hours off)Normalize to UTC before computing differences
Using DATEDIFF() when you need hoursOnly returns whole days, even for datetime inputsUse TIMESTAMPDIFF(HOUR, ...) for hour-level precision
Not handling NULL end datesTIMESTAMPDIFF() returns NULL if either arg is NULLAdd WHERE end_date IS NOT NULL or use COALESCE()
Wrong argument orderDATEDIFF(start, end) gives a negative numberRemember: DATEDIFF(end, start) and TIMESTAMPDIFF(unit, start, end)
Month arithmetic surprisesDATE_ADD('2026-01-31', INTERVAL 1 MONTH) = 2026-02-28Be aware of month-end behavior and test edge cases
Unbounded DELETE for retentionLocks the table for minutes/hoursUse LIMIT to batch: DELETE ... LIMIT 10000 per run

Best Practices

  • Use TIMESTAMPDIFF() for precision — it handles any unit and gives accurate results
  • Use DATEDIFF() for simple day counts — it's cleaner when you only need days
  • Handle NULLs explicitly — delivery dates, resolution times often have NULL end values
  • Batch deletion for retention — never DELETE FROM ... WHERE created_at < X without a LIMIT
  • Test month-end edge cases — January 31 + 1 month = February 28 (or 29 in leap years)
  • Use operator syntax for readabilityNOW() - INTERVAL 7 DAY reads better than DATE_SUB(NOW(), INTERVAL 7 DAY)

Hands-On Practice

Exercise 1: Renewal Date Calculator (Easy)

Write a query that shows each subscription's start_date and renewal_date based on plan_type ('monthly', 'quarterly', 'annual').

Exercise 2: SLA Report (Medium)

Create a query that:

  1. Lists all open support tickets
  2. Shows hours since creation
  3. Marks tickets as "SLA BREACHED" if open for more than 24 hours
  4. Orders by longest-open first

Exercise 3: Retention Cleanup Script (Advanced)

Write a batch deletion query that:

  1. Deletes audit log rows older than 90 days
  2. Processes no more than 10,000 rows at a time
  3. Shows how many rows were deleted
  4. Explain how you would schedule this to run daily

Connection to Other Concepts

Related ConceptHow It Connects
Current Date/Time FunctionsNOW() is the starting point for most arithmetic
FormattingFormat the result: DATE_FORMAT(NOW() + INTERVAL 30 DAY, '%M %d, %Y')
Event SchedulerRecurring events use interval expressions for scheduling
IndexesDate range queries (BETWEEN, >= / <) benefit from indexes on date columns
Retention PoliciesNOW() - INTERVAL N DAY is the foundation of every data retention query

What to Learn Next

  • 12. Views — create reusable query patterns for your time-based reports

Visual Learning Diagram

flowchart LR
A["Known Date\n2026-02-10"] -->|"+ INTERVAL 30 DAY"| B["Future Date\n2026-03-12"]
A -->|"- INTERVAL 7 DAY"| C["Past Date\n2026-02-03"]

D["Start Date"] --> E["DATEDIFF()"]
F["End Date"] --> E
E --> G["Days Between\n(integer)"]

H["Start Timestamp"] --> I["TIMESTAMPDIFF()"]
J["End Timestamp"] --> I
I --> K["Precision Difference\n(hours, minutes, etc.)"]

classDef func fill:#0d6efd,stroke:#fff,color:#fff
class E,I func

Quick Reference

-- Add/subtract intervals
SELECT DATE_ADD(NOW(), INTERVAL 30 DAY);
SELECT DATE_SUB(NOW(), INTERVAL 1 MONTH);
SELECT NOW() + INTERVAL 1 YEAR;
SELECT NOW() - INTERVAL 24 HOUR;

-- Difference in days
SELECT DATEDIFF('2026-12-31', '2026-01-01');

-- Difference in any unit
SELECT TIMESTAMPDIFF(HOUR, start_at, end_at) FROM jobs;
SELECT TIMESTAMPDIFF(MINUTE, created_at, resolved_at) FROM tickets;
SELECT TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) FROM users;

-- Common patterns
WHERE created_at >= NOW() - INTERVAL 7 DAY -- last 7 days
WHERE created_at < NOW() - INTERVAL 90 DAY -- older than 90 days
WHERE due_date < CURDATE() -- overdue

What's Next