I’ve been conditioned to store past dates as UTC in a database since that is in fact when the event occurred. For future dates, I would store it with a specific timezone, to avoid changes such as leap seconds or timezone rule changes.
Postgres has timestamp with timezone
, but under the covers, it stores it as UTC, inferring that the specified timezone is an offset of UTC. If the timezone rules were to change, that would not be reflected in the column.
What is recommended in this case?
That seems backward. The main advantage of UTC over other time zones is that it is less prone to unexpected future changes: UTC only introduces leap seconds at known, limited points of the calendar year; and has none of the frequent politically-mandated offset changes.
Storing values in some locally-managed timezone leaves those values more prone (compared with UTC) to arbitrary, unpredictable future changes in meaning.
So, the general recommendation is: Store all time values (whether date, or date+time) as UTC in the database, process them internally as UTC values; and convert to/from a local timezone only at external interfaces.
For PostgreSQL, that means prefer
TIMESTAMP WITH TIME ZONE
.It sounds like you want to store a localtime with respect to a certain timezone. In that case, store a
timestamp
(without timezone) and thetimezone
in a separate column.For example, suppose you want to record an event which will occur at 10 am on Feb 26, 2030 in Chicago and it must be at 10 am localtime regardless of the timezone rule in effect on that date.
If the database stores the timestamp without timezone:
Then later, you can find the UTC datetime of the event using
The query returns the UTC datetime,
2030-02-26 16:00:00
, which corresponds to2030-02-26 10:00:00
localtime in Chicago.Using
AT TIME ZONE
delays the application of the timezone rules to when the query is made instead of when thetimestamptz
was inserted.Using
AT TIME ZONE
on atimestamp
localizes the datetime to the given timezone, but reports the datetime in the user's timezone. UsingAT TIME ZONE
on atimestamptz
converts the datetime to the given timezone, then drops the offset, thus returning atimestamp
. Above,AT TIME ZONE
is used twice: first to localize atimestamp
and next to convert the returnedtimestamptz
to a new timezone (UTC). The result is atimestamp
in UTC.Here is an example, demonstrating
AT TIME ZONE
's behavior ontimestamp
s:2030-02-26 10:00:00-06
and2030-02-26 08:00:00-08
are the same datetimes but reported in different user timezones. This shows 10am in Chicago is 8am in Los Angeles (using current timezone definitions):An alternative to using
AT TIME ZONE
twice is to set the user timezone toUTC
. Then you could useNote that when done this way, a
timestamptz
is returned instead of atimestamp
.Beware that storing localtimes can be problematic because there can be nonexistent times and ambiguous times. For example,
2018-03-11 02:30:00
is a nonexistent localtime inAmerica/Chicago
. Postgresql normalizes nonexistent localtimes by assuming it refers to the corresponding time after Daylight Savings Time (DST) has begun (as though someone forgot to set their clock forward):An example of an ambiguous localtime is
2018-11-04 01:00:00
inAmerica/Chicago
. It occurs twice due to DST. Postgresql resolves this ambiguity by choosing the later time, after DST has ended:Notice that this means there is no way to refer to
2018-11-04 06:00:00 UTC
by storing localtimes in theAmerica/Chicago
timezone:Particularly if you want to protect yourself from future time zune changes you should use
timestamp with time zone
.PostgreSQL internally stores the number of microseconds since 2000-01-01 00:00:00, which is safe from time zone changes. If you keep updating your PostgreSQL, it will always display that absolute value correctly for your session time zone.
There is no provision for leap seconds in PostgreSQL.