Datetimes versus timestamps in MySQL

Storing datetime and timestamp data in MySQL correctly.

Datetimes versus timestamps in MySQL

There are several different ways to store dates and times in MySQL, and knowing which one to use requires understanding what you'll be storing and how MySQL handles each type.

There are five column types that you can use to store temporal data in MySQL. They are:

  • DATE
  • YEAR
  • TIME

Each column type stores slightly different data, has different minimum and maximum values, and requires different amounts of storage.

In the table below, you'll see each column type and their various attributes.

| Column | Data | Bytes | Min | Max |
| DATE | Date only | 3 | 1000-01-01 | 9999-12-31 |
| DATETIME | Date + time | 8 | 1000-01-01 00:00:00 | 9999-12-31 23:59:59 |
| TIMESTAMP | Date + time | 4 | 1970-01-01 00:00:00 | 2038-01-19 03:14:17 |
| YEAR | Year only | 1 | 1901 | 2155 |
| TIME | Time only | 3 | -838:59:59 | 838:59:59 |

Dates, years, and times

Based on this table, the first question you must ask yourself is: "What kind of data do I need to store?"

The DATE column type

If you are storing a date only, with no time information, the choice is easy! You would use the DATE column. This column is helpful for things like birthdays, anniversaries, employee start dates, etc. The DATE column is a no-fuss affair: it stores dates.

It can store a massive range, from the beginning of the year 1000 to the end of the year 9999, in a minimal footprint, only 3 bytes.

The YEAR column type

Likewise, if you're storing a year with no date information, there is a unique column just for that: the YEAR column. This column is not widely used, but it serves its purpose exceedingly well when you need it. The YEAR column is compact at only 1 byte, but the range is not very large, from 1901 to 2155. If you need a wider range, you'd probably migrate to a signed or unsigned small integer, depending on your needs.

The TIME column type

Finally, rounding out the single-purpose column types is the TIME column. The time column stores time only, in the hhh:mm:ss format. The legal range of this column is much wider than 00:00:00 to 23:59:59 because you can use it to store intervals of time and wall-clock time. The valid range spans from -838:59:59 to 838:59:59, approximately 35 days in either direction.

If you are storing a time or duration irrespective of date, the TIME column is made for that. If you are storing a date and time together, you should use either the DATETIME or TIMESTAMP columns to keep it as one logical unit instead of splitting it into two columns.

DATETIMEs and TIMESTAMPs both serve a similar purpose: storing a reference to a specific point in time. If you were tasked with storing a reference to February 14th, 2029, at 8:47am, you could use either type to satisfy that requirement!

Again, the question of whether to use a DATETIME or TIMESTAMP in MySQL depends on what you are trying to do. Neither is inherently bad, and neither is inherently good.

The first difference is that the storage size for a TIMESTAMP is 4 bytes, which is half the size of a DATETIME at 8 bytes. Storage space is cheap, but when designing table schemas, we always want to choose the smallest possible column type that fits the full range of our data. When creating your tables, there is no need to be generous!

Because the storage size of the TIMESTAMP is much smaller than the DATETIME, the range of legal values is likewise much smaller.

The 2038 problem in MySQL

A TIMESTAMP ranges from 1970-01-01 00:00:00 to 2038-01-19 03:14:17. The year 2038 might sound familiar to you! This is one incarnation of the famous Year 2038 Problem. You may have also seen it written as Y2038, Y2K38, or even The Epochalypse, which should be credited for its clever wordplay.

The 2038 issue arises in systems that calculate Unix time, defined as the seconds that have passed since the Unix epoch at 00:00:00 UTC on January 1, 1970. These systems use a signed 32-bit integer to store this time, which can only hold integer values between -2^31 and 2^31 - 1. The maximum timestamp that can be accurately represented corresponds to 2^31 - 1 seconds after the epoch, which is 03:14:07 UTC on January 19, 2038.

For our purposes, this immediately eliminates TIMESTAMP as a contender for storing dates in the far future. If there is even a reasonable chance that you might need to store dates beyond 2038, it's best to use a DATETIME.

For now, there are instances where a TIMESTAMP is perfectly adequate! One such scenario is when you are recording the current time when an action takes place. Because we're still more than a decade away from the year 2038, it's a perfectly reasonable choice to use TIMESTAMP for columns like:

  • updated_at
  • created_at
  • deleted_at
  • archived_at
  • posted_at

These columns are usually populated by your application. As long as these columns record the current time at which an event happened, you don't have to worry about the 2038 problem for a long time. And surely, by then, we'll have a solution for it. (Surely, right?)

When to use DATETIME

Given the relatively narrow range of legal values for a TIMESTAMP column, you should consider a DATETIME for any datetime where you might need to populate a date outside the 1970 to 2038 range. If the field is open to user input, you'll likely want to use a DATETIME or validate that the input falls within the 1970-2038 window for a TIMESTAMP.

Timezone treatment of DATETIME versus TIMESTAMP in MySQL

The storage size and the legal range are the most significant, most obvious differences between the DATETIME and TIMESTAMP columns, but there is one slightly more pernicious difference: timezones. Timezones are the bane of almost every developer's existence, and you're not free from them in MySQL either.

The DATETIME column does absolutely nothing concerning timezones. If you write a value of 2029-02-14 08:47 into the database, you will always and forever receive a value of 2029-02-14 08:47. No matter what your server or connection timezones are set to, you'll always get 2029-02-14 08:47 back.

TIMESTAMP on the other hand, tries to "help you out" by always converting to and from UTC. From the documentation:

MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval. (This does not occur for other types such as DATETIME.) By default, the current time zone for each connection is the server's time.

Let's do a small example to prove this. We will create a table with DATETIME and TIMESTAMP columns and insert some data.

CREATE TABLE timezone_test (
`timestamp` TIMESTAMP,
`datetime` DATETIME

Now we'll explicitly set our connection timezone to UTC and insert 2029-02-14 08:47:

SET SESSION time_zone = '+00:00';
INSERT INTO timezone_test VALUES ('2029-02-14 08:47', '2029-02-14 08:47');
SELECT * FROM timezone_test;
-- | timestamp | datetime |
-- |---------------------|---------------------|
-- | 2029-02-14 08:47:00 | 2029-02-14 08:47:00 |

Looking good so far; the value we inserted is the value we got back! Let's change our session timezone to -05:00 now and see what happens:

SET SESSION time_zone = '-05:00';
SELECT * FROM timezone_test;
-- | timestamp | datetime |
-- |---------------------|---------------------|
-- | 2029-02-14 03:47:00 | 2029-02-14 08:47:00 |

You'll notice that the timestamp column has shifted five hours, while the datetime column remains the same. In practice, this shouldn't matter too much, as hopefully your server and connection timezones are always set to UTC, but if they aren't, you might be in for a big surprise when the values you put in the database are not the values you get back out.

Hopefully this helps demystify the differences between all of the columns you can use to store temporal data in MySQL! If you'd like to watch a video on the same topic, you can find that and much more in our free MySQL for Developers course.

Want a powerful and performant database that doesn’t slow you down?