diff --git a/python/prophet/forecaster.py b/python/prophet/forecaster.py index ec728c9fe..064a65b4d 100644 --- a/python/prophet/forecaster.py +++ b/python/prophet/forecaster.py @@ -10,7 +10,7 @@ import logging from collections import OrderedDict, defaultdict from copy import deepcopy -from datetime import timedelta +from datetime import timedelta, date from typing import Dict, List, Union import numpy as np @@ -25,6 +25,32 @@ logger.setLevel(logging.INFO) NANOSECONDS_TO_SECONDS = 1000 * 1000 * 1000 +def _validate_ds_ns_range(ds_series) -> None: + # pandas ns bounds + ns_min = date(1677, 9, 21) + ns_max = date(2262, 4, 11) + + def _to_date(x): + try: + # handles 'YYYY-MM-DD...' strings; slice keeps date part + return date.fromisoformat(str(x)[:10]) + except Exception: + return None + + parsed = ds_series.map(_to_date) + + if parsed.isna().any(): + bad = ds_series[parsed.isna()].iloc[0] + raise ValueError(f"Invalid ds value (cannot parse to date): {bad}") + + oob = (parsed < ns_min) | (parsed > ns_max) + if oob.any(): + bad = ds_series[oob].iloc[0] + raise ValueError( + f"Prophet supports pandas datetime64[ns] only " + f"({ns_min.isoformat()} to {ns_max.isoformat()}). " + f"Out-of-range ds value: {bad}" + ) class Prophet(object): stan_backend: IStanBackend @@ -213,7 +239,8 @@ def validate_inputs(self): raise ValueError( 'holidays_mode must be "additive" or "multiplicative"' ) - + + def validate_column_name(self, name, check_holidays=True, check_seasonalities=True, check_regressors=True): """Validates the name of a seasonality, holiday, or regressor. @@ -1125,6 +1152,8 @@ def preprocess(self, df: pd.DataFrame, **kwargs) -> ModelInputData: Saves the preprocessed data to the instantiated object, and also returns the relevant components as a ModelInputData object. """ + _validate_ds_ns_range(df["ds"]) + if ('ds' not in df) or ('y' not in df): raise ValueError( 'Dataframe must have columns "ds" and "y" with the dates and ' diff --git a/python/prophet/tests/test_oob_dates.py b/python/prophet/tests/test_oob_dates.py new file mode 100644 index 000000000..461d20f17 --- /dev/null +++ b/python/prophet/tests/test_oob_dates.py @@ -0,0 +1,13 @@ +import pandas as pd +import pytest +from prophet import Prophet + +def test_in_range_ok(): + df = pd.DataFrame({"ds": ["2024-01-01","2024-01-02"], "y": [1.0, 2.0]}) + Prophet().fit(df) # no error + +def test_out_of_range_raises(): + df = pd.DataFrame({"ds": ["3969-12-02","3969-12-03"], "y": [1.0, 2.0]}) + with pytest.raises(ValueError) as e: + Prophet().fit(df) + assert "datetime64[ns]" in str(e.value)