Pure-reference catalog of leap-second mechanics and the bugs they cause: the 23:59:60 UTC insertion (announced ~6 months ahead by IERS Bulletin C; 27 inserted 1972-2016; abolished by 2035 per CGPM 2022), the Google/AWS leap-smear alternative, and the four bug classes a real insertion exposes - time_t non-monotonicity, negative durations, NTP cascading, and cross-node clock skew - each with a monotonic-clock fix and a freezegun simulation. Use when auditing time-sensitive code (financial timestamping, distributed logs, NTP-driven schedulers) for second-by-second progress assumptions; for the far more common daylight-saving-time transition hazards, use dst-transition-reference instead.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
A leap second is an extra second (23:59:60 UTC) inserted into the day to keep UTC within 0.9 seconds of UT1 (astronomical time). Per IERS Bulletin C (datacenter.iers.org/data/latestVersion/bulletinC.txt), Bulletin C is issued every six months, either to announce a time step in UTC or to confirm that there will be no step at the next possible date - so a leap second gets roughly six months of notice.
Important - 2035 abolition: Per the 27th CGPM resolution (2022), leap seconds will be abolished by 2035, with the gap between UTC and UT1 allowed to grow. Existing leap seconds (27 inserted between 1972 and 2026) remain in the historical record.
time_t
non-monotonicity, negative durations, NTP cascading, distributed
clock skew).time_t monotonicity.| Property | Detail |
|---|---|
| Frequency | Irregular; announced by IERS Bulletin C |
| Insertion point | Last second of UTC June 30 or December 31 |
| Direction | Almost always +1 (insert); the spec allows -1 but never used |
| Wire format | 23:59:60 UTC (a real 61st second of the minute) |
| POSIX time_t | Does not include leap seconds; time_t jumps backward by 1 or stalls |
| NTP | NTP messages signal upcoming leap; clients handle it differently |
Whether a host inserts a real 23:59:60 or smears the second over 24 hours (Google / AWS), plus the full record of the 27 leap seconds inserted between 1972 and 2026 (most recent 2016-12-31), lives in references/smear-strategies-and-history.md. The smear is operationally invisible to applications; a real insertion exposes the discontinuity that the bug classes below exploit.
time_t non-monotonicityPOSIX time_t is defined as seconds since epoch with 86400
seconds per day - no leap seconds. On a leap-second insertion,
the system clock either:
Code that relies on "1 second of CPU time = 1 second of clock" breaks.
start = time.time()
do_work() # crosses a leap second
elapsed = time.time() - start
assert elapsed >= 0 # FAILS on a real-inserted leap secondUse monotonic clocks (time.monotonic(), clock_gettime(CLOCK_MONOTONIC))
for duration measurement. Monotonic clocks ignore wall-clock
leap-second insertions.
NTP messages carry a leap-second indicator. Different OS versions handle the indicator differently - Linux historically had bugs where the leap insertion caused kernel hangs (2012 incident).
If different nodes handle leap differently (one steps, one smears), clock skew between them temporarily exceeds 1 second. Per AWS docs (aws.amazon.com/blogs/aws/look-before-you-leap-the-coming-leap-second-and-aws), AWS uses leap-smear specifically to avoid this.
| Behaviour | Test |
|---|---|
| Duration calculation uses monotonic clock | time.monotonic() consistent across leap |
| Sortable timestamps don't collide | Even with stalled time_t, sequence-numbers / nanosecond resolution avoids equal timestamps |
| Log timestamps don't skew | Compare logs across services during a known leap event |
Cron jobs at 00:00:00 UTC of leap day | Fires exactly once |
| Financial timestamping | Per-trade microsecond resolution + monotonic counter |
# Override the system clock to simulate
import freezegun
@freezegun.freeze_time('2016-12-31 23:59:59 UTC')
def test_leap_handling():
t1 = time.time()
time_pass_one_second() # mock
t2 = time.time()
assert t2 - t1 == 1 # ideal; on real leap = 0 (stalled)Note: most test libraries don't simulate actual leap-second mechanics - they're a real OS-level event. Production tests require an OS test that replays NTP leap-second indication.
Goal: prove a duration-measurement code path stays non-negative across a real-inserted leap second.
Pick the bug class: negative durations. A path that measures elapsed
time with time.time() - start can return a value below zero when the
wall clock stalls or steps back on a leap-second insertion (the
negative-durations snippet above).
Apply the fix: swap the wall clock for a monotonic clock -
time.monotonic() (or clock_gettime(CLOCK_MONOTONIC)) - which ignores
wall-clock leap-second insertions.
Write the assertion against the "Duration calculation uses monotonic clock" row of the testable-behaviours table: elapsed stays monotonic and non-negative across the leap.
start = time.monotonic()
do_work() # crosses 2016-12-31 23:59:60 UTC
elapsed = time.monotonic() - start
assert elapsed >= 0 # holds; the time.time() version can tripSimulate it with the freezegun harness above: pin the clock to the most
recent real leap second (freeze_time('2016-12-31 23:59:59 UTC')) and
advance across the inserted second. The monotonic assertion holds where
the wall-clock form (t2 - t1 == 1) reads 0 on a stalled time_t.
The caveat from "Simulating a leap second in tests" still applies: freezegun can't replay the OS-level leap indication, so this asserts the code's clock choice, not the kernel's leap behaviour - a real leap needs an OS-level test.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
time.time() - start for duration | Wall-clock; affected by leap | Use time.monotonic() |
Asserting time.time() < time.time() adjacent calls | Trips on stalled time_t | Use sequence numbers + monotonic |
| Treating UNIX time_t as continuous | Historical leap insertions broke this | Per |
| IANA leap-seconds.list | ||
| No monitoring during announced leap | Latent bugs surface in prod | Pre-leap rehearsal + monitoring |
| Per-second metrics with stale timestamps | Loss of one second of data | Sub-second granularity |
| Hardcoding 86400 in "seconds-per-day" | True only sometimes | Calendar arithmetic |
| Assuming all servers smear | Some don't | Verify per-host strategy |
dst-transition-reference.libfaketime-c,
freezegun-python,
mockclock-jvm,
timezone-test-matrix-builder.