Calendars, leap years and leap seconds

The Gregorian leap rule including the 100 and 400 exceptions, why month lengths vary, how ISO week numbering works, and what leap seconds do to a clock that claims to be exact.

The leap year rule, exactly

A year is a leap year if it is divisible by 4, unless it is divisible by 100, unless it is divisible by 400. Two exceptions, and code that implements only the first one is subtly wrong for centuries.

def is_leap(year):
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

# 2024 leap, 2023 not, 1900 not (divisible by 100), 2000 leap (divisible by 400)
assert [y for y in (2023, 2024, 1900, 2000) if is_leap(y)] == [2024, 2000]
MonthDaysMonthDays
January31July31
February28 or 29August31
March31September30
April30October31
May31November30
June30December31

Never compute a month length by hand, and never assume February has 28 days. Use the library helper — calendar.monthrange, daysInMonth, time.DaysIn — because the correct answer depends on the year.

Week numbers and the year boundary

ISO 8601 defines week 1 as the week containing the first Thursday of the year, so weeks start on Monday and a year has 52 or 53 weeks. This means 30 December can belong to the next ISO year.

from datetime import date

d = date(2025, 12, 29)
iso_year, iso_week, iso_day = d.isocalendar()
print(iso_year, iso_week, iso_day)     # 2026 1 1 — belongs to ISO year 2026

# the trap: strftime year is NOT the ISO year
print(d.strftime("%G-W%V-%u"))         # 2026-W01-1  (ISO year, ISO week)
print(d.strftime("%Y-W%W"))            # 2025-W52     (calendar year, week 0-based)
  • %G, %V and %u are the ISO year, week and weekday.
  • %Y and %W are the calendar year and a week number that has an incomplete first week — mixing them produces off-by-one errors at year boundaries.
  • The US convention differs: weeks start on Sunday and week 1 contains 1 January, so there is no single correct week number without naming the system.

Leap seconds break the assumption of 86400

Leap seconds are inserted by the IERS to keep civil time within 0.9 seconds of astronomical time. There have been 27 of them since 1972, which means UTC minutes can have 61 seconds and a UTC day can have 86401.

  • Unix time ignores leap seconds: it counts 86400 seconds per day, so it drifts about one second from UTC over decades.
  • POSIX mandates that a leap second be handled by repeating or smearing the last second, and Linux spreads the correction over milliseconds (leap smearing).
  • Google and other operators smear 24 hours around the event so no system ever sees a 23:59:60 timestamp.
  • A monotonic clock never jumps, which is why elapsed-time measurement must not use wall clock.
💡
Leap seconds do not affect most applications, but they break assumptions in code that computes durations as a count of wall-clock seconds across the event. If you need a continuous timescale, use TAI or a monotonic clock, not UTC.

FAQ

Should I worry about a 53-week year?
Yes if you generate weekly reports or billing periods. A year with 53 ISO weeks will produce 53 buckets, and any code that assumes 52 silently loses a week of data.
Is 1900 or 2000 a leap year?
1900 is not (divisible by 100, not by 400). 2000 is (divisible by 400). This is exactly why the two-exception rule matters, and why some spreadsheets got 1900 wrong.

ISO 8601 and RFC 3339 formats The 2038 problem and 32-bit time

Last refreshed 2026-09-18.