FA-77196 / Calendar recurrence rules / Open access
UNTIL value forms and COUNT/UNTIL precedence for timed series: utc until · case 01
A UTC UNTIL in a zone east of UTC cuts off the last local instance.
ROOT CAUSE
The UTC UNTIL is converted to local time in the wrong direction.
VERIFIED REPAIR
At the utc until decision use `dt.datetime.fromisoformat(until[:-1]) + dt.timedelta(minutes=offset_min)`, leaving the other decision sites of the model unchanged.
Unsuccessful approach: The partial repair `dt.datetime.fromisoformat(until[:-1])` ignores the offset and compares UTC with local wall time.
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']),
('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']),
('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'])]]
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-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 / 35caf3d5528e3518b8d7a1a1efae696b8dd98b91cf5ea65286b7a961142ff88b
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])
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']),
('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']),
('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'])]]
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-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 / c303c34bce9985cd9ae60574cbb079a75116e13b84200690414109fa4fb2dcd7
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']),
('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']),
('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'])]]
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 / 62d860073f1d023df60524763ad01747833336b8c39e0d220444c8616185034e
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.255380+00:00.
Case digest / 1fba92f1a29a671dd2213e5dff32ef486c39e93c4ecf2926598e3c07ec1092b7