FA-77071 / Calendar recurrence rules / Open access
Occurrence test and next-occurrence lookup for an anchored N-weekly series: before anchor · case 01
A query on the anchor date returns the anchor as the next occurrence instead of the following one.
ROOT CAUSE
A query exactly on the anchor is treated as being before the series.
VERIFIED REPAIR
At the before anchor decision use `diff < 0`, leaving the other decision sites of the model unchanged.
Unsuccessful approach: The partial repair `diff < 0 and diff % period != 0` sends aligned dates before the anchor to the index formula, which returns a date before the anchor.
Case contract
A series occurs every interval_weeks weeks starting at anchor, for count occurrences. For a query date return [is_occurrence, next occurrence strictly after the query or None, 0-based index of that next occurrence or None]. Dates before the anchor are never occurrences; the next occurrence after a query before the anchor is the anchor itself.
Why this case matters
Recurring calendar series are expanded into concrete instances for display, reminders and conflict checks.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import datetime as dt
import calendar
N = 1
observations = []
def solve(anchor, interval_weeks, count, query):
a = dt.date.fromisoformat(anchor)
q = dt.date.fromisoformat(query)
period = 7 * interval_weeks
diff = (q - a).days
is_occ = diff >= 0 and diff % period == 0 and diff // period < count
if diff <= 0:
idx = 0
else:
idx = diff // period + 1
if idx >= count:
return [is_occ, None, None]
return [is_occ, (a + dt.timedelta(days=idx * period)).isoformat(), idx]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: aligned date before anchor', ['2024-01-05', 2, 10, '2023-12-22'], [False, '2024-01-05', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1]),
('query on later occurrence', ['2024-01-05', 2, 10, '2024-02-02'], [True, '2024-02-16', 3]),
('day before occurrence', ['2024-01-05', 2, 10, '2024-01-18'], [False, '2024-01-19', 1]),
('one period after last', ['2024-01-05', 2, 3, '2024-02-16'], [False, None, None]),
('query on last occurrence', ['2024-01-05', 2, 3, '2024-02-02'], [True, None, None]),
('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0])],
[('query on later occurrence', ['2024-01-05', 2, 10, '2024-02-02'], [True, '2024-02-16', 3]),
('day before occurrence', ['2024-01-05', 2, 10, '2024-01-18'], [False, '2024-01-19', 1]),
('one period after last', ['2024-01-05', 2, 3, '2024-02-16'], [False, None, None]),
('query on last occurrence', ['2024-01-05', 2, 3, '2024-02-02'], [True, None, None]),
('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('weekly mid week', ['2024-03-04', 1, 5, '2024-03-13'], [False, '2024-03-18', 2]),
('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('three periods before anchor weekly', ['2024-03-04', 1, 5, '2024-02-12'], [False, '2024-03-04', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])],
[('one period after last', ['2024-01-05', 2, 3, '2024-02-16'], [False, None, None]),
('query on last occurrence', ['2024-01-05', 2, 3, '2024-02-02'], [True, None, None]),
('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('weekly mid week', ['2024-03-04', 1, 5, '2024-03-13'], [False, '2024-03-18', 2]),
('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('aligned before anchor weekly', ['2024-03-04', 1, 5, '2024-02-26'], [False, '2024-03-04', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])],
[('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('weekly mid week', ['2024-03-04', 1, 5, '2024-03-13'], [False, '2024-03-18', 2]),
('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('aligned before anchor weekly', ['2024-03-04', 1, 5, '2024-02-26'], [False, '2024-03-04', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0]),
('three periods before anchor weekly', ['2024-03-04', 1, 5, '2024-02-12'], [False, '2024-03-04', 0]),
('beyond series end', ['2024-03-04', 1, 2, '2024-04-30'], [False, None, None]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])],
[('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('aligned before anchor weekly', ['2024-03-04', 1, 5, '2024-02-26'], [False, '2024-03-04', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0]),
('three periods before anchor weekly', ['2024-03-04', 1, 5, '2024-02-12'], [False, '2024-03-04', 0]),
('beyond series end', ['2024-03-04', 1, 2, '2024-04-30'], [False, None, None]),
('penultimate occurrence', ['2024-06-07', 2, 4, '2024-07-05'], [True, '2024-07-19', 3]),
('regression: aligned date before anchor', ['2024-01-05', 2, 10, '2023-12-22'], [False, '2024-01-05', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
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 |
|---|---|---|---|
| regression: aligned date before anchor | [False, '2024-01-05', 0] | [False, '2024-01-05', 0] | Passed |
| query on anchor | [True, '2024-01-05', 0] | [True, '2024-01-19', 1] | Failed |
| query on later occurrence | [True, '2024-02-16', 3] | [True, '2024-02-16', 3] | Passed |
| day before occurrence | [False, '2024-01-19', 1] | [False, '2024-01-19', 1] | Passed |
| one period after last | [False, None, None] | [False, None, None] | Passed |
| query on last occurrence | [True, None, None] | [True, None, None] | Passed |
| far before anchor | [False, '2024-01-05', 0] | [False, '2024-01-05', 0] | Passed |
| two periods before anchor | [False, '2024-01-05', 0] | [False, '2024-01-05', 0] | Passed |
SHA-256 / 067e9d914e52ad04362ab25bd2c02112b5fc11a6d0cbe60a2c85777a8f4b3f40
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import datetime as dt
import calendar
N = 1
observations = []
def solve(anchor, interval_weeks, count, query):
a = dt.date.fromisoformat(anchor)
q = dt.date.fromisoformat(query)
period = 7 * interval_weeks
diff = (q - a).days
is_occ = diff >= 0 and diff % period == 0 and diff // period < count
if diff < 0 and diff % period != 0:
idx = 0
else:
idx = diff // period + 1
if idx >= count:
return [is_occ, None, None]
return [is_occ, (a + dt.timedelta(days=idx * period)).isoformat(), idx]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: aligned date before anchor', ['2024-01-05', 2, 10, '2023-12-22'], [False, '2024-01-05', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1]),
('query on later occurrence', ['2024-01-05', 2, 10, '2024-02-02'], [True, '2024-02-16', 3]),
('day before occurrence', ['2024-01-05', 2, 10, '2024-01-18'], [False, '2024-01-19', 1]),
('one period after last', ['2024-01-05', 2, 3, '2024-02-16'], [False, None, None]),
('query on last occurrence', ['2024-01-05', 2, 3, '2024-02-02'], [True, None, None]),
('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0])],
[('query on later occurrence', ['2024-01-05', 2, 10, '2024-02-02'], [True, '2024-02-16', 3]),
('day before occurrence', ['2024-01-05', 2, 10, '2024-01-18'], [False, '2024-01-19', 1]),
('one period after last', ['2024-01-05', 2, 3, '2024-02-16'], [False, None, None]),
('query on last occurrence', ['2024-01-05', 2, 3, '2024-02-02'], [True, None, None]),
('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('weekly mid week', ['2024-03-04', 1, 5, '2024-03-13'], [False, '2024-03-18', 2]),
('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('three periods before anchor weekly', ['2024-03-04', 1, 5, '2024-02-12'], [False, '2024-03-04', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])],
[('one period after last', ['2024-01-05', 2, 3, '2024-02-16'], [False, None, None]),
('query on last occurrence', ['2024-01-05', 2, 3, '2024-02-02'], [True, None, None]),
('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('weekly mid week', ['2024-03-04', 1, 5, '2024-03-13'], [False, '2024-03-18', 2]),
('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('aligned before anchor weekly', ['2024-03-04', 1, 5, '2024-02-26'], [False, '2024-03-04', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])],
[('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('weekly mid week', ['2024-03-04', 1, 5, '2024-03-13'], [False, '2024-03-18', 2]),
('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('aligned before anchor weekly', ['2024-03-04', 1, 5, '2024-02-26'], [False, '2024-03-04', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0]),
('three periods before anchor weekly', ['2024-03-04', 1, 5, '2024-02-12'], [False, '2024-03-04', 0]),
('beyond series end', ['2024-03-04', 1, 2, '2024-04-30'], [False, None, None]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])],
[('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('aligned before anchor weekly', ['2024-03-04', 1, 5, '2024-02-26'], [False, '2024-03-04', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0]),
('three periods before anchor weekly', ['2024-03-04', 1, 5, '2024-02-12'], [False, '2024-03-04', 0]),
('beyond series end', ['2024-03-04', 1, 2, '2024-04-30'], [False, None, None]),
('penultimate occurrence', ['2024-06-07', 2, 4, '2024-07-05'], [True, '2024-07-19', 3]),
('regression: aligned date before anchor', ['2024-01-05', 2, 10, '2023-12-22'], [False, '2024-01-05', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
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 |
|---|---|---|---|
| regression: aligned date before anchor | [False, '2024-01-05', 0] | [False, '2024-01-05', 0] | Passed |
| query on anchor | [True, '2024-01-19', 1] | [True, '2024-01-19', 1] | Passed |
| query on later occurrence | [True, '2024-02-16', 3] | [True, '2024-02-16', 3] | Passed |
| day before occurrence | [False, '2024-01-19', 1] | [False, '2024-01-19', 1] | Passed |
| one period after last | [False, None, None] | [False, None, None] | Passed |
| query on last occurrence | [True, None, None] | [True, None, None] | Passed |
| far before anchor | [False, '2024-01-05', 0] | [False, '2024-01-05', 0] | Passed |
| two periods before anchor | [False, '2023-12-22', -1] | [False, '2024-01-05', 0] | Failed |
SHA-256 / 3c4393d9c84cca16dc97dfd5b06475494d5252bada5b462014760dbca7fe51c3
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import datetime as dt
import calendar
N = 1
observations = []
def solve(anchor, interval_weeks, count, query):
a = dt.date.fromisoformat(anchor)
q = dt.date.fromisoformat(query)
period = 7 * interval_weeks
diff = (q - a).days
is_occ = diff >= 0 and diff % period == 0 and diff // period < count
if diff < 0:
idx = 0
else:
idx = diff // period + 1
if idx >= count:
return [is_occ, None, None]
return [is_occ, (a + dt.timedelta(days=idx * period)).isoformat(), idx]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: aligned date before anchor', ['2024-01-05', 2, 10, '2023-12-22'], [False, '2024-01-05', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1]),
('query on later occurrence', ['2024-01-05', 2, 10, '2024-02-02'], [True, '2024-02-16', 3]),
('day before occurrence', ['2024-01-05', 2, 10, '2024-01-18'], [False, '2024-01-19', 1]),
('one period after last', ['2024-01-05', 2, 3, '2024-02-16'], [False, None, None]),
('query on last occurrence', ['2024-01-05', 2, 3, '2024-02-02'], [True, None, None]),
('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0])],
[('query on later occurrence', ['2024-01-05', 2, 10, '2024-02-02'], [True, '2024-02-16', 3]),
('day before occurrence', ['2024-01-05', 2, 10, '2024-01-18'], [False, '2024-01-19', 1]),
('one period after last', ['2024-01-05', 2, 3, '2024-02-16'], [False, None, None]),
('query on last occurrence', ['2024-01-05', 2, 3, '2024-02-02'], [True, None, None]),
('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('weekly mid week', ['2024-03-04', 1, 5, '2024-03-13'], [False, '2024-03-18', 2]),
('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('three periods before anchor weekly', ['2024-03-04', 1, 5, '2024-02-12'], [False, '2024-03-04', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])],
[('one period after last', ['2024-01-05', 2, 3, '2024-02-16'], [False, None, None]),
('query on last occurrence', ['2024-01-05', 2, 3, '2024-02-02'], [True, None, None]),
('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('weekly mid week', ['2024-03-04', 1, 5, '2024-03-13'], [False, '2024-03-18', 2]),
('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('aligned before anchor weekly', ['2024-03-04', 1, 5, '2024-02-26'], [False, '2024-03-04', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])],
[('far before anchor', ['2024-01-05', 3, 4, '2023-11-01'], [False, '2024-01-05', 0]),
('weekly mid week', ['2024-03-04', 1, 5, '2024-03-13'], [False, '2024-03-18', 2]),
('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('aligned before anchor weekly', ['2024-03-04', 1, 5, '2024-02-26'], [False, '2024-03-04', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0]),
('three periods before anchor weekly', ['2024-03-04', 1, 5, '2024-02-12'], [False, '2024-03-04', 0]),
('beyond series end', ['2024-03-04', 1, 2, '2024-04-30'], [False, None, None]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])],
[('four-weekly aligned', ['2024-03-04', 4, 6, '2024-04-29'], [True, '2024-05-27', 3]),
('aligned before anchor weekly', ['2024-03-04', 1, 5, '2024-02-26'], [False, '2024-03-04', 0]),
('two periods before anchor', ['2024-01-05', 2, 10, '2023-12-08'], [False, '2024-01-05', 0]),
('three periods before anchor weekly', ['2024-03-04', 1, 5, '2024-02-12'], [False, '2024-03-04', 0]),
('beyond series end', ['2024-03-04', 1, 2, '2024-04-30'], [False, None, None]),
('penultimate occurrence', ['2024-06-07', 2, 4, '2024-07-05'], [True, '2024-07-19', 3]),
('regression: aligned date before anchor', ['2024-01-05', 2, 10, '2023-12-22'], [False, '2024-01-05', 0]),
('query on anchor', ['2024-01-05', 2, 10, '2024-01-05'], [True, '2024-01-19', 1])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
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 |
|---|---|---|---|
| regression: aligned date before anchor | [False, '2024-01-05', 0] | [False, '2024-01-05', 0] | Passed |
| query on anchor | [True, '2024-01-19', 1] | [True, '2024-01-19', 1] | Passed |
| query on later occurrence | [True, '2024-02-16', 3] | [True, '2024-02-16', 3] | Passed |
| day before occurrence | [False, '2024-01-19', 1] | [False, '2024-01-19', 1] | Passed |
| one period after last | [False, None, None] | [False, None, None] | Passed |
| query on last occurrence | [True, None, None] | [True, None, None] | Passed |
| far before anchor | [False, '2024-01-05', 0] | [False, '2024-01-05', 0] | Passed |
| two periods before anchor | [False, '2024-01-05', 0] | [False, '2024-01-05', 0] | Passed |
SHA-256 / cd9152816aa5112282b7f66a355b7effc591b3a0793fd2c473320b13f4d41844
Verification & scope
Stipulated, bounded recurrence-expansion model evaluated offline on explicit fixtures; it is not a complete iCalendar implementation and makes no claim of standards conformance. 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:49:21.978384+00:00.
Case digest / 61def5bc9f64e04c4801829b1c5fc188424b711cd5887fa5e256e7196045d18e