UTC is not GMT, and offset is not zone
Four words that look like synonyms are the root of nearly every date bug. UTC (Coordinated Universal Time) is a time scale, the ruler the world measures instants against. GMT (Greenwich Mean Time) is the historical ancestor of that ruler, used today as a legacy zone (the United Kingdom’s in winter). An offset is just a numeric shift relative to UTC, like −03:00. And an IANA zone, like America/Sao_Paulo, is a living rule: it says which offset applies on each date, including whether, when and for how long there is daylight saving. Confusing the ruler, the number and the rulebook is where most calendars and schedulers break.
- UTC
- The reference civil time scale. It has no location: 12:00 UTC is the same instant across the whole planet.
- GMT
- Greenwich Mean Time. Historically the basis of world time; today, in practice, a zone name (UTC+00:00 in British winter). For engineering, prefer to say UTC.
- TAI
- International Atomic Time: the uninterrupted count of atomic seconds, with no adjustments. UTC is TAI minus a whole number of leap seconds.
- Offset
- The difference between a local clock and UTC at one instant, like −03:00 or +05:30. It is a fact about one moment, not about the future.
- IANA zone
- An Area/Location identifier (America/Sao_Paulo) carrying a region’s past and future offset rules, daylight saving included.
The distinction that matters most is offset versus zone. −03:00 says where the clock is now; America/Sao_Paulo says where it will be next year, even if the law changes. Storing an offset is taking a photo; storing a zone is storing the rules that generate every photo, past and future. That difference is the axis of everything that follows.
A short history of coordinated time
- 1884International Meridian Conference
In Washington, 25 nations pick the Greenwich meridian as zero longitude; GMT becomes the world time standard.
- 1960International time coordination
Worldwide coordination of time and frequency broadcasts begins, the embryo of UTC.
- 1967The second turns atomic
The SI second is defined by the caesium atom; "UTC" settles in as the official abbreviation.
- 1972UTC takes its current form
UTC starts stepping in whole 1-second jumps over TAI; the first leap second is inserted on 30 June 1972.
- 1980sThe zone database is born
The time zone database (tz / zoneinfo) appears, gathering each region’s local history; today it is maintained by IANA.
- 2019Brazil ends daylight saving
Decree No. 9,772 of 25 April 2019 revokes daylight saving; the last period applied had ended in February 2019.
- 2022Retiring the leap second
Resolution 4 of the 27th CGPM decides to let the UT1−UTC difference grow: leap seconds are to be discontinued by or before 2035.
UTC is TAI plus a whole number of leap seconds, inserted irregularly to keep the civil clock within about 0.9 second of the Earth’s actual rotation (UT1). Because the Earth does not spin at a predictable rate, those jumps could never be scheduled years ahead, which torments any system that assumes every minute has exactly 60 seconds. That is why the General Conference on Weights and Measures (CGPM) decided in 2022 to stop inserting leap seconds by 2035, accepting that UTC will drift a little further from the Earth’s rotation in exchange for predictability.
The IANA database and why America/Sao_Paulo
Every operating system, language and runtime consults the same source of truth about zones: the IANA time zone database (also called tz, zoneinfo or the Olson database). Its identifiers follow the Area/Location pattern, a continent or ocean plus the largest representative city: America/Sao_Paulo, Europe/London, Asia/Tokyo. Why the city and not "BRT"? Because the abbreviation is ambiguous (is BST British Summer Time or Bangladesh Standard Time?) and, worse, it encodes the current offset, which changes. A city name is politically stable and lets the database attach the region’s full history and future rules to it. When a government changes the rules, as Brazil did in 2019, a new tzdata release ships and every updated system learns the new behavior. Releases have no fixed calendar: they come out when politics forces them, often several times a year, named 2025a, 2025b, and so on.
An example makes the offset-versus-zone difference concrete. São Paulo (America/Sao_Paulo) is at UTC−3 all year now, since it no longer has daylight saving. New York (America/New_York) alternates: UTC−4 in the Northern Hemisphere summer (EDT) and UTC−5 in winter (EST). Set a recurring call "at 9 a.m. in São Paulo" and watch what happens to New York’s clock as the season turns:
| Date | São Paulo (UTC−3) | In UTC | New York |
|---|---|---|---|
| 15 Jul 2025 | 09:00 | 12:00 | 08:00 (EDT, UTC−4) |
| 15 Jan 2025 | 09:00 | 12:00 | 07:00 (EST, UTC−5) |
The middle step is always the same: 09:00 in São Paulo is 09:00 + 3 = 12:00 UTC on both days. What changes is the trip back: in July, New York is at −4 from UTC, so 12:00 − 4 = 08:00; in January it is at −5, so 12:00 − 5 = 07:00. The gap between the two cities is 1 hour in the Northern summer and 2 hours in winter. Had you stored only the offset from the first reading, the second would come out wrong. Convert any pair of cities in the time zone converter below.
ISO 8601, RFC 3339 and the instant on the wire
To travel between systems, a time has to name an instant unambiguously, and that requires a zone reference. ISO 8601 is the date-and-time format standard; RFC 3339 is its lean internet profile, adopted in APIs, logs and JSON. A string is an instant only if it carries an offset or the Z suffix. Here are the parts:
2025-07-15T09:00:00-03:00 <- RFC 3339 / ISO 8601
2025-07-15 date (ano-mes-dia / year-month-day)
T separator between date and time
09:00:00 local time (hh:mm:ss)
-03:00 offset from UTC (3 h behind)
= 2025-07-15T12:00:00Z same instant; Z = UTC = +00:00Here lies the reason offset ≠ zone: from the string 2025-07-15T09:00:00−03:00 you know the instant, but not the rules. You cannot tell whether that clock will read −03:00 in January, because you do not know if it is São Paulo (fixed offset) or a zone with daylight saving. An offset is a fact about a single moment; a zone is the rulebook that projects the future. To reconstruct future local times, then, you need the IANA identifier, not a number.
A second example, now starting from a Unix timestamp, the seconds elapsed since 1970-01-01T00:00:00Z. A log records the value 1,700,000,000. Converting, we reach 2023-11-14T22:13:20Z. That instant is unique; what changes is the wall clock in each place:
Notice that in Tokyo the date rolls forward to November 15: adding 9 hours to 22:13 crosses midnight. It is the same instant, 1,700,000,000 on the wire, seen by three clocks. Read any Unix timestamp in the timestamp converter and do duration arithmetic in the hours calculator.
What about the year 2038 problem?
Systems that store Unix time in a signed 32-bit integer only reach 2,147,483,647 (2³¹ − 1) seconds after the epoch. That limit is hit at 03:14:07 UTC on 19 January 2038; the next second, the counter overflows and "wraps" to 20:45:52 UTC on 13 December 1901.
The fix is to store in 64 bits, which pushes the overflow billions of years out, or to use an ISO 8601 string. It is worth auditing embedded and legacy systems that are rarely updated; they are the most exposed.
Times that do not exist and that happen twice
Where daylight saving still exists, each transition creates an anomaly in the local clock. In the United States, the rule in force since 2007 is: spring forward at 2 a.m. on the second Sunday of March and fall back at 2 a.m. on the first Sunday of November. In 2025, that fell on March 9 and November 2. On those two dates, local time stops being a continuous line.
| Transition | What happens to the clock | Example (2025) |
|---|---|---|
| Spring forward | Jumps from 02:00 straight to 03:00; the hour in between does not exist. | Mar 9: 02:30 never happens. |
| Fall back | Falls from 02:00 back to 01:00; the hour in between occurs twice. | Nov 2: 01:30 happens twice. |
The consequences are practical and unpleasant. A job scheduled for 02:30 on the spring transition day may never fire; on the fall transition day, it may fire twice. The string "2025-11-02 01:30" in America/New_York is genuinely ambiguous, it maps to two different UTC instants (one still in EDT, one already in EST), and the parser needs a rule to choose. This is exactly why wall-clock schedulers demand care at transitions; if you define jobs with cron expressions, test their behavior on those two days of the year.
The golden rule of storage
Put it all together and the storage strategy resolves itself. There are two models, and the choice between them decides whether your system survives a change of law. Store the instant in UTC plus the IANA zone, or store the local time plus an offset, but the two are not interchangeable:
Store UTC + IANA zone
- The UTC instant never changes meaning, even if the zone’s law changes.
- The identifier (America/Sao_Paulo) rebuilds the correct local time on any date.
- Display becomes a calculation; the stored value is canonical and future-proof.
Store local time + offset
- The offset freezes a snapshot: if the rule changes, the recomputed time comes out wrong.
- It cannot tell the two "01:30" of fall apart; it is ambiguous by construction.
- It only serves to record what a clock read at that instant, not to schedule the future.
- Store instants (logs, "created at", "paid at") in UTC, ideally as timestamp with time zone or an ISO 8601 string with Z.
- Store the IANA zone identifier (America/Sao_Paulo), not the offset, when the event is future and recurring.
- Never store only the offset (−03:00) for a future event: it cannot rebuild rules that may still change by law.
- Use the user’s zone only in the display layer; convert at the edge and keep UTC at the core.
- Test the system exactly on the daylight-saving transitions, the hour that does not exist and the one that happens twice.
- Prefer 64 bits (or an ISO string) over 32-bit Unix seconds, to avoid the 2038 overflow.
Frequently asked questions
Are UTC and GMT the same thing?
Should I store dates in UTC or local time?
Why use America/Sao_Paulo instead of "BRT" or −03:00?
What is the year 2038 problem?
Does Brazil still have daylight saving time?
UTC is the ruler, the offset is a snapshot and the IANA zone is the rulebook. Store instants in UTC, store America/Sao_Paulo (not −03:00) when the event is future and recurring, and never reduce a recurring commitment to a single instant. Test your code on the daylight-saving transitions, the hour that does not exist and the one that happens twice, and dates stop breaking in production.
Sources & references
- RFC 3339, Date and Time on the Internet: Timestamps
- IANA, Time Zone Database (tz / zoneinfo)
- BIPM, Resolution 4 of the 27th CGPM (2022), on the future of UTC
- Brazilian Chamber of Deputies, Decree No. 9,772 of 25 April 2019
- The Open Group, POSIX, “Seconds Since the Epoch” (§4.16)
- Protocols of the International Meridian Conference (Washington, 1884)