DATE vs TIMESTAMP data types

Concepts covered: Date vs Timestamp

A DATE holds a calendar day and nothing smaller. A TIMESTAMP holds a calendar day plus a time of day, down to a fraction of a second. Printed side by side the 2 values look like cousins, which is how a column of the wrong type survives unnoticed for months. 2024-03-15 is a DATE. 2024-03-15 14:30:45 is a TIMESTAMP. The second answers a question the first cannot: at which moment?

TypeStoresLiteralSmallest step
DATEyear, month, dayDATE '2024-03-15'1 day
TIMESTAMPyear, month, day, hour, minute, second, fractionTIMESTAMP '2024-03-15 14:30:45'1 millisecond

Questions each type answers

A literal is the type name followed by the value in single quotes. The type name tells the engine to parse the quoted text as a day or a moment rather than keep it as text, so DATE '2024-03-15' compares as a day while '2024-03-15' on its own is a VARCHAR of 10 characters that compares letter by letter. Write the year first, then the month, then the day, each with its leading zero.

Choosing between them

Store as DATE
  • Check-in and check-out days
  • Birthdays and due dates
  • The day a daily report covers
  • Anything a person writes on a calendar
Store as TIMESTAMP
  • The moment a payment was captured
  • When a row was last updated
  • Events such as clicks or messages
  • Anything measured at a finer grain than a day

The bookings table in the next example mixes both. check_in and check_out are DATE columns because a guest arrives on a day and the hour is between the guest and the host. created_at is a TIMESTAMP because the moment a booking was made feeds fraud checks and lead-time reports. Run it and compare the last 2 columns: casting a TIMESTAMP to DATE keeps the day and drops the clock.

SELECT
  booking_id,
  check_in,
  created_at,
  CAST(created_at AS DATE) AS created_day
FROM bookings
bookings
booking_idlisting_idcheck_incheck_outcreated_at
11012024-03-142024-03-172024-03-01 09:12:00
21012024-03-152024-03-182024-03-14 00:00:00
31022024-03-162024-03-202024-03-14 18:45:10
41022024-03-202024-03-212024-03-15 08:05:30
51032024-02-282024-03-022024-02-01 12:00:00
Result
booking_idcheck_increated_atcreated_day
12024-03-142024-03-01 09:12:002024-03-01
22024-03-152024-03-14 00:00:002024-03-14
32024-03-162024-03-14 18:45:102024-03-14
42024-03-202024-03-15 08:05:302024-03-15
52024-02-282024-02-01 12:00:002024-02-01
ExpressionResultWhat happened
CAST(TIMESTAMP '2024-03-15 14:30:45' AS DATE)2024-03-15the clock was dropped
CAST(DATE '2024-03-15' AS TIMESTAMP)2024-03-15 00:00:00midnight was supplied
CAST('2024-03-15' AS DATE)2024-03-15text was parsed into a day
CAST(DATE '2024-03-15' AS VARCHAR)2024-03-15the day was printed as 10 characters

Casting a DATE up to a TIMESTAMP has to invent a time of day, and the engine chooses midnight. That midnight sits behind the most common date mistake in production queries. When a TIMESTAMP column is compared to a DATE literal, the literal is widened to midnight of its day before the comparison runs, so created_at = DATE '2024-03-14' asks for bookings created at exactly 00:00:00.000 on March 14, and almost nothing is created at that instant.

SELECT
  booking_id,
  created_at
FROM bookings
WHERE created_at = DATE '2024-03-14'
bookings
booking_idlisting_idcheck_incheck_outcreated_at
11012024-03-142024-03-172024-03-01 09:12:00
21012024-03-152024-03-182024-03-14 00:00:00
31022024-03-162024-03-202024-03-14 18:45:10
41022024-03-202024-03-212024-03-15 08:05:30
51032024-02-282024-03-022024-02-01 12:00:00
Result
booking_idcreated_at

One row comes back, booking 2, which happens to carry a midnight stamp. Booking 3 was created at 18:45:10 the same day and fails the test, because 18:45:10 is later than 00:00:00. To ask about the whole day, bring the column down to the literal's type with a cast, so both sides are days. Play the animation and watch the comparison turn true for 2 rows and false for 3.

SELECT
  booking_id,
  created_at
FROM bookings
WHERE CAST(created_at AS DATE) = DATE '2024-03-14'
bookings
booking_idlisting_idcheck_incheck_outcreated_at
11012024-03-142024-03-172024-03-01 09:12:00
21012024-03-152024-03-182024-03-14 00:00:00
31022024-03-162024-03-202024-03-14 18:45:10
41022024-03-202024-03-212024-03-15 08:05:30
51032024-02-282024-03-022024-02-01 12:00:00

Both wrong choices cost something. A team that stores check-in as a TIMESTAMP pays 8 bytes a row where 4 would do and writes a cast into every day comparison. The column also accepts a clock reading. Once one source starts writing arrivals at 15:00 or 16:30, check_in = DATE '2024-03-15' silently drops exactly those rows while the midnight ones still match. A team that stores payment time as a DATE loses the hour for good and can never say which of 2 payments on the same day arrived first.

Booking 2 has check_in 2024-03-15. What does this query return?

SELECT
  CAST(check_in AS TIMESTAMP) AS check_in_ts
FROM bookings
WHERE booking_id = 2

About this lesson

This section is part of the Dates: Beginner lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.