Skip to main content

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples in this guide work on VillageSQL. Install Now →
Time zone bugs are among the hardest to debug — they appear and disappear based on server location, show up differently in dev vs production, and often only surface after a daylight saving transition. The root cause is almost always a mismatch between how MySQL stores time and how the application expects to read it.

DATETIME vs TIMESTAMP

These two types look similar but behave differently at the critical moment — retrieval. TIMESTAMP automatically converts values:
  • On INSERT/UPDATE: converts from session time_zone to UTC for storage
  • On SELECT: converts from UTC back to session time_zone for display
DATETIME makes no conversions. The value you write in is the value you get back.

The 2038 Problem

TIMESTAMP is stored as a 32-bit Unix timestamp. It can’t represent dates after 2038-01-19 03:14:07 UTC. Any application that stores future dates — reservations, subscriptions, scheduled events — should use DATETIME to avoid hitting this limit.

Server and Session Time Zones

MySQL has two independent time zone settings:
If time_zone shows SYSTEM, MySQL is using the operating system’s time zone. This works until the OS changes (server migration, DST transition) — then your timestamps shift. Best practice: set the server to UTC explicitly, and handle display conversion in the application.
Or at runtime:

Named Time Zones

'+00:00' and 'UTC' are both valid, but named zones like 'America/New_York' require the timezone tables to be loaded. If CONVERT_TZ() returns NULL or named zones fail, the tables are missing.

AUTO_UPDATE Columns

TIMESTAMP columns support automatic population on insert and update:
CURRENT_TIMESTAMP is evaluated in UTC if the session timezone is UTC, so created_at and updated_at are stored consistently regardless of who connects and from what timezone. You can do the same with DATETIME and NOW():
NOW() returns the current date and time in the session’s timezone — another reason to standardize on UTC for the session.

Frequently Asked Questions

Should I store timestamps as UTC in DATETIME or use TIMESTAMP?

If your dates stay within the 2038 ceiling, TIMESTAMP is cleaner — it handles UTC conversion automatically. For future dates beyond 2038 or for applications that need to store historical dates before 1970, use DATETIME and enforce UTC at the application layer by always converting to UTC before inserting.

Why do timestamps shift after a server migration?

The old server used time_zone = SYSTEM pointing to one OS timezone; the new server points to a different one (or UTC). TIMESTAMP values stored as UTC display correctly in both timezones, but if any values were stored assuming the system timezone rather than UTC, they’ll shift. Audit with SELECT @@global.time_zone on both servers before migrating.

How do I store just a date with no time component?

Use DATE. It stores 3 bytes and has no time-of-day or timezone ambiguity. Operations like WHERE date_col = '2024-03-15' are unambiguous regardless of the session timezone.

Troubleshooting

See also