pandas-dev/pandas · error · ValueError

Neither `start` nor `end` can be NaT

Error message

Neither `start` nor `end` can be NaT

What it means

Raised in _generate_range after Timestamp coercion when either start or end is pandas.NaT. Generating a range anchored on NaT is meaningless — there is no real reference instant — so it is rejected up front, before the freq/periods arithmetic would propagate the NaT silently.

Source

Thrown at pandas/core/arrays/datetimes.py:422

        periods = dtl.validate_periods(periods)
        if freq is None and any(x is None for x in [periods, start, end]):
            raise ValueError("Must provide freq argument if no data is supplied")

        if com.count_not_none(start, end, periods, freq) != 3:
            raise ValueError(
                "Of the four parameters: start, end, periods, "
                "and freq, exactly three must be specified"
            )
        freq = to_offset(freq)

        if start is not None:
            start = Timestamp(start)

        if end is not None:
            end = Timestamp(end)

        if start is NaT or end is NaT:
            raise ValueError("Neither `start` nor `end` can be NaT")

        if unit is not None:
            if unit not in ["s", "ms", "us", "ns"]:
                raise ValueError("'unit' must be one of 's', 'ms', 'us', 'ns'")
        else:
            unit = "ns"

        if start is not None:
            start = start.as_unit(unit, round_ok=False)
        if end is not None:
            end = end.as_unit(unit, round_ok=False)

        left_inclusive, right_inclusive = validate_inclusive(inclusive)
        start, end = _maybe_normalize_endpoints(start, end, normalize)
        tz = _infer_tz_from_endpoints(start, end, tz)

        if tz is not None:
            # Localize the start and end arguments

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Filter or fill NaT before using it as a bound: start = df['start'].dropna().iloc[0].
  2. Validate with pd.isna(start) / pd.isna(end) and branch.
  3. If the column may be empty, default to a real Timestamp or skip the range generation.

Example fix

# before
start = pd.to_datetime(maybe_bad_string)  # may be NaT
pd.date_range(start, '2020-12-31', freq='D')

# after
start = pd.to_datetime(maybe_bad_string)
if pd.isna(start):
    raise ValueError(f'unparseable start: {maybe_bad_string!r}')
pd.date_range(start, '2020-12-31', freq='D')
Defensive patterns

Strategy: validation

Validate before calling

if pd.isna(start) or pd.isna(end):
    raise ValueError('start/end cannot be NaT; supply a valid Timestamp')

Type guard

def is_valid_bound(x) -> bool:
    return x is None or (isinstance(x, (pd.Timestamp, str)) and not pd.isna(pd.Timestamp(x)))

Try / catch

try:
    pd.date_range(start, end, freq='D')
except ValueError as e:
    if 'can be NaT' in str(e):
        start = df['start'].dropna().iloc[0]
        pd.date_range(start, end, freq='D')
    else: raise

Prevention

When it happens

Trigger: pd.date_range(start=pd.NaT, end='2020-01-05', periods=3); pd.date_range(start='2020-01-01', end=pd.NaT, freq='D'); passing a column value that is NaT as the start/end (e.g. df['start'].iloc[0] when the column is all-NaT).

Common situations: Start/end sourced from a row that is missing; upstream parsing returned NaT for an unparseable date string; timezone localization of an ambiguous/nonexistent time yielded NaT.

Related errors


AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07). Data as JSON: /api/errors/d009742cfc2edad8. Report an issue: GitHub.