Skip to content
Back to Blog
Tutorials

What Exactly Lives Inside a PostgreSQL timestamp Column?

A plain-English guide to how PostgreSQL stores timestamp vs timestamptz, why timezones bite, and how to choose the right type for your use case.

Go Tools Team 6 min read

What Exactly Lives Inside a PostgreSQL timestamp Column?

PostgreSQL maintains both timestamp and timestamptz as a single 64-bit integer: the number of microseconds since 1970-01-01 00:00:00 UTC. The distinction emerges only during data formatting for human consumption.

Why Does This Trip People Up?

  • Two columns, one date… two different query results
  • Your app inserts 2025-07-29 10:00, but another team sees 02:00
  • The frontend renders an ISO string that doesn’t match the backend log

Two Cans of Peaches: One Plain, One Labeled

Data TypeOfficial NameStored ValueWhat Happens on SELECT
timestamptimestamp without time zoneraw microsecond countSent back unchanged — Postgres never guesses a timezone
timestamptztimestamp with time zonesame microsecond countPostgres applies the session TimeZone setting just before sending the text

Analogy

  • timestamp = a jar of peaches with no origin label. You know it’s fruit, but not where it was canned.
  • timestamptz = a jar proudly stamped “Made in UTC+8.” Anyone opening it can decide whether to convert the nutrients panel.

Under the Hood: It’s Just a Giant Number

2000-01-01 00:00:00 UTC  → 0
2000-01-01 00:00:01 UTC  → 1 000 000
  • Unit: microseconds (one-millionth of a second)
  • Range: 4713 BC – 294276 AD — Indiana Jones approved
  • Storage for timestamp and timestamptz is identical; interpretation differs

A 15-Second Demo

-- Client thinks in Shanghai time
SET TimeZone = 'Asia/Shanghai';

CREATE TABLE demo (
  created_ts timestamp,
  created_tz timestamptz
);

INSERT INTO demo VALUES ('2025-07-29 10:00', '2025-07-29 10:00');
QueryResultWhy
SELECT created_ts FROM demo;2025-07-29 10:00:00Raw value, no TZ math
SELECT created_tz FROM demo;2025-07-29 10:00:00+08Tag applied on output
SET TimeZone = 'UTC'; then select2025-07-29 02:00:00+00Same instant, new lens

Common Pitfalls & Quick Fixes

1. Different users, different clocks

  • Cause: clients use different TimeZone settings with timestamptz
  • Fix: either keep everything timestamp + agree on one zone, or enforce SET TimeZone = 'UTC' at connection init

2. Storing “wall time” but picked the wrong type

  • Business calendars (store hours, due dates) should use timestamp
  • Cross-border workflows (orders, logs) should store UTC in timestamptz

3. APIs that drift

  • Always ship timestamptz as ISO-8601 strings with the offset (Z or +08:00)
  • Let the UI format locally

Cheat Sheet: Which One Should I Use?

Local calendar only → timestamp
Anything global    → timestamptz (store UTC)
  • Financial reports, class schedules → timestamp
  • Audit logs, e-commerce orders → timestamptz

Verify in Seconds with Go Tools

NeedToolHow-to
Inspect the epoch value from SQLEpoch ConverterPaste 1690622400, hit Convert
Compare two timezones at a glanceTimezone ConverterEnter 10:00 Asia/Shanghai
Tidy bulk JSON with time fieldsJSON FormatterDrop in the payload, prettify & scan

All utilities run entirely in your browser — no data ever leaves your machine.

Wrap-up

  • Both Postgres time types are microsecond counters; the label is the whole difference
  • Choosing the wrong one means puzzling timestamps and broken math
  • Test, convert, and sanity-check with the right tools to save hours of debugging

Related Articles

View all articles