DevKitLab Logo DevKitLab
Time Zones / UTC / ISO 8601 / Date & Time

UTC, GMT, ISO 8601, and Unix Time: What's the Difference?

UTC, GMT, a Z-suffixed string, +08:00, a ten-digit number — they get used as if they were the same thing, and swapping them is how time bugs slip in. They aren't variants of 'the time'; they sit on four different layers. This untangles the reference scale, the zone, the offset, and the two ways to write an instant down.

You keep meeting the same moment wearing different clothes. A teammate says “just store everything in UTC.” An API hands you "2026-07-16T09:00:00Z". A config file wants GMT+8. A database column holds 1700000000. A calendar invite says 2026-07-16T17:00:00+08:00. They all seem to be saying “the time,” so it feels safe to treat them as interchangeable — read the Z one as GMT, drop the offset off the calendar string, store the API value in the epoch column without converting. And then something is off by eight hours, or a meeting lands on the wrong day next spring, and nobody can see why.

Here’s the reframing that makes it all line up: these are not five names for the same thing. They sit on four different layers, each answering a different question. One is a reference scale — the line every clock is measured against. One is a time zone — a named region whose rules say how far its clocks sit from that line, and when. One is an offset — the fixed +HH:MM those rules produce at a particular instant. And two are notations — ways to write a single instant down, one as text and one as a number. Confusing them isn’t a small vocabulary slip; it’s mixing up a ruler, a measuring rule, and the measurement it produces.

This article sorts the pile into those layers. What UTC actually is (a reference scale, not a place). Why GMT means two different things and shouldn’t stand in for either UTC or “UK time.” The distinction that causes the most real bugs — a time zone like Asia/Shanghai versus the offset +08:00 it happens to produce today, and the GMT+8 / Etc/GMT+8 traps that live in the gap. What ISO 8601 is (a way of writing, not a time), when its strings really do sort by time, and what the Z means. And finally how they snap together, so “store it in UTC,” “give me an ISO string,” and “1700000000” stop competing and start describing one instant from different angles. It’s the companion to why a Unix timestamp shows the wrong time; that one was about the number, this one is about all the names around it.

The one idea: four layers, not five synonyms

Sort the terms into four jobs and the confusion drains away:

  • The reference scale — UTC. One agreed line that every other clock is defined relative to. Not a place; nobody’s wall clock has to show it.
  • The time zone — Asia/Shanghai, Europe/London. A named region carrying a history of rules: its offsets, its daylight-saving transitions, and every past change to them. These live in the IANA time zone database.
  • The offset — +08:00, -05:00, Z. A fixed number of hours from UTC at one instant — the value a zone’s rules produce for a given moment, not a zone itself.
  • The notations — ISO 8601 text and the Unix epoch number. Two ways to pin one instant down: ISO 8601 writes it as human-readable text, the Unix/POSIX epoch counts it as a number. An ISO string carrying Z or an offset, and a Unix/POSIX count, both identify an instant relative to UTC.

So there’s one reference (UTC), zones that carry rules, the offsets those rules produce at an instant, and two spellings of a moment. GMT is the one term that doesn’t get its own layer, because — as we’ll see — it has drifted between two of them.

UTC: the reference scale, not a zone you live in

UTC — Coordinated Universal Time — is a reference time scale, not a time zone. It’s the master line the world agrees to measure time against, maintained from a global ensemble of atomic clocks and nudged by the occasional leap second so it never drifts far from the Earth’s actual rotation. Every civil time on the planet is defined as UTC plus or minus some offset. (The initialism is deliberately neither the English word order nor the French one — a committee compromise so no language “owned” it.)

Two consequences matter for everyday code:

  • Nobody has to display UTC, but everyone can agree on it. Tokyo shows UTC+9 on the wall, Chicago shows UTC−6 or −5, and both are the same instant expressed against the same reference. That’s why “store confirmed instants in UTC” is good advice: it’s the one reading two servers, or two users, will never disagree about. (Note the word confirmed — a future local appointment is the exception, and we’ll get to why.)
  • UTC never observes daylight saving. It’s a fixed reference; it doesn’t spring forward. DST is something zones add on top. So when a stored UTC time looks “an hour off in summer,” UTC didn’t move — a zone conversion did.

One piece of live context, since it’s 2026: the leap-second scheme is itself due to change. In 2022 the General Conference on Weights and Measures resolved to raise the tolerance on UT1−UTC — the gap between UTC and the Earth’s rotation — in or before 2035, a change expected to end the need for regular leap seconds. So “the occasional leap second” is a scheme already set to change, not a fixed law of the universe. The mistake to retire either way: treating UTC as “the time in London” or “GMT.” It’s the line the wall clocks are measured from, not one of them.

GMT: one label, two meanings

GMT — Greenwich Mean Time — is not a clean synonym for UTC, because it means two different things.

  1. Historically, a time scale: mean solar time at the Greenwich meridian, tied to the Earth’s rotation (closely related to what’s now called UT1). It was the world’s reference from the 1880s until UTC, defined by atomic clocks, took over in the 1970s.
  2. In everyday modern use, a zero-offset label: a stand-in for UTC+00:00. When someone says “14:00 GMT,” they almost always mean UTC, and the two agree to well under a second — close enough whenever you don’t care about sub-second precision.

For logs and casual coordination, reading “GMT” as UTC is harmless. But keep two guardrails:

  • For a reference to anchor stored data, say UTC, not GMT — UTC is the precise modern scale, GMT is the fuzzy word that sometimes means a historical scale and sometimes a zero-offset label.
  • “GMT” is not “UK time” year-round. The United Kingdom uses GMT in winter but shifts to BST (British Summer Time, UTC+1) in summer. So in code, never hard-code GMT to mean Britain — use the IANA zone Europe/London, which knows about BST; GMT the label never moves, but London’s wall clock does.

The one that actually bites: a time zone is not an offset

This is the distinction worth committing to memory, because it’s the source of the subtle, ship-to-production time bugs. Asia/Shanghai and +08:00 are not two ways of saying the same thing — they’re two different layers.

  • A time zone — Asia/Shanghai, Europe/Berlin, America/New_York — is a named region with a whole history of rules: its current offset, whether and when it observes daylight saving, and every past change to those rules. They live in the IANA time zone database that ships with your OS and language runtime — and that database is updated several times a year, often because a government changes its clocks by political decision, so a zone’s rules are not frozen.
  • An offset — +08:00, -05:00, Z — is the fixed result a zone’s rules produce at one instant. It tells you how far ahead or behind UTC a moment was, and nothing else.

Why the difference is not academic: an offset can’t tell you what the offset will be at a different instant. Berlin is +01:00 in winter and +02:00 in summer. Store a future event as “next March, 9:00 +01:00” and you’ve frozen today’s offset onto a date that may fall after the spring DST change — so the meeting fires an hour off. To store a future local event correctly you must keep the zone name plus the local time (Europe/Berlin, 09:00), so the offset is computed from the rules in force then. (Zones also make some wall-clock times vanish or repeat across a DST switch — the spring-forward hour that never happens, the fall-back hour that happens twice — which is its own source of “off by an hour” bugs; the companion article walks through those.)

Three traps live right in this gap, and all three are real:

  • GMT+8, +08:00, and Etc/GMT+8 are not the same string. +08:00 is the ISO/RFC offset (eight hours ahead of UTC). GMT+8 is an informal label some libraries accept. But Etc/GMT+8 in the IANA/POSIX database has an inverted sign — it means UTC−8, not +8. Reach for Etc/GMT+8 expecting Shanghai and you’ll land in the Pacific. For interchange, prefer +08:00; when you need rules, use a real zone like Asia/Shanghai.
  • Zone abbreviations are ambiguous — never parse them. CST is US Central Time, and China Standard Time, and Cuba Standard Time. IST is India, Israel, and Ireland. They carry no reliable offset; treat them as display decoration only, and store the IANA name.

When you need to see how one instant lands across several named zones — each zone’s real rules and DST applied — that’s what a time zone converter is for: it resolves the zone rather than assuming a frozen offset.

ISO 8601: a way of writing, not a time

ISO 8601 is a text format for dates and times — a notation, not a clock. It exists so a written moment is unambiguous across languages and locales, instead of the 03/04/05 guessing game. Its shape:

2026-07-16T09:00:00Z          ← date, "T", time, and a UTC designator
2026-07-16T17:00:00+08:00     ← the same instant, written with an offset

The pieces that matter: the date is big-endian (YYYY-MM-DD), a literal T separates date from time, and the part people forget is the most important — the designator on the end, either Z or an offset like ±HH:MM, is what ties the text to the reference. Without it, the string is only a local date-time with no anchor — the application has to supply the missing zone, and a parser may fall back to a default (often the runtime’s local zone) or reject the value outright. (That silent fallback to local time is exactly the “off by a day” bug in the companion article.)

A common half-truth is worth correcting here: “ISO strings sort chronologically as plain text.” They do — but only once they’re normalized: same format, same precision, zero-padded fields, and the same offset, ideally all converted to UTC Z. Mix offsets and text order stops matching time order — 2026-07-16T20:00:00+08:00 sorts after 2026-07-16T13:00:00Z as text, yet it’s the earlier instant (12:00Z versus 13:00Z). So the “sortable” superpower is real, but it’s a property of normalized UTC strings, not of ISO text in general.

Two more notes save real confusion. First, when an API says “send an ISO 8601 timestamp,” it almost always means RFC 3339, the stricter internet profile of ISO 8601 — a full date and time with a required offset (Z or numeric). Plain ISO 8601 also permits things you rarely want in an API, like week dates (2026-W29) and durations (P3DT4H). Second, Z and +00:00 are exactly equal: both say “offset zero — this is UTC."

"Z” and “Zulu”: the smallest piece

The lone Z at the end of ...09:00:00Z is just the designator for offset zero, i.e. UTC. Read aloud in aviation and the military it’s “Zulu” — the phonetic-alphabet word for the letter Z — so you’ll hear an instant called “0900 Zulu.” Z and +00:00 are the two standard ways to write offset zero; “UTC” is the plain-language name for that meaning, not a suffix you can append (2026-07-16T09:00:00UTC is not a valid timestamp). All three point at the same anchor, but only two of them are syntax. The only trap is forgetting the Z and assuming it — a string that should have carried it but didn’t is not UTC, it’s ambiguous.

The number: Unix (POSIX) epoch

The database column holding 1700000000 is the other notation — an instant written as a count, not text. Its rules are worth stating precisely, because loose talk of “the epoch number” is where digit-count confusion starts:

  • The count runs from the Unix epoch, 1970-01-01T00:00:00Z — anchored, like everything else, to UTC.
  • POSIX time is counted in seconds, and it deliberately ignores leap seconds (every day is treated as exactly 86,400 seconds), which keeps the number reversible into a date with plain arithmetic.
  • The unit is not universal. 1700000000 is seconds (10 digits today); JavaScript’s Date.now() returns milliseconds (1700000000000, 13 digits); some systems use microseconds or nanoseconds. Handing seconds to something expecting milliseconds is the classic bug the companion article is built around.
1700000000       Unix/POSIX seconds
1700000000000    Unix milliseconds (JavaScript)

To move between that count and a human-readable date — and to check whether a value is seconds or milliseconds — a Unix timestamp converter does exactly that leg.

How they actually fit together

Put one instant through all of them and the layers stop competing:

instant:      a specific moment
epoch:        1700000000                    (seconds, counted from the UTC reference)
ISO 8601 (Z): 2023-11-14T22:13:20Z          (text, anchored to UTC)
ISO 8601 (+): 2023-11-14T14:13:20-08:00     (same instant; the offset Los Angeles produced that day)
zone:         "Asia/Shanghai" → 06:13 the next day on the wall clock

Every line is the same moment. The epoch number and the two ISO strings are three notations for it; the zone’s rules produce the offset that turns it back into someone’s wall clock; UTC is the reference all of them lean on. This is why the layered advice is consistent rather than contradictory: “store it in UTC” means keep the instant anchored to the reference — an epoch number or a Z string both do that; “give me an ISO string” asks for the text notation; “convert to the user’s zone” happens once, at display, using the zone’s rules.

The decisions, in one place

Most real questions come down to what to persist. This table covers almost all of them (and a separate article digs into choosing between an integer timestamp and an ISO string, and which native column type to reach for):

What you’re storingStore this
A moment that already happened (created_at, a log, an audit trail)A UTC instant — a Unix timestamp, or an ISO 8601 string ending in Z
Any fixed point in timeThe UTC instant (epoch number or Z string)
A future local event (“9:00 in Berlin next March”)Local date-time + the IANA zone name (Europe/Berlin) — never a bare offset
A recurring calendar eventLocal time + IANA zone + the recurrence rule
Something to show a userNothing new — convert the stored UTC instant to the viewer’s zone at the last step

Two habits make the table stick. First, “store everything in UTC” is almost right but too broad: store confirmed instants in UTC, but store future local schedules as local time plus an IANA zone, or a rule change will silently move them. Second, when the terms blur, ask which layer you’re in — reference, zone, offset, or notation — and the answer usually falls out.

The through-line, and the tie back to the number itself: UTC is the reference scale; a time zone carries the rules; an offset is what those rules produce at an instant; and the epoch count and the ISO string are two ways to write that instant down. Once each term has a layer, “UTC vs GMT vs ISO 8601” stops being a versus — they were never answering the same question.

References