Postgres: Time Zone Not Recognized (Fix)
Chat2DB TeamPostgreSQL reports a bad time zone name in two different ways. In an expression:
ERROR: time zone "Mars/Olympus" not recognizedand when you set the session time zone, or when a driver sets it at connection time:
ERROR: invalid value for parameter "TimeZone": "US/Eastern"
FATAL: invalid value for parameter "TimeZone": "Asia/Calcutta"The second form is the one that surprises people, because names such as US/Eastern, Asia/Calcutta and EST worked for years and suddenly stop working after an upgrade or a move to a new container image. This guide explains where PostgreSQL gets its time zone names, why legacy names disappear, and how to fix each case. It also covers a quieter problem: offsets such as GMT+8 and UTC+08:00 that are accepted but mean the opposite of what you expect. All examples were run on PostgreSQL 18.
Where PostgreSQL Gets Time Zone Names
PostgreSQL does not hard-code time zones. It reads the IANA time zone database (tzdata) from disk. Builds from source ship their own copy under share/timezone, but most Linux packages are compiled with --with-system-tzdata and read the operating system's /usr/share/zoneinfo instead. You can check a server's build:
pg_config --configure | tr ' ' '\n' | grep tzdata
# '--with-system-tzdata=/usr/share/zoneinfo'The names available to SQL are listed in pg_timezone_names, and the abbreviations (which are a separate list) in pg_timezone_abbrevs:
SELECT name FROM pg_timezone_names WHERE name ILIKE '%new%york%';
-- name
-- ------------------
-- America/New_York
SELECT count(*) FROM pg_timezone_names;If a name is not in pg_timezone_names, SET timezone rejects it.
Cause 1: Legacy Names Removed from the OS Time Zone Database
IANA keeps old names such as US/Eastern, Asia/Calcutta (now Asia/Kolkata) and EST as "backward" links. Debian 13 (trixie) moved these links into a separate package, tzdata-legacy, which is not installed by default. The official postgres:17 and postgres:18 Docker images are based on Debian 13 at the time of writing, and they read the system time zone files. So on these images:
SET timezone = 'US/Eastern';
-- ERROR: invalid value for parameter "TimeZone": "US/Eastern"
SET timezone = 'Asia/Calcutta';
-- ERROR: invalid value for parameter "TimeZone": "Asia/Calcutta"
SET timezone = 'EST';
-- ERROR: invalid value for parameter "TimeZone": "EST"while the canonical names work:
SELECT name, abbrev, utc_offset FROM pg_timezone_names
WHERE name ILIKE '%kolkata%' OR name ILIKE '%calcutta%'; name | abbrev | utc_offset
--------------+--------+------------
Asia/Kolkata | IST | 05:30:00Only Asia/Kolkata is listed. The old alias is gone. On our test server pg_timezone_names had 487 entries without the legacy package.
This breaks applications in a nasty way, because the name often comes from the client, not from your SQL. The PostgreSQL JDBC driver sends the JVM's default time zone in the startup packet. A JVM that reports Asia/Calcutta, or a container with TZ=US/Eastern, then cannot connect at all:
FATAL: invalid value for parameter "TimeZone": "Asia/Calcutta"We reproduced the connection failure with psql's PGTZ environment variable, which works the same way.
Fix A: Install the Legacy Links on the Server
On Debian and Ubuntu based servers or images, install the package:
apt-get update && apt-get install -y tzdata-legacyPostgreSQL reads time zone files on demand, so new sessions see the names without a restart:
SET timezone = 'Asia/Calcutta';
SELECT '2026-10-27 12:00:00+00'::timestamptz; timestamptz
---------------------------
2026-10-27 17:30:00+05:30After installing the package our server listed 600 names. For a Docker image, add the line to your Dockerfile so that it survives rebuilds:
FROM postgres:18
RUN apt-get update && apt-get install -y --no-install-recommends tzdata-legacy \
&& rm -rf /var/lib/apt/lists/*Fix B: Use Canonical Names Everywhere
The long-term fix is to stop using aliases. Use America/New_York instead of US/Eastern, Asia/Kolkata instead of Asia/Calcutta, America/Panama (or a real location) instead of EST, and UTC for UTC. For Java clients, set the JVM time zone explicitly, for example -Duser.timezone=UTC or -Duser.timezone=Asia/Kolkata, so the driver sends a name every server knows.
Check what is stored in the configuration too. A bad name in ALTER DATABASE ... SET timezone or ALTER ROLE ... SET timezone is rejected when you run the command:
ALTER DATABASE enc SET timezone = 'Asia/Calcuta';
-- ERROR: invalid value for parameter "TimeZone": "Asia/Calcuta"But a setting that was valid on the old server keeps living in pg_db_role_setting and is restored by pg_dump. Before an upgrade, list the per-database and per-role settings:
SELECT coalesce(d.datname, '*') AS database, coalesce(r.rolname, '*') AS role, s.setconfig
FROM pg_db_role_setting s
LEFT JOIN pg_database d ON d.oid = s.setdatabase
LEFT JOIN pg_roles r ON r.oid = s.setrole;Cause 2: Typos and Spaces
Names are matched case-insensitively, so utc and america/new_york work, but every other character must be exact:
SET timezone = 'America/New York';
-- ERROR: invalid value for parameter "TimeZone": "America/New York"
SELECT '2026-10-27 12:00:00 Europe/Pariss'::timestamptz;
-- ERROR: time zone "europe/pariss" not recognizedIANA names use underscores instead of spaces, and the city in the name is a representative city, not a country. When in doubt, search the catalog:
SELECT name FROM pg_timezone_names WHERE name ILIKE '%paris%';Cause 3: Abbreviations Are Not Time Zones
PST, EST, IST and CST are abbreviations, and PostgreSQL treats them differently from full names. In date/time input and in AT TIME ZONE, abbreviations are looked up in the timezone_abbreviations list and mean a fixed offset:
SELECT '2026-10-27 12:00:00+00'::timestamptz AT TIME ZONE 'PST' AS pst,
'2026-10-27 12:00:00+00'::timestamptz AT TIME ZONE 'IST' AS ist; pst | ist
---------------------+---------------------
2026-10-27 04:00:00 | 2026-10-27 14:00:00IST gave 14:00, two hours ahead, not India's five and a half. In the default abbreviation set, IST is Israel Standard Time:
SELECT abbrev, utc_offset, is_dst FROM pg_timezone_abbrevs
WHERE abbrev IN ('IST', 'PST', 'EST', 'CST', 'BST') ORDER BY abbrev; abbrev | utc_offset | is_dst
--------+------------+--------
BST | 01:00:00 | t
CST | -06:00:00 | f
EST | -05:00:00 | f
IST | 02:00:00 | f
PST | -08:00:00 | fThe TimeZone setting does not accept these abbreviations at all, which is why SET timezone = 'PST' fails. Even where an abbreviation works, it is a fixed offset with no daylight saving time. PST in July is still UTC-8, while Los Angeles is on UTC-7. Use full names for anything that crosses a DST change.
Cause 4: POSIX Offsets Mean the Opposite
This case raises no error, which makes it dangerous. PostgreSQL accepts POSIX-style zone specifications such as GMT+8 and UTC+08:00. In POSIX notation the sign is inverted: positive means west of Greenwich.
SELECT '2026-10-27 12:00:00+00'::timestamptz AT TIME ZONE 'GMT+8' AS posix_gmt_plus_8,
'2026-10-27 12:00:00+00'::timestamptz AT TIME ZONE 'Etc/GMT-8' AS etc_gmt_minus_8,
'2026-10-27 12:00:00+00'::timestamptz AT TIME ZONE 'Asia/Shanghai' AS shanghai,
'2026-10-27 12:00:00+00'::timestamptz AT TIME ZONE INTERVAL '+08:00' AS interval_form; posix_gmt_plus_8 | etc_gmt_minus_8 | shanghai | interval_form
---------------------+---------------------+---------------------+---------------------
2026-10-27 04:00:00 | 2026-10-27 20:00:00 | 2026-10-27 20:00:00 | 2026-10-27 20:00:00GMT+8 produced 04:00, which is UTC minus 8 hours. The same happens with the string '+08:00' and with 'UTC+08:00'. Setting the session zone to a bare offset string is just as misleading:
SET timezone = '+05:30';
SELECT '2026-10-27 12:00:00+00'::timestamptz AS shown; shown
---------------------------
2026-10-27 06:30:00-05:30The output even prints -05:30, which tells you what happened. If you need a fixed offset with the ISO sign convention, use an INTERVAL (as in the last column above) or a named zone. For UTC+8 with no DST, Asia/Shanghai or Asia/Singapore express the intent clearly.
Where Time Zones Come From in a Session
When a session's time zone is wrong or invalid, check these sources, from highest to lowest priority:
| Source | How to see it |
|---|---|
SET timezone in the session | SHOW timezone; |
Driver startup options (JDBC sends the JVM zone, PGTZ for libpq) | Connection string, JVM user.timezone, environment |
ALTER ROLE ... SET timezone | pg_db_role_setting query above |
ALTER DATABASE ... SET timezone | Same query |
timezone in postgresql.conf | SELECT setting, source FROM pg_settings WHERE name = 'TimeZone'; |
The server default is chosen by initdb from the machine's time zone when the cluster is created. Official Docker images default to Etc/UTC.
Store timestamptz, Convert at the Edges
Most time zone trouble goes away with one design rule: store points in time as timestamptz and convert only when you display them. timestamptz stores UTC internally. The session TimeZone only affects how values are parsed and printed, so a wrong session zone shows wrong local times but never corrupts the stored instants. Plain timestamp columns, on the other hand, depend on whoever wrote them using the right zone.
To display a stored value in a user's zone, pass the zone name per query instead of changing the session:
SELECT order_id, created_at AT TIME ZONE 'America/New_York' AS created_local
FROM orders;When you check time zone settings across several environments, it helps to run the same diagnostic queries everywhere. In Chat2DB (opens in a new tab) you can save the pg_db_role_setting and pg_timezone_names queries as snippets and run them against each connection.
FAQ
What is the difference between "time zone not recognized" and "invalid value for parameter TimeZone"?
They report the same problem in different places. time zone "..." not recognized comes from date/time input and AT TIME ZONE. invalid value for parameter "TimeZone" comes from setting the TimeZone parameter with SET, ALTER ROLE, ALTER DATABASE, postgresql.conf or a connection option. When it happens during connection startup it is reported as FATAL and the connection fails.
Why did US/Eastern stop working after I upgraded my Docker image?
The newer image is based on Debian 13, where legacy names are in the optional tzdata-legacy package, and PostgreSQL reads the system time zone files. Install tzdata-legacy in the image, or switch to the canonical name America/New_York.
Is UTC+8 east or west of Greenwich in PostgreSQL?
As a time zone string, UTC+8 and GMT+8 follow POSIX rules and mean eight hours west of Greenwich (UTC-8). Use INTERVAL '+08:00' or a named zone such as Asia/Shanghai for UTC+8.
