The discrepancy that catches everyone once
The first time you see GPS time next to a Unix timestamp from the same instant, you'll notice something off. Take a snapshot of "now" from a GPS receiver and a server clock at the same moment, and they'll disagree by 18 seconds.
Not a millisecond drift. Not a timezone confusion. Exactly 18 seconds — and it'll be that exact number for the foreseeable future. Until it isn't.
Why 18, specifically?
GPS time started on January 6, 1980 at 00:00:00 UTC. At that exact moment, GPS time and UTC were equal. They have not been equal since.
The reason is that UTC inserts leap seconds, and GPS does not. UTC follows the Earth's rotation, which is gradually slowing down. To keep our clocks aligned with the actual position of the sun, the International Earth Rotation and Reference Systems Service (IERS) periodically inserts a "leap second" into UTC — making one specific minute 61 seconds long instead of 60.
Between 1980 and 2017, the IERS inserted 18 leap seconds. Each one made UTC fall one more second behind GPS time. The last leap second was inserted on December 31, 2016 at 23:59:60 UTC. There hasn't been one since, and the IERS has announced that no leap seconds will be added through at least 2026.
So as of right now, in 2026: GPS time is 18 seconds ahead of UTC. That's the formula:
GPS_seconds = UTC_seconds + 18 (currently)
The conversion gets uglier than that
You can't just always add 18. That number was 17 for most of 2015, 16 from 2012 to 2014, and so on back. If you're processing historical GPS data — drone telemetry from 2014, surveying records from 2010, satellite logs from any era — you need the leap-second offset that was in effect at that timestamp.
Here's the actual table you need:
| Leap second added | Offset after |
|---|---|
| 1981-06-30 | 1 |
| 1982-06-30 | 2 |
| 1983-06-30 | 3 |
| 1985-06-30 | 4 |
| 1987-12-31 | 5 |
| 1989-12-31 | 6 |
| 1990-12-31 | 7 |
| 1992-06-30 | 8 |
| 1993-06-30 | 9 |
| 1994-06-30 | 10 |
| 1995-12-31 | 11 |
| 1997-06-30 | 12 |
| 1998-12-31 | 13 |
| 2005-12-31 | 14 |
| 2008-12-31 | 15 |
| 2012-06-30 | 16 |
| 2015-06-30 | 17 |
| 2016-12-31 | 18 |
Notice how irregular this is. There were three leap seconds added in three consecutive years (1981–1983), then a 19-month gap, then a 28-month gap. Between 1998 and 2005 there was no leap second at all — almost 7 years. There's no formula here. The IERS adds them when Earth's rotation drift demands it.
The practical implication for your code
Most code that converts GPS to Unix time uses a constant: LEAP_SECONDS = 18. This is fine for current-day work. It's wrong if you're processing historical data — silently wrong, in the way that gives you off-by-one errors that pass all your unit tests.
If you're writing forensics code, satellite telemetry analysis, or anything that processes historical GPS data, you need a proper lookup function:
LEAP_TABLE = [
(78796800, 1), # 1972-06-30 23:59:60 UTC
(94694400, 2), # 1972-12-31
# ... continues for every leap second ...
(1483228799, 18), # 2016-12-31
]
def leap_seconds_at(unix_time):
"""Return the cumulative leap second offset at a given Unix time."""
count = 0
for ts, total in LEAP_TABLE:
if unix_time >= ts:
count = total
return count
Bonus complication: leap seconds inside a Unix timestamp
Here's where it gets genuinely strange. Unix time pretends leap seconds don't exist. By definition, every Unix day is exactly 86400 seconds long. When a leap second happens, Unix doesn't add a 86401st second to that day — it just repeats the second number that came before it. The clock goes 23:59:59 → 23:59:60 → 23:59:59 → 00:00:00 in the world of POSIX time_t, you get the second-to-last second twice.
This means there is a one-second window during every leap second insertion where the same Unix timestamp represents two different moments in time. Most production systems handle this with "leap smearing" — Google and Amazon and Cloudflare all spread the leap second out across a 24-hour window so nothing ever has to handle a 61-second minute. But the raw POSIX behavior is what it is.
What this means for the GPS-to-Unix conversion: during the second of a leap-second insertion, the mapping is ambiguous. For 99.99999% of timestamps, this doesn't matter. If you ever build a financial trading system or a transaction log that needs sub-second precision around leap second boundaries, it matters a lot.
The future
In November 2022, the International Bureau of Weights and Measures voted to abolish leap seconds by 2035. The resolution lets the UTC-UT1 gap grow up to a few seconds before being corrected with a much rarer "leap minute" or similar adjustment.
If this happens, GPS-UTC offset will stay at 18 (or whatever it is when leap seconds end) for decades. The historical lookup table will still be needed for old data, but new code can finally hard-code a constant without quietly accumulating errors.
What to do today
- For current-date work: use 18 as a constant. It will remain correct through at least 2026.
- For historical data: use a leap-second lookup table. NIST maintains the official list at hpiers.obspm.fr.
- For new GPS-aware code: store both the raw GPS time and the converted UTC time when you ingest data. Don't keep just one and assume you can compute the other later.
- Try the GPS converter: if you want to play with these conversions interactively, the GPS time converter handles both directions and shows you the week + second-of-week breakdown that most GPS receivers actually output.
Published May 12, 2026. Tagged: gps, time-sync, leap-seconds.