MySQL TIMESTAMP cannot represent values after 2038-01-19 03:14:07 UTC. If an application stores long-term expirations, contracts, or scheduled events beyond that point, use DATETIME and define exactly how the application normalizes time zones.
Last updated: November 12, 2026.
CREATE TABLE scheduled_events (
event_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
event_name VARCHAR(200) NOT NULL,
starts_at_utc DATETIME(6) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (event_id),
INDEX ix_scheduled_events_starts_at (starts_at_utc)
);
INSERT INTO scheduled_events (event_name, starts_at_utc)
VALUES ('Long-term renewal', '2045-06-30 14:00:00.000000');The column name records the storage contract: starts_at_utc contains a UTC wall-clock value even though DATETIME itself performs no automatic time-zone conversion.
Choose the type for the required range
The MySQL date and time type reference documents a much wider range for DATETIME, while TIMESTAMP ends in 2038. Keep TIMESTAMP for values within its range when automatic session-time-zone conversion is desirable. Use DATE when the value is a calendar date with no time of day.
Do not assume a future MySQL release has extended the range. Check the manual for the exact deployed server version before changing the schema or relying on a feature request. Make strict SQL mode part of the test environment so an out-of-range value fails instead of becoming a zero or truncated date.
Preserve the time-zone contract
MySQL automatically converts TIMESTAMP between the session time zone and UTC, but it does not do that for DATETIME. The MySQL time-zone documentation explains this distinction. Parse an incoming offset or named zone in application code, convert the instant to UTC, and store that value.
If the original named zone matters for recurring civil-time events, store it separately, for example America/New_York. An offset alone does not preserve future daylight-saving rules. During migration, add the new column, dual-write temporarily, backfill in bounded batches, and compare representative values on both sides of 2038. Switch reads only after null, range, and time-zone checks pass; remove the old column in a later deployment.