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
- Normalize the chrono timestamp before conversion (e.g. truncate nanoseconds below 1_000_000_000 or round)
- Check and handle the nanosecond field explicitly (`dt.nanosecond() >= 1_000_000_000`) and log/adjust in Rust instead of relying on the warning
- Keep the data as a higher-precision type (e.g. integer nanoseconds since epoch) rather than Python datetime
- 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
- Normalize chrono timestamps (truncate/round nanoseconds >= 1_000_000_000) before crossing into Python
- Model leap seconds with explicit TAI/UTC conversion instead of encoding them in nanoseconds
- If nanosecond precision matters, keep data in integer-nanosecond form rather than Python datetime
- Run conversion under a warnings filter during tests to detect silent truncation
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
- failed to import `datetime` C API
- Neither abi3 or abi3t features are enabled
- Cannot target an abi3t version below {MINIMUM_SUPPORTED_VERS
- failed to run the Python interpreter at {}: {}
- Python script failed
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/1157f20e0b89ed17.
Report an issue: GitHub.