7.4 Timezone Handling: AT TIME ZONE and the timezone Setting
Alright, let’s get our hands dirty with timezones. This is where the clean, logical world of database theory meets the messy, political reality of human timekeeping. It’s absurd, but we have to deal with it. The good news is that Postgres has some powerful tools to manage this chaos, primarily through the AT TIME ZONE operator and the timezone setting. The bad news is that if you use them wrong, you’ll be off by hours, and your users will be very, very angry.
The absolute first thing to burn into your brain is the critical distinction between a timestamp with time zone (timestamptz) and a timestamp without time zone (timestamp). The names are misleading. A timestamptz does not store a timezone. Think of it as storing an absolute moment in time, a point on the universal timeline. It’s stored in UTC internally. The “with time zone” part means it’s aware of timezone rules and can be converted to any timezone for display. A plain timestamp, on the other hand, is a date and a time without any context. It’s just a note that says “January 1st, 12:00 PM.” It could be noon in London, Tokyo, or on the moon. It has no idea.
The AT TIME ZONE Operator: Your Conversion Workhorse
This operator is your Swiss Army knife for converting between timestamptz and timestamp, and its behavior changes based on what you feed it. This is the part that confuses everyone at first, so pay attention.
When you apply AT TIME ZONE to a timestamptz, it converts that absolute moment to the local time of the specified zone and returns a timestamp (without time zone). You’re stripping away the timezone context to see what the time was in that place.
-- It's 4:00 PM UTC on a Tuesday.
SELECT TIMESTAMP '2023-10-03 16:00:00+00' AT TIME ZONE 'America/New_York';
-- Result: 2023-10-03 12:00:00 (type: timestamp)
-- Why? Because when it's 4PM UTC, it's only 12PM (noon) in New York.
When you apply AT TIME ZONE to a plain timestamp, you’re doing the opposite. You’re assuming that the stored time is in the specified zone and you’re asking for the corresponding absolute moment (timestamptz) in UTC.
-- Let's say you stored '2023-10-03 12:00:00' meaning "noon in New York."
SELECT TIMESTAMP '2023-10-03 12:00:00' AT TIME ZONE 'America/New_York';
-- Result: 2023-10-03 16:00:00+00 (type: timestamptz)
-- Why? Because noon in New York is 4PM UTC.
The most common pitfall here is using the wrong type to start with. If you have a timestamp column and you use AT TIME ZONE on it, you’re not converting a time, you’re assigning a timezone origin story to an ambiguous value. This is a fantastic way to introduce errors.
The timezone Setting: The Default Lens
The timezone setting in your session (set via SET timezone TO 'America/Los_Angeles'; or in your connection string) is the default lens through which timestamptz values are displayed. It does not change the stored value one bit. The UTC moment remains pristine. It only changes how Postgres translates that moment for you when it outputs a result.
SET timezone TO 'UTC';
SELECT TIMESTAMPTZ '2023-10-03 16:00:00 UTC';
-- Output: 2023-10-03 16:00:00+00
SET timezone TO 'America/New_York';
SELECT TIMESTAMPTZ '2023-10-03 16:00:00 UTC';
-- Output: 2023-10-03 12:00:00-04
This is why you should always store timestamptz. You can always change how you view it later. If you store a plain timestamp, that context is lost forever.
Best Practices and Absurdities
Use
timestamptzfor everything except truly zone-agnostic concepts (like “Christmas always starts at midnight on the 25th”). Store user-facing events, log entries, and sensor readings astimestamptz. You’ll thank me later.Use full timezone names, not abbreviations.
America/New_Yorkis brilliant.ESTis a trap. Is it US Eastern Standard Time? Australian Eastern Standard Time? Is it currently in effect, or are we on EDT? Abbreviations are ambiguous and don’t account for daylight saving time rules.AT TIME ZONE 'EST'is basically rolling dice.The system’s
timezonesetting is a minefield. Never rely on the server’s OS timezone setting. Always set it explicitly per session or in your database client. Your application should control this, not the whim of a server config.Dealing with input is a nightmare. You’ll get input in local time. Your job is to convert it to
timestamptzimmediately. You need to know which timezone the input is referring to. There’s no magic bullet here. You either demand timezone info from users, infer it from their profile, or make a dangerous assumption (e.g., “all input is in the ‘America/New_York’ zone”). The first option is best.
The designers made a questionable choice with the AT TIME ZONE syntax overloading, but once you understand its split personality, it’s incredibly powerful. Master these two tools—the operator and the setting—and you’ll be one of the few who can actually handle time correctly. And that, my friend, is a superpower.