FA-77206 / Calendar recurrence rules / Open access
UNTIL value forms and COUNT/UNTIL precedence for timed series: until compare · case 01
An instance exactly at a date-time UNTIL is missing.
ROOT CAUSE
The UNTIL bound is treated as exclusive.
VERIFIED REPAIR
At the until compare decision use `t > limit`, leaving the other decision sites of the model unchanged.
Unsuccessful approach: The partial repair `t.date() > limit.date()` compares dates only, so an instance later on the UNTIL day than the UNTIL time is kept.
Case contract
A daily-interval timed series in a fixed local offset (offset_min east of UTC). until may be None, a date (inclusive through the end of that local day, i.e. 23:59), a local date-time (inclusive), or a UTC date-time ending in Z (converted to local by adding offset_min). count may be None; count 0 means no instances. When both are given the series stops at whichever limit is reached first. Unbounded series are capped at 40 instances. Return ISO local date-times.
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(dtstart, interval, count, until, offset_min):
first = dt.datetime.fromisoformat(dtstart)
if until is None:
limit = None
elif until.endswith('Z'):
limit = dt.datetime.fromisoformat(until[:-1]) + dt.timedelta(minutes=offset_min)
elif len(until) == 10:
limit = dt.datetime.combine(dt.date.fromisoformat(until), dt.time(23, 59))
else:
limit = dt.datetime.fromisoformat(until)
out = []
for k in range(40):
t = first + dt.timedelta(days=k * interval)
if limit is not None and t >= limit:
break
if count is not None and len(out) >= count:
break
out.append(t.isoformat(timespec='minutes'))
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: count and until both set',
['2024-01-01T09:00', 1, 3, '2024-01-10', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('date until includes that day',
['2024-01-01T09:00', 2, None, '2024-01-07', 0],
['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00']),
('date until evening event',
['2024-01-01T20:00', 1, None, '2024-01-03', 60],
['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00']),
('local until exact',
['2024-01-01T09:00', 1, None, '2024-01-04T09:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'])],
[('date until evening event',
['2024-01-01T20:00', 1, None, '2024-01-03', 60],
['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00']),
('local until exact',
['2024-01-01T09:00', 1, None, '2024-01-04T09:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('count zero', ['2024-01-01T09:00', 1, 0, None, 0], []),
('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], [])],
[('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('count zero', ['2024-01-01T09:00', 1, 0, None, 0], []),
('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], []),
('until before count reached',
['2024-01-01T09:00', 3, 10, '2024-01-08', 0],
['2024-01-01T09:00', '2024-01-04T09:00', '2024-01-07T09:00']),
('count before until reached',
['2024-03-01T18:30', 7, 2, '2024-12-31T00:00Z', 60],
['2024-03-01T18:30', '2024-03-08T18:30'])],
[('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('count zero', ['2024-01-01T09:00', 1, 0, None, 0], []),
('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], []),
('until before count reached',
['2024-01-01T09:00', 3, 10, '2024-01-08', 0],
['2024-01-01T09:00', '2024-01-04T09:00', '2024-01-07T09:00']),
('count before until reached',
['2024-03-01T18:30', 7, 2, '2024-12-31T00:00Z', 60],
['2024-03-01T18:30', '2024-03-08T18:30']),
('regression: count and until both set',
['2024-01-01T09:00', 1, 3, '2024-01-10', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('date until includes that day',
['2024-01-01T09:00', 2, None, '2024-01-07', 0],
['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'])],
[('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], []),
('until before count reached',
['2024-01-01T09:00', 3, 10, '2024-01-08', 0],
['2024-01-01T09:00', '2024-01-04T09:00', '2024-01-07T09:00']),
('count before until reached',
['2024-03-01T18:30', 7, 2, '2024-12-31T00:00Z', 60],
['2024-03-01T18:30', '2024-03-08T18:30']),
('regression: count and until both set',
['2024-01-01T09:00', 1, 3, '2024-01-10', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('date until includes that day',
['2024-01-01T09:00', 2, None, '2024-01-07', 0],
['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00']),
('date until evening event',
['2024-01-01T20:00', 1, None, '2024-01-03', 60],
['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00']),
('local until exact',
['2024-01-01T09:00', 1, None, '2024-01-04T09:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'])]]
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: count and until both set | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Passed |
| date until includes that day | ['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00'] | ['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00'] | Passed |
| date until evening event | ['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00'] | ['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00'] | Passed |
| local until exact | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00'] | Failed |
| local until earlier time | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Passed |
| utc until east offset | ['2024-01-01T09:00', '2024-01-02T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Failed |
| utc until west offset | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Passed |
SHA-256 / a97b0b48caf8cadbed5369663adbe0e8f092584dc9a47b6929683f1924d4db1a
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(dtstart, interval, count, until, offset_min):
first = dt.datetime.fromisoformat(dtstart)
if until is None:
limit = None
elif until.endswith('Z'):
limit = dt.datetime.fromisoformat(until[:-1]) + dt.timedelta(minutes=offset_min)
elif len(until) == 10:
limit = dt.datetime.combine(dt.date.fromisoformat(until), dt.time(23, 59))
else:
limit = dt.datetime.fromisoformat(until)
out = []
for k in range(40):
t = first + dt.timedelta(days=k * interval)
if limit is not None and t.date() > limit.date():
break
if count is not None and len(out) >= count:
break
out.append(t.isoformat(timespec='minutes'))
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: count and until both set',
['2024-01-01T09:00', 1, 3, '2024-01-10', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('date until includes that day',
['2024-01-01T09:00', 2, None, '2024-01-07', 0],
['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00']),
('date until evening event',
['2024-01-01T20:00', 1, None, '2024-01-03', 60],
['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00']),
('local until exact',
['2024-01-01T09:00', 1, None, '2024-01-04T09:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'])],
[('date until evening event',
['2024-01-01T20:00', 1, None, '2024-01-03', 60],
['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00']),
('local until exact',
['2024-01-01T09:00', 1, None, '2024-01-04T09:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('count zero', ['2024-01-01T09:00', 1, 0, None, 0], []),
('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], [])],
[('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('count zero', ['2024-01-01T09:00', 1, 0, None, 0], []),
('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], []),
('until before count reached',
['2024-01-01T09:00', 3, 10, '2024-01-08', 0],
['2024-01-01T09:00', '2024-01-04T09:00', '2024-01-07T09:00']),
('count before until reached',
['2024-03-01T18:30', 7, 2, '2024-12-31T00:00Z', 60],
['2024-03-01T18:30', '2024-03-08T18:30'])],
[('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('count zero', ['2024-01-01T09:00', 1, 0, None, 0], []),
('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], []),
('until before count reached',
['2024-01-01T09:00', 3, 10, '2024-01-08', 0],
['2024-01-01T09:00', '2024-01-04T09:00', '2024-01-07T09:00']),
('count before until reached',
['2024-03-01T18:30', 7, 2, '2024-12-31T00:00Z', 60],
['2024-03-01T18:30', '2024-03-08T18:30']),
('regression: count and until both set',
['2024-01-01T09:00', 1, 3, '2024-01-10', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('date until includes that day',
['2024-01-01T09:00', 2, None, '2024-01-07', 0],
['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'])],
[('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], []),
('until before count reached',
['2024-01-01T09:00', 3, 10, '2024-01-08', 0],
['2024-01-01T09:00', '2024-01-04T09:00', '2024-01-07T09:00']),
('count before until reached',
['2024-03-01T18:30', 7, 2, '2024-12-31T00:00Z', 60],
['2024-03-01T18:30', '2024-03-08T18:30']),
('regression: count and until both set',
['2024-01-01T09:00', 1, 3, '2024-01-10', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('date until includes that day',
['2024-01-01T09:00', 2, None, '2024-01-07', 0],
['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00']),
('date until evening event',
['2024-01-01T20:00', 1, None, '2024-01-03', 60],
['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00']),
('local until exact',
['2024-01-01T09:00', 1, None, '2024-01-04T09:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'])]]
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: count and until both set | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Passed |
| date until includes that day | ['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00'] | ['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00'] | Passed |
| date until evening event | ['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00'] | ['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00'] | Passed |
| local until exact | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00'] | Passed |
| local until earlier time | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Failed |
| utc until east offset | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Passed |
| utc until west offset | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Passed |
SHA-256 / 76c4afe98358f51edc5fe0a8031c2634dbe110877433d26fa7a49849b1fde1af
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(dtstart, interval, count, until, offset_min):
first = dt.datetime.fromisoformat(dtstart)
if until is None:
limit = None
elif until.endswith('Z'):
limit = dt.datetime.fromisoformat(until[:-1]) + dt.timedelta(minutes=offset_min)
elif len(until) == 10:
limit = dt.datetime.combine(dt.date.fromisoformat(until), dt.time(23, 59))
else:
limit = dt.datetime.fromisoformat(until)
out = []
for k in range(40):
t = first + dt.timedelta(days=k * interval)
if limit is not None and t > limit:
break
if count is not None and len(out) >= count:
break
out.append(t.isoformat(timespec='minutes'))
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: count and until both set',
['2024-01-01T09:00', 1, 3, '2024-01-10', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('date until includes that day',
['2024-01-01T09:00', 2, None, '2024-01-07', 0],
['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00']),
('date until evening event',
['2024-01-01T20:00', 1, None, '2024-01-03', 60],
['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00']),
('local until exact',
['2024-01-01T09:00', 1, None, '2024-01-04T09:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'])],
[('date until evening event',
['2024-01-01T20:00', 1, None, '2024-01-03', 60],
['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00']),
('local until exact',
['2024-01-01T09:00', 1, None, '2024-01-04T09:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('count zero', ['2024-01-01T09:00', 1, 0, None, 0], []),
('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], [])],
[('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('count zero', ['2024-01-01T09:00', 1, 0, None, 0], []),
('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], []),
('until before count reached',
['2024-01-01T09:00', 3, 10, '2024-01-08', 0],
['2024-01-01T09:00', '2024-01-04T09:00', '2024-01-07T09:00']),
('count before until reached',
['2024-03-01T18:30', 7, 2, '2024-12-31T00:00Z', 60],
['2024-03-01T18:30', '2024-03-08T18:30'])],
[('utc until west offset',
['2024-01-01T09:00', 1, None, '2024-01-03T15:30Z', -300],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('count zero', ['2024-01-01T09:00', 1, 0, None, 0], []),
('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], []),
('until before count reached',
['2024-01-01T09:00', 3, 10, '2024-01-08', 0],
['2024-01-01T09:00', '2024-01-04T09:00', '2024-01-07T09:00']),
('count before until reached',
['2024-03-01T18:30', 7, 2, '2024-12-31T00:00Z', 60],
['2024-03-01T18:30', '2024-03-08T18:30']),
('regression: count and until both set',
['2024-01-01T09:00', 1, 3, '2024-01-10', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('date until includes that day',
['2024-01-01T09:00', 2, None, '2024-01-07', 0],
['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('utc until east offset',
['2024-01-01T09:00', 1, None, '2024-01-03T07:00Z', 120],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'])],
[('count zero with until', ['2024-01-01T09:00', 1, 0, '2024-02-01', 0], []),
('until before count reached',
['2024-01-01T09:00', 3, 10, '2024-01-08', 0],
['2024-01-01T09:00', '2024-01-04T09:00', '2024-01-07T09:00']),
('count before until reached',
['2024-03-01T18:30', 7, 2, '2024-12-31T00:00Z', 60],
['2024-03-01T18:30', '2024-03-08T18:30']),
('regression: count and until both set',
['2024-01-01T09:00', 1, 3, '2024-01-10', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']),
('date until includes that day',
['2024-01-01T09:00', 2, None, '2024-01-07', 0],
['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00']),
('date until evening event',
['2024-01-01T20:00', 1, None, '2024-01-03', 60],
['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00']),
('local until exact',
['2024-01-01T09:00', 1, None, '2024-01-04T09:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00']),
('local until earlier time',
['2024-01-01T09:00', 1, None, '2024-01-04T08:00', 0],
['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'])]]
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: count and until both set | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Passed |
| date until includes that day | ['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00'] | ['2024-01-01T09:00', '2024-01-03T09:00', '2024-01-05T09:00', '2024-01-07T09:00'] | Passed |
| date until evening event | ['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00'] | ['2024-01-01T20:00', '2024-01-02T20:00', '2024-01-03T20:00'] | Passed |
| local until exact | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00'] | Passed |
| local until earlier time | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Passed |
| utc until east offset | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Passed |
| utc until west offset | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | ['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00'] | Passed |
SHA-256 / 13fbb784d2e2b875dca02e455df5cebcaeaaf89685195522df5874f84a9c985f
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:23.303858+00:00.
Case digest / 921f7ddcfc629afd1ec1903ba5aea1104b35b109e16fad0dbba57b1f0a3a4d5a