pandas-dev/pandas · error · ValueError
start and end must not be NaT
Error message
start and end must not be NaT
What it means
Raised in _get_ordinal_range when either `start` or `end` resolved to pandas NaT. A period range cannot be generated from an unparseable or missing temporal bound, so pandas aborts rather than producing a garbage range. This is the explicit guard before it would try to read .ordinal off a NaT.
Source
Thrown at pandas/core/arrays/period.py:1536
"exactly two must be specified"
)
if freq is not None:
freq = to_offset(freq, is_period=True)
mult = freq.n
if start is not None:
start = Period(start, freq)
if end is not None:
end = Period(end, freq)
is_start_per = isinstance(start, Period)
is_end_per = isinstance(end, Period)
if is_start_per and is_end_per and start.freq != end.freq:
raise ValueError("start and end must have same freq")
if start is NaT or end is NaT:
raise ValueError("start and end must not be NaT")
if freq is None:
if is_start_per:
freq = start.freq
elif is_end_per:
freq = end.freq
else: # pragma: no cover
raise ValueError("Could not infer freq from start/end")
mult = freq.n
if periods is not None:
periods = periods * mult
if start is None:
data = np.arange(
end.ordinal - periods + mult, end.ordinal + 1, mult, dtype=np.int64
)
else:
data = np.arange(View on GitHub (pinned to 71959b8cb9)
Solutions
- Validate start/end with pd.isna() before calling period_range and skip/handle the empty case.
- Sanitize inputs through pd.to_datetime(..., errors='coerce') then dropna() so NaT never reaches period_range.
- If the NaT comes from a config/UI field, require a valid date and surface a form error instead of calling the API.
Example fix
// before rng = pd.period_range(start=df.loc[i,'from'], end=df.loc[i,'to'], freq='D') // after s, e = df.loc[i,'from'], df.loc[i,'to'] rng = pd.period_range(start=s, end=e, freq='D') if pd.notna(s) and pd.notna(e) else pd.PeriodIndex([], freq='D')
Defensive patterns
Strategy: validation
Validate before calling
import pandas as pd
def period_range_or_empty(start, end, freq):
if pd.isna(start) or pd.isna(end):
return pd.PeriodIndex([], freq=freq)
return pd.period_range(start=start, end=end, freq=freq) Type guard
def is_valid_period_bound(x) -> bool:
return x is not None and not pd.isna(x) Try / catch
try:
rng = pd.period_range(start=start, end=end, freq=freq)
except ValueError as e:
if 'must not be NaT' in str(e):
rng = pd.PeriodIndex([], freq=freq)
else:
raise Prevention
- Run pd.notna() on date inputs before calling period_range.
- Sanitize CSV dates with to_datetime(..., errors='coerce').dropna().
- Surface empty/invalid date fields at the form/config layer.
When it happens
Trigger: period_range(start=pd.NaT, end='2020', freq='D'); passing a column value that parsed to NaT (e.g. an empty string or invalid date string) as start or end; start=pd.Timestamp('NaT') or a NaT produced by a failed to_datetime conversion.
Common situations: Dates read from dirty CSVs where some cells are blanks/NaN; downstream code that forwards user-supplied 'from'/'to' filters without validating them; timezone or format mismatches in to_datetime that silently yield NaT.
Related errors
- start and end must have same freq
- Could not infer freq from start/end
- Quarter must be 1 <= q <= 4
- cannot convert float NaN to integer
- Mismatched Period array lengths
AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07).
Data as JSON: /api/errors/257a62cf3a6f714b.
Report an issue: GitHub.