Guides

Handling Leap Seconds

Introduction

Leap seconds are occasional one-second adjustments to Coordinated Universal Time (UTC) to keep it synchronized with Earth's rotation. They represent one of the most complex aspects of time handling in software development.

Quick Summary: UTC adds leap seconds to stay within 0.9 seconds of UT1 (solar time). As of January 2026, 37 leap seconds have been added since 1972, making UTC 37 seconds behind International Atomic Time (TAI).

What Are Leap Seconds?

Definition

A leap second is a one-second adjustment applied to UTC to account for:

  1. Earth's decelerating rotation: Earth's rotation is gradually slowing down
  2. Irregular rotation rate: Earth's rotation speed varies unpredictably
  3. UT1-UTC divergence: Keeping UTC within ±0.9 seconds of solar time (UT1)
Leap Second Formula:
  If UT1 - UTC > 0.9 seconds → Add positive leap second
  If UT1 - UTC < -0.9 seconds → Add negative leap second

Result: UTC stays synchronized with Earth's rotation

How Leap Seconds Work

When a leap second is added, the last minute of a UTC day has 61 seconds instead of 60:

Normal Day (no leap second):
  23:59:58 UTC
  23:59:59 UTC
  00:00:00 UTC (next day)

Leap Second Day:
  23:59:58 UTC
  23:59:59 UTC
  23:59:60 UTC  ← Leap second!
  00:00:00 UTC (next day)

Note: Negative leap seconds have never occurred in practice, though they're theoretically possible if Earth's rotation suddenly accelerated.

History of Leap Seconds

Timeline

YearEventUTC-TAI Offset
1972First leap second added+10 seconds
1972-198412 leap seconds added+22 seconds
1985-19958 leap seconds added+29 seconds
1996-20053 leap seconds added+32 seconds
2008-20163 leap seconds added+35 seconds
2017Last leap second+36 seconds
2025Future leap second+37 seconds

Recent Leap Seconds

All Leap Seconds (1972 - 2025):
  - 1972-06-30: +1 second (UTC-TAI = +11s)
  - 1972-12-31: +1 second (UTC-TAI = +12s)
  - 1973-12-31: +1 second (UTC-TAI = +13s)
  - 1974-12-31: +1 second (UTC-TAI = +14s)
  - 1975-12-31: +1 second (UTC-TAI = +15s)
  - 1976-12-31: +1 second (UTC-TAI = +16s)
  - 1977-12-31: +1 second (UTC-TAI = +17s)
  - 1978-12-31: +1 second (UTC-TAI = +18s)
  - 1979-12-31: +1 second (UTC-TAI = +19s)
  - 1981-06-30: +1 second (UTC-TAI = +20s)
  - 1982-06-30: +1 second (UTC-TAI = +21s)
  - 1983-06-30: +1 second (UTC-TAI = +22s)
  - 1985-06-30: +1 second (UTC-TAI = +23s)
  - 1987-12-31: +1 second (UTC-TAI = +24s)
  - 1988-12-31: +1 second (UTC-TAI = +25s)
  - 1989-12-31: +1 second (UTC-TAI = +26s)
  - 1990-12-31: +1 second (UTC-TAI = +27s)
  - 1992-06-30: +1 second (UTC-TAI = +28s)
  - 1993-06-30: +1 second (UTC-TAI = +29s)
  - 1994-06-30: +1 second (UTC-TAI = +30s)
  - 1995-12-31 +1 second (UTC-TAI = +31s)
  - 1997-12-31 +1 second (UTC-TAI = +32s)
  - 1998-12-31 +1 second (UTC-TAI = +33s)
  - 1999-12-31 +1 second (UTC-TAI = +34s)
  - 2000-12-31 +1 second (UTC-TAI = +35s)
  - 2005-12-31 +1 second (UTC-TAI = +36s)
  - 2008-12-31: +1 second (UTC-TAI = +37s)

Future of Leap Seconds

The International Telecommunication Union (ITU) is considering abolishing leap seconds by 2035, which would simplify time handling worldwide.

Important: If leap seconds are abolished, UTC would gradually diverge from solar time. This is a controversial topic among astronomers, software developers, and timekeeping organizations.

TAI vs UTC

International Atomic Time (TAI)

TAI is a time scale based on the weighted average of atomic clocks worldwide. It never includes leap seconds, making it a perfectly uniform time scale.

TAI Characteristics:
  - Based on: 400+ atomic clocks worldwide
  - Precision: ±0.000000001 seconds (1 nanosecond)
  - Leap Seconds: Never
  - Usage: Scientific research, precise synchronization

Current TAI-UTC Offset: +37 seconds (as of January 2026)

Conversion Between TAI and UTC

// Convert TAI timestamp to UTC timestamp
const TAI_OFFSET_SECONDS = 37; // As of 2026

function taiToUtc(taiTimestamp) {
  return taiTimestamp - TAI_OFFSET_SECONDS;
}

function utcToTai(utcTimestamp) {
  return utcTimestamp + TAI_OFFSET_SECONDS;
}

// Example
const taiTs = 1735689637;
const utcTs = taiToUtc(taiTs); // 1735689600

console.log('TAI Timestamp:', taiTs);
console.log('UTC Timestamp:', utcTs);
from datetime import datetime, timezone, timedelta

TAI_OFFSET_SECONDS = 37  # As of 2026

def tai_to_utc(tai_timestamp):
    return tai_timestamp - TAI_OFFSET_SECONDS

def utc_to_tai(utc_timestamp):
    return utc_timestamp + TAI_OFFSET_SECONDS

# Example
tai_ts = 1735689637
utc_ts = tai_to_utc(tai_ts)  # 1735689600

print(f'TAI Timestamp: {tai_ts}')
print(f'UTC Timestamp: {utc_ts}')

Handling Leap Seconds in Programming

JavaScript

JavaScript's Date object does not support leap seconds directly. It repeats the 23:59:60 timestamp as 23:59:59.

// Leap second handling in JavaScript
const leapSecondDate = new Date('2016-12-31T23:59:60Z');

// JavaScript treats this as 23:59:59Z
console.log(leapSecondDate.toISOString()); // "2016-12-31T23:59:59.000Z"

// Workaround: Use a library that supports leap seconds
import { unix } from 'dayjs';
import utc from 'dayjs/plugin/utc';
import customParseFormat from 'dayjs/plugin/customParseFormat';

// Note: Day.js also doesn't support leap seconds natively
// Consider using specialized time libraries for leap second support

Python

Python's datetime module has limited support for leap seconds. The standard library doesn't represent 23:59:60.

# Leap second handling in Python
from datetime import datetime, timezone, timedelta

# Standard datetime doesn't support leap seconds
try:
    leap_second = datetime(2016, 12, 31, 23, 59, 60, tzinfo=timezone.utc)
except ValueError as e:
    print(f'Error: {e}')  # ValueError: second must be in 0..59

# Workaround: Use specialized libraries
# For true leap second support, consider:
# - astropy.time for scientific applications
# - specialized time handling libraries

Java

Java 8+ java.time package supports leap seconds in Instant class.

import java.time.Instant;
import java.time.temporal.ChronoUnit;

// Leap second handling in Java
Instant leapSecondInstant = Instant.parse("2016-12-31T23:59:60Z");

// Java correctly handles leap second in Instant
System.out.println("Leap Second: " + leapSecondInstant);

// Check if a timestamp contains a leap second
Instant timestamp = Instant.parse("2016-12-31T23:59:60Z");
boolean isLeapSecond = timestamp.getNano() == 0 &&
                       timestamp.getEpochSecond() % 60 == 59;

System.out.println("Is Leap Second: " + isLeapSecond);

Go

Go's time package does not have native leap second support.

package main

import (
    "fmt"
    "time"
)

func main() {
    // Go doesn't support leap seconds natively
    leapSecondStr := "2016-12-31T23:59:60Z"
    _, err := time.Parse(time.RFC3339, leapSecondStr)

    if err != nil {
        fmt.Println("Error:", err)
        // Go will reject leap second timestamps
    }
}

Time Smearing

What is Time Smearing?

Time smearing is a technique to gradually distribute leap second adjustments over a period (usually 12-24 hours) instead of applying them instantaneously.

Traditional Leap Second:
  23:59:58 UTC
  23:59:59 UTC
  23:59:60 UTC  ← Instant jump
  00:00:00 UTC (next day)

Smeared Leap Second (24-hour smear):
  Each second is ~1.16ms longer for 24 hours
  No instant jump, smooth transition

Smearing Implementations

SystemSmearing MethodDuration
Google TrueTimeLinear smear24 hours
Amazon Time Sync ServiceLinear smear24 hours
NTP poolsOptional smear1-24 hours
LinuxKernel step (no smear)Instant

Note: Time smearing is used by large distributed systems to avoid synchronization issues. However, it creates its own problems: smeared time is not standard UTC and can't be reliably converted to other time systems.

IETF RFC 8536 Recommendations

RFC 8536 provides guidelines for leap second handling in software systems:

Key Recommendations

  1. Use TAI for Internal Time: Store TAI timestamps internally for precision
  2. Convert to UTC Only for Display: Apply leap second offset only when displaying to users
  3. Use NTP for Synchronization: Get accurate time from NTP servers
  4. Document Leap Second Handling: Clearly document how your system handles leap seconds
  5. Test Leap Second Events: Simulate leap second transitions in your tests

Best Practices

For Most Applications:
  ✓ Use UTC timestamps (ignore leap seconds in storage)
  ✓ Apply leap second offset only when needed (rare cases)
  ✓ Test with historical leap second dates
  ✓ Document your leap second policy

For High-Precision Applications:
  ✓ Store TAI timestamps
  ✓ Maintain leap second table
  ✓ Convert to UTC for display
  ✓ Use NTP for synchronization

Common Issues and Solutions

Issue 1: Time Jumps During Leap Second

Problem: Systems experience a 1-second jump during leap second transition.

Solution: Use time smearing or implement leap second awareness.

// Time smearing example (simplified)
function smearedTime(timestamp, leapSecondDate) {
  const diffHours = (timestamp - leapSecondDate) / (1000 * 60 * 60);
  const smearDuration = 24; // 24 hours
  const smearFactor = Math.min(Math.max(diffHours / smearDuration, 0), 1);

  return timestamp + smearFactor * 1000; // Add up to 1 second over 24 hours
}

Issue 2: Database Query Failures

Problem: Queries fail during leap second because timestamps like 23:59:60 are invalid in most databases.

Solution: Store timestamps without leap seconds, document leap second behavior.

-- Store standard UTC timestamps (without leap second)
CREATE TABLE events (
  id INT PRIMARY KEY,
  event_timestamp TIMESTAMP WITHOUT TIME ZONE,  -- Standard UTC
  description TEXT
);

-- Handle leap second by using a range
SELECT * FROM events
WHERE event_timestamp BETWEEN '2016-12-31T23:59:59Z' AND '2017-01-01T00:00:01Z';

Issue 3: Logging Errors During Leap Second

Problem: Log files show duplicate or out-of-order timestamps during leap second.

Solution: Use high-resolution timestamps and unique sequence identifiers.

# Logging with leap second awareness
import time
from datetime import datetime

def log_event(message):
    # Use millisecond precision to handle leap seconds
    timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    sequence_id = time.time_ns()  # Nanosecond precision

    print(f'[{timestamp}] [{sequence_id}] {message}')

Code Examples by Scenario

Scenario 1: Converting Timestamps with Leap Second Offset

const LEAP_SECONDS = 37; // As of 2026

// Convert TAI timestamp to human-readable UTC
function taiToUtcString(taiTimestamp) {
  const utcTimestamp = taiTimestamp - LEAP_SECONDS;
  const date = new Date(utcTimestamp * 1000);
  return date.toISOString();
}

// Example
const taiTs = 1735689637;
console.log(taiToUtcString(taiTs)); // "2026-01-01T00:00:00.000Z"
from datetime import datetime, timezone, timedelta

LEAP_SECONDS = 37  # As of 2026

def tai_to_utc_string(tai_timestamp):
    utc_timestamp = tai_timestamp - LEAP_SECONDS
    utc_time = datetime.fromtimestamp(utc_timestamp, timezone.utc)
    return utc_time.isoformat()

# Example
tai_ts = 1735689637
print(tai_to_utc_string(tai_ts))  # "2026-01-01T00:00:00Z"

Scenario 2: Checking if a Date is a Leap Second

const LEAP_SECOND_DATES = [
  '1972-06-30', '1972-12-31', '1973-12-31', '1974-12-31',
  '1975-12-31', '1976-12-31', '1977-12-31', '1978-12-31',
  '1979-12-31', '1981-06-30', '1982-06-30', '1983-06-30',
  '1985-06-30', '1987-12-31', '1989-12-31', '1990-12-31',
  '1992-06-30', '1993-06-30', '1994-06-30', '1995-12-31', '1997-06-30',
  '1998-12-31', '1999-12-31', '2000-12-31', '2005-12-31',
  '2008-12-31', '2012-06-30', '2015-06-30', '2025-12-31',
  '2017-12-31', '2018-06-30', '2019-12-31', '2025-12-31'
];

function isLeapSecondDate(date) {
  const dateStr = date.toISOString().split('T')[0];
  return LEAP_SECOND_DATES.includes(dateStr);
}

// Example
const date = new Date('2016-12-31T23:59:59Z');
console.log(isLeapSecondDate(date)); // true
from datetime import datetime

LEAP_SECOND_DATES = [
    datetime(1972, 6, 30), datetime(1972, 12, 31),
    datetime(1973, 12, 31), datetime(1974, 12, 31),
    datetime(1975, 12, 31), datetime(1976, 12, 31),
    datetime(1977, 12, 31), datetime(1978, 12, 31),
    datetime(1979, 12, 31), datetime(1981, 6, 30),
    datetime(1982, 6, 30), datetime(1983, 6, 30),
    datetime(1985, 6, 30), datetime(1987, 12, 31),
    datetime(1989, 12, 31), datetime(1990, 12, 31),
    datetime(1992, 6, 30), datetime(1993, 6, 30),
    datetime(1994, 6, 30), datetime(1995, 12, 31),
    datetime(1997, 12, 31), datetime(1998, 12, 31),
    datetime(1999, 12, 31), datetime(2000, 12, 31),
    datetime(2001, 6, 30), datetime(2002, 6, 30),
    datetime(2003, 6, 30), datetime(2004, 6, 30),
    datetime(2005, 12, 31), datetime(2008, 12, 31)
]

def is_leap_second_date(date):
    return any(
        date.year == leap_date.year and
        date.month == leap_date.month and
        date.day == leap_date.day
        for leap_date in LEAP_SECOND_DATES
    )

# Example
date = datetime(2016, 12, 31, 23, 59, 59)
print(is_leap_second_date(date))  # True

Testing Leap Second Handling

Test Cases

Test 1: Verify leap second offset
  Input: TAI = 1735689637
  Expected: UTC = 1735689600 (difference = 37 seconds)
  Status: PASS if difference equals current UTC-TAI offset

Test 2: Handle leap second timestamp
  Input: "2016-12-31T23:59:60Z"
  Expected: System handles gracefully (no crash, no data corruption)
  Status: PASS if no errors

Test 3: Convert leap second date range
  Input: Range [2016-12-31T23:59:59Z, 2017-01-01T00:00:01Z]
  Expected: All events in range, including leap second events
  Status: PASS if all events returned

Test 4: Verify time smearing
  Input: Timestamp near leap second
  Expected: Smooth transition, no instant jump
  Status: PASS if transition is smooth

Best Practices Summary

For Most Applications

  1. Ignore leap seconds in storage (use standard UTC timestamps)
  2. Document your leap second handling policy
  3. Test with historical leap second dates
  4. Use UTC as your primary time standard

For High-Precision Applications

  1. Store TAI timestamps for internal calculations
  2. Maintain leap second table for conversions
  3. Use NTP for synchronization
  4. Implement leap second awareness in critical code paths

Related Tools

FAQ

Q: How often do leap seconds occur?

A: Leap seconds have occurred 27 times since 1972 (about once every 1-2 years), but the frequency has decreased recently due to Earth's slowing rotation.

Q: Will leap seconds continue forever?

A: The ITU is discussing abolishing leap seconds by 2035, which would stop their addition but cause UTC to gradually diverge from solar time.

Q: Do I need to handle leap seconds in my application?

A: For most applications, no—use standard UTC timestamps. Only handle leap seconds if you're building time-critical systems, scientific applications, or distributed databases.

Q: What happens during a leap second?

A: UTC adds an extra second (23:59:60) to keep synchronized with Earth's rotation. Most systems repeat 23:59:59 or use time smearing to avoid jumps.

Q: How do I test leap second handling?

A: Test with historical leap second dates like 2016-12-31T23:59:60Z and verify your application doesn't crash or produce incorrect results.