{"record":{"id":"cf130195fc75d099","repo":"pandas-dev/pandas","slug":"offset-offset-did-not-decrement-date","errorCode":null,"errorMessage":"Offset {offset} did not decrement date","messagePattern":"Offset (.+?) did not decrement date","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"pandas/core/arrays/datetimes.py","lineNumber":3288,"sourceCode":"            next_date = offset._apply(cur)\n            next_date = next_date.as_unit(unit)\n            if next_date <= cur:\n                raise ValueError(f\"Offset {offset} did not increment date\")\n            cur = next_date\n    else:\n        while cur >= end:\n            yield cur\n\n            if cur == end:\n                # GH#24252 avoid overflows by not performing the addition\n                # in offset.apply unless we have to\n                break\n\n            # faster than cur + offset\n            next_date = offset._apply(cur)\n            next_date = next_date.as_unit(unit)\n            if next_date >= cur:\n                raise ValueError(f\"Offset {offset} did not decrement date\")\n            cur = next_date\n","sourceCodeStart":3270,"sourceCodeEnd":3290,"githubUrl":"https://github.com/pandas-dev/pandas/blob/3b7651241d4da534b3559b60ef128e1c34f54116/pandas/core/arrays/datetimes.py#L3270-L3290","documentation":"Raised during backward date-range generation (the offset.n < 0 branch of the generator). After applying the offset to the current date, pandas asserts the result is strictly earlier than the current date; if `offset._apply(cur)` yields a date >= cur, the offset is not actually moving backward and the loop would never terminate, so pandas aborts. This is an infinite-loop guard, not a range-bounds check.","triggerScenarios":"Constructing a date_range/DatetimeIndex that iterates backward (start > end with a negative-direction offset, or an offset whose `.n < 0`) where the offset's `_apply` returns a value that does not decrease the timestamp. Happens with custom DateOffset subclasses whose apply is a no-op, with offsets that roll forward to the same anchored point (e.g. some MonthEnd / YearBegin anchorings on boundary dates), or with semi-monthend offsets that snap instead of step.","commonSituations":"Custom DateOffset subclasses that forget to move the date in one direction; calling `date_range(start, end, freq=<negative offset>)` where start > end but the offset increments; using `pd.offsets.SemiMonthEnd(-1)` or similar negative offsets on dates where the rule resolves to the same day; version upgrades that changed anchor semantics for BusinessHour/CustomBusinessHour.","solutions":["Verify the offset actually steps backward: test `offset._apply(Timestamp(x)) < Timestamp(x)` for representative x in your range before generating.","If you want a backward range, pass a normal positive offset and let pandas build start->end with start < end, then reverse with [::-1].","For custom DateOffset subclasses, ensure `_apply`/`apply` strictly decreases the input when `n < 0` (multiply the delta by sign(n)).","Check that start > end is paired with an offset whose n < 0 (or rely on pandas to negate), and that no anchor snapping returns the same date."],"exampleFix":"# before\nrng = date_range('2024-12-31', '2024-01-01', freq=MyNoOpOffset(-1))  # _apply returns same date\n\n# after - step the offset so apply strictly decreases, or reverse a forward range\nrng = date_range('2024-01-01', '2024-12-31', freq='MS')[::-1]","handlingStrategy":"validation","validationCode":"import pandas as pd\ndef offset_decrements(offset, ts):\n    return offset._apply(ts) < ts\n\n# before building a backward range\nstart, end = pd.Timestamp('2024-12-31'), pd.Timestamp('2024-01-01')\nif start >= end:\n    assert offset_decrements(freq, start), f'offset {freq} does not decrement; date_range would fail'","typeGuard":"def is_backward_offset(offset: pd.offsets.BaseOffset, sample: pd.Timestamp) -> bool:\n    return offset._apply(sample) < sample","tryCatchPattern":"try:\n    rng = pd.date_range(start, end, freq=freq)\nexcept ValueError as e:\n    if 'did not decrement date' in str(e):\n        rng = pd.date_range(end, start, freq=freq)[::-1]  # build forward, reverse\n    else:\n        raise","preventionTips":["For backward ranges, prefer building forward (start < end) and reversing with [::-1].","Unit-test custom DateOffset subclasses in both directions on boundary dates.","Confirm the offset's n sign matches the desired direction before constructing date_range."],"tags":["datetime","date-range","offset","infinite-loop-guard"],"backgroundTag":null,"analyzedSha":"3b7651241d4da534b3559b60ef128e1c34f54116","analyzedAt":"2026-08-11T22:10:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}