FA-291 / Calendar arithmetic / Open access
Adding a calendar month drifts past the target month · case 01
A month offset expressed as thirty days lands on the wrong date, or a blanket day cap loses valid month-end days.
ROOT CAUSE
Calendar month movement is replaced with fixed-duration movement or a universal twenty-eight-day cap.
VERIFIED REPAIR
Move the year/month pair by the requested number of months, then clamp the original day to that destination month's actual length.
Unsuccessful approach: Capping every day at twenty-eight avoids invalid dates but truncates thirty-day, thirty-one-day, and leap-February destinations.
Case contract
Given a valid ISO calendar date and an integer month offset, return an ISO date in the destination month, keeping the original day when possible and otherwise clamping once to the last day. Supported results stay within years 1 through 9999.
Why this case matters
Billing dates and calendar schedules need an explicit month-end convention. Calendar movement is not equivalent to adding elapsed days or repeatedly applying a one-month clamp.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
from datetime import date, timedelta
import calendar
N = 1
observations = []
def solve(text, months):
return (date.fromisoformat(text) + timedelta(days=30 * months)).isoformat()
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
year = 2019 + N
february_day = [29, 28, 28, 28, 29][N - 1]
check('January end clamps to actual February', solve(f'{year}-01-31', 1), f'{year}-02-{february_day}')
check('two-month move clamps only at destination', solve(f'{year}-01-31', 2), f'{year}-03-31')
check('reverse month movement', solve(f'{year}-03-31', -1), f'{year}-02-{february_day}')
check('year rollover with variable offset', solve('2023-12-15', N), f'2024-{N:02d}-15')
check('zero offset preserves month end', solve(f'{year}-07-31', 0), f'{year}-07-31')
check('century divisible by four hundred is leap', solve('2000-01-31', 1), '2000-02-29')
check('other century is not leap', solve('2100-01-31', 1), '2100-02-28')
check('thirty-day destination', solve(f'{year}-03-31', 1), f'{year}-04-30')
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| January end clamps to actual February | 2020-03-01 | 2020-02-29 | Failed |
| two-month move clamps only at destination | 2020-03-31 | 2020-03-31 | Passed |
| reverse month movement | 2020-03-01 | 2020-02-29 | Failed |
| year rollover with variable offset | 2024-01-14 | 2024-01-15 | Failed |
| zero offset preserves month end | 2020-07-31 | 2020-07-31 | Passed |
| century divisible by four hundred is leap | 2000-03-01 | 2000-02-29 | Failed |
| other century is not leap | 2100-03-02 | 2100-02-28 | Failed |
| thirty-day destination | 2020-04-30 | 2020-04-30 | Passed |
SHA-256 / 3cd3b42c0c60d12bd68836afd3dc504bb8b4c32e25515ef099f83fb0161bcf02
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
from datetime import date, timedelta
import calendar
N = 1
observations = []
def solve(text, months):
original = date.fromisoformat(text)
index = original.year * 12 + original.month - 1 + months
year, month = divmod(index, 12)
return date(year, month + 1, min(original.day, 28)).isoformat()
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
year = 2019 + N
february_day = [29, 28, 28, 28, 29][N - 1]
check('January end clamps to actual February', solve(f'{year}-01-31', 1), f'{year}-02-{february_day}')
check('two-month move clamps only at destination', solve(f'{year}-01-31', 2), f'{year}-03-31')
check('reverse month movement', solve(f'{year}-03-31', -1), f'{year}-02-{february_day}')
check('year rollover with variable offset', solve('2023-12-15', N), f'2024-{N:02d}-15')
check('zero offset preserves month end', solve(f'{year}-07-31', 0), f'{year}-07-31')
check('century divisible by four hundred is leap', solve('2000-01-31', 1), '2000-02-29')
check('other century is not leap', solve('2100-01-31', 1), '2100-02-28')
check('thirty-day destination', solve(f'{year}-03-31', 1), f'{year}-04-30')
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| January end clamps to actual February | 2020-02-28 | 2020-02-29 | Failed |
| two-month move clamps only at destination | 2020-03-28 | 2020-03-31 | Failed |
| reverse month movement | 2020-02-28 | 2020-02-29 | Failed |
| year rollover with variable offset | 2024-01-15 | 2024-01-15 | Passed |
| zero offset preserves month end | 2020-07-28 | 2020-07-31 | Failed |
| century divisible by four hundred is leap | 2000-02-28 | 2000-02-29 | Failed |
| other century is not leap | 2100-02-28 | 2100-02-28 | Passed |
| thirty-day destination | 2020-04-28 | 2020-04-30 | Failed |
SHA-256 / 682a36f70635b46063f9848be47ca87c42c4323add4b48c5bbf493361fe5c179
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
from datetime import date, timedelta
import calendar
N = 1
observations = []
def solve(text, months):
original = date.fromisoformat(text)
index = original.year * 12 + original.month - 1 + months
year, month = divmod(index, 12)
month += 1
day = min(original.day, calendar.monthrange(year, month)[1])
return date(year, month, day).isoformat()
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
year = 2019 + N
february_day = [29, 28, 28, 28, 29][N - 1]
check('January end clamps to actual February', solve(f'{year}-01-31', 1), f'{year}-02-{february_day}')
check('two-month move clamps only at destination', solve(f'{year}-01-31', 2), f'{year}-03-31')
check('reverse month movement', solve(f'{year}-03-31', -1), f'{year}-02-{february_day}')
check('year rollover with variable offset', solve('2023-12-15', N), f'2024-{N:02d}-15')
check('zero offset preserves month end', solve(f'{year}-07-31', 0), f'{year}-07-31')
check('century divisible by four hundred is leap', solve('2000-01-31', 1), '2000-02-29')
check('other century is not leap', solve('2100-01-31', 1), '2100-02-28')
check('thirty-day destination', solve(f'{year}-03-31', 1), f'{year}-04-30')
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| January end clamps to actual February | 2020-02-29 | 2020-02-29 | Passed |
| two-month move clamps only at destination | 2020-03-31 | 2020-03-31 | Passed |
| reverse month movement | 2020-02-29 | 2020-02-29 | Passed |
| year rollover with variable offset | 2024-01-15 | 2024-01-15 | Passed |
| zero offset preserves month end | 2020-07-31 | 2020-07-31 | Passed |
| century divisible by four hundred is leap | 2000-02-29 | 2000-02-29 | Passed |
| other century is not leap | 2100-02-28 | 2100-02-28 | Passed |
| thirty-day destination | 2020-04-30 | 2020-04-30 | Passed |
SHA-256 / c8d0f3b82b455305b42700e8223ddcd45d3ab4560c5bdc29b86064ba05c25fb9
Verification & scope
This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:36:52.038828+00:00.
Case digest / 7bd6a63044f8d966259a0dd388753a70888ae4567aa48361313c96947d776962