{"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":1539,"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":1521,"sourceCodeEnd":1557,"githubUrl":"https://github.com/pandas-dev/pandas/blob/3b7651241d4da534b3559b60ef128e1c34f54116/pandas/core/arrays/period.py#L1521-L1557","documentation":"Raised by _get_ordinal_range when start or end resolves to pandas.NaT. A period range needs concrete endpoints; NaT cannot anchor or terminate it.","triggerScenarios":"Passing start=pd.NaT or end=pd.NaT to period_range; feeding Period values derived from un-parseable strings (which become NaT) into the range builder.","commonSituations":"Dirty input data with missing/blank date fields converted via pd.to_datetime(errors='coerce'); conditional code that sometimes yields NaT; downstream of failed parsing silently producing NaT.","solutions":["Filter NaT out of inputs before constructing the range.","Replace NaT with a concrete fallback date or use try/except around parsing.","Validate with pd.notna() before passing endpoints."],"exampleFix":"# before\nstart = pd.to_datetime(maybe_bad, errors='coerce')\npd.period_range(start, end, freq='D')\n# after\nstart = pd.to_datetime(maybe_bad, errors='coerce')\nif pd.isna(start):\n    raise ValueError(f'unparseable start: {maybe_bad!r}')\npd.period_range(start, end, freq='D')","handlingStrategy":"validation","validationCode":"import pandas as pd\n\ndef endpoints_not_nat(start, end) -> bool:\n    return pd.notna(start) and pd.notna(end)","typeGuard":"def is_usable_endpoint(value) -> bool:\n    return value is not None and not (isinstance(value, float) and value != value) and not pd.isna(value)","tryCatchPattern":"try:\n    pr = pd.period_range(start=start, end=end, freq=freq)\nexcept ValueError as e:\n    if 'must not be NaT' in str(e):\n        start = start if pd.notna(start) else fallback_start\n        end = end if pd.notna(end) else fallback_end\n        pr = pd.period_range(start=start, end=end, freq=freq)\n    else:\n        raise","preventionTips":["pd.notna(value) every endpoint before passing it.","Avoid errors='coerce' in parsing if you cannot tolerate NaT.","Drop rows with NaT dates upstream of range construction."],"tags":["pandas","period","nat","validation"],"backgroundTag":null,"analyzedSha":"3b7651241d4da534b3559b60ef128e1c34f54116","analyzedAt":"2026-08-11T22:10:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}