PyO3/pyo3 · warning · PyUserWarning

ignored leap-second, `datetime` does not support leap-second

Error message

ignored leap-second, `datetime` does not support leap-seconds

What it means

This is not a panic but a warning emitted by pyo3's chrono integration: Python's `datetime` has no leap-second representation, so a `chrono::NaiveDateTime` whose nanosecond field encodes a leap second (nanoseconds >= 1_000_000_000) cannot be converted losslessly. pyo3 emits a `PyUserWarning` ('ignored leap-second') and truncates the leap second to a full second. If issuing the warning itself fails, the error is written as unraisable.

Source

Thrown at src/conversions/chrono.rs:623

impl From<&NaiveTime> for TimeArgs {
    fn from(value: &NaiveTime) -> Self {
        let ns = value.nanosecond();
        let checked_sub = ns.checked_sub(1_000_000_000);
        let truncated_leap_second = checked_sub.is_some();
        let micro = checked_sub.unwrap_or(ns) / 1000;
        Self {
            hour: value.hour() as u8,
            min: value.minute() as u8,
            sec: value.second() as u8,
            micro,
            truncated_leap_second,
        }
    }
}

fn warn_truncated_leap_second(obj: &Bound<'_, PyAny>) {
    let py = obj.py();
    if let Err(e) = PyErr::warn(
        py,
        &py.get_type::<PyUserWarning>(),
        c"ignored leap-second, `datetime` does not support leap-seconds",
        0,
    ) {
        e.write_unraisable(py, Some(obj))
    };
}

#[cfg(not(Py_LIMITED_API))]
fn py_date_to_naive_date(
    py_date: impl core::ops::Deref<Target = impl PyDateAccess>,
) -> PyResult<NaiveDate> {
    NaiveDate::from_ymd_opt(
        py_date.get_year(),
        py_date.get_month().into(),
        py_date.get_day().into(),
    )

View on GitHub (pinned to ac9b6899d3)

Solutions

  1. Normalize the chrono timestamp before conversion (e.g. truncate nanoseconds below 1_000_000_000 or round)
  2. Check and handle the nanosecond field explicitly (`dt.nanosecond() >= 1_000_000_000`) and log/adjust in Rust instead of relying on the warning
  3. Keep the data as a higher-precision type (e.g. integer nanoseconds since epoch) rather than Python datetime
  4. Suppress/expect the UserWarning on the Python side if truncation is acceptable

Example fix

// before
let py_dt = chrono_dt.into_pyobject(py)?; // warns, truncates leap second
// after
let dt = if chrono_dt.nanosecond() >= 1_000_000_000 {
    chrono_dt - chrono::Duration::nanoseconds((chrono_dt.nanosecond() - 999_999_999) as i64)
} else { chrono_dt };
let py_dt = dt.into_pyobject(py)?; // no leap second to truncate
Defensive patterns

Strategy: validation

Validate before calling

fn has_leap_second(dt: &chrono::NaiveDateTime) -> bool {
    dt.nanosecond() >= 1_000_000_000
}
// before conversion: assert/normalize if has_leap_second(&dt)

Try / catch

// Python side: expect the UserWarning if truncation is acceptable
import warnings
with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter("always")
    py_dt = convert(dt)
    if any('leap-second' in str(x.message) for x in w):
        ...  # handle or log precision loss

Prevention

When it happens

Trigger: Converting a chrono `NaiveDateTime`/`DateTime` with leap-second nanoseconds (e.g. from `chrono::DateTime::from_timestamp(…, 1_999_999_999)` or parsed leap-second data) to Python via `IntoPyObject`/`into_pyobject` for `PyDateTime`.

Common situations: Ingesting high-precision timestamps from scientific or GPS/time-source data (TAI/GPS offsets, leap-second tables) and passing them to Python datetime objects, which silently drops the extra nanoseconds.

Related errors


AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05). Data as JSON: /api/errors/1157f20e0b89ed17. Report an issue: GitHub.