{"record":{"id":"257a62cf3a6f714b","repo":"pandas-dev/pandas","slug":"start-and-end-must-not-be-nat","errorCode":null,"errorMessage":"start and end must not be NaT","messagePattern":"start and end must not be NaT","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"pandas/core/arrays/period.py","lineNumber":1536,"sourceCode":"            \"exactly two must be specified\"\n        )\n\n    if freq is not None:\n        freq = to_offset(freq, is_period=True)\n        mult = freq.n\n\n    if start is not None:\n        start = Period(start, freq)\n    if end is not None:\n        end = Period(end, freq)\n\n    is_start_per = isinstance(start, Period)\n    is_end_per = isinstance(end, Period)\n\n    if is_start_per and is_end_per and start.freq != end.freq:\n        raise ValueError(\"start and end must have same freq\")\n    if start is NaT or end is NaT:\n        raise ValueError(\"start and end must not be NaT\")\n\n    if freq is None:\n        if is_start_per:\n            freq = start.freq\n        elif is_end_per:\n            freq = end.freq\n        else:  # pragma: no cover\n            raise ValueError(\"Could not infer freq from start/end\")\n        mult = freq.n\n\n    if periods is not None:\n        periods = periods * mult\n        if start is None:\n            data = np.arange(\n                end.ordinal - periods + mult, end.ordinal + 1, mult, dtype=np.int64\n            )\n        else:\n            data = np.arange(","sourceCodeStart":1518,"sourceCodeEnd":1554,"githubUrl":"https://github.com/pandas-dev/pandas/blob/71959b8cb9b2459c16e14b34f28b178ccfe14735/pandas/core/arrays/period.py#L1518-L1554","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nrng = pd.period_range(start=df.loc[i,'from'], end=df.loc[i,'to'], freq='D')\n// after\ns, e = df.loc[i,'from'], df.loc[i,'to']\nrng = pd.period_range(start=s, end=e, freq='D') if pd.notna(s) and pd.notna(e) else pd.PeriodIndex([], freq='D')","handlingStrategy":"validation","validationCode":"import pandas as pd\n\ndef period_range_or_empty(start, end, freq):\n    if pd.isna(start) or pd.isna(end):\n        return pd.PeriodIndex([], freq=freq)\n    return pd.period_range(start=start, end=end, freq=freq)","typeGuard":"def is_valid_period_bound(x) -> bool:\n    return x is not None and not pd.isna(x)","tryCatchPattern":"try:\n    rng = pd.period_range(start=start, end=end, freq=freq)\nexcept ValueError as e:\n    if 'must not be NaT' in str(e):\n        rng = pd.PeriodIndex([], freq=freq)\n    else:\n        raise","preventionTips":["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."],"tags":["pandas","period","period-range","nat","validation"],"analyzedSha":"71959b8cb9b2459c16e14b34f28b178ccfe14735","analyzedAt":"2026-08-07T01:30:20.476Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}