FAILURE MAP
← Case archive

FA-77211 / Calendar recurrence rules / Open access

UNTIL value forms and COUNT/UNTIL precedence for timed series: count stop · case 01

A series with both COUNT and UNTIL runs past COUNT until UNTIL is reached.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

COUNT is ignored whenever UNTIL is present.

VERIFIED REPAIR

At the count stop decision use `count is not None and len(out) >= count`, leaving the other decision sites of the model unchanged.

Unsuccessful approach: The partial repair `count and len(out) >= count` treats COUNT=0 as unbounded.

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 limit is 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']),
  ('count zero', ['2024-01-01T09:00', 1, 0, None, 0], [])],
 [('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'])],
 [('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'])]]
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 fixtureActualExpectedOutcome
regression: count and until both set['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00', '2024-01-05T09:00', '2024-01-06T09:00', '2024-01-07T09:00', '2024-01-08T09:00', '2024-01-09T09:00', '2024-01-10T09:00']['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00']Failed
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
count zero[][]Passed

SHA-256 / 9b84d137f0f70b253afc105ba63cb03a88c7ab45d5d0dbdde733e7f23b588550

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 > limit:
            break
        if count 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']),
  ('count zero', ['2024-01-01T09:00', 1, 0, None, 0], [])],
 [('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'])],
 [('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'])]]
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 fixtureActualExpectedOutcome
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
count zero['2024-01-01T09:00', '2024-01-02T09:00', '2024-01-03T09:00', '2024-01-04T09:00', '2024-01-05T09:00', '2024-01-06T09:00', '2024-01-07T09:00', '2024-01-08T09:00', '2024-01-09T09:00', '2024-01-10T09:00', '2024-01-11T09:00', '2024-01-12T09:00', '2024-01-13T09:00', '2024-01-14T09:00', '2024-01-15T09:00', '2024-01-16T09:00', '2024-01-17T09:00', '2024-01-18T09:00', '2024-01-19T09:00', '2024-01-20T09:00', '2024-01-21T09:00', '2024-01-22T09:00', '2024-01-23T09:00', '2024-01-24T09:00', '2024-01-25T09:00', '2024-01-26T09:00', '2024-01-27T09:00', '2024-01-28T09:00', '2024-01-29T09:00', '2024-01-30T09:00', '2024-01-31T09:00', '2024-02-01T09:00', '2024-02-02T09:00', '2024-02-03T09:00', '2024-02-04T09:00', '2024-02-05T09:00', '2024-02-06T09:00', '2024-02-07T09:00', '2024-02-08T09:00', '2024-02-09T09:00'][]Failed

SHA-256 / dbc1b7e07d443a15f0f88752fa0da59e73f805cde1a2d668c0c57591423482b6

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']),
  ('count zero', ['2024-01-01T09:00', 1, 0, None, 0], [])],
 [('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'])],
 [('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'])]]
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 fixtureActualExpectedOutcome
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
count zero[][]Passed

SHA-256 / b09fd110f49ac53453900b1e1219edac8a59618dcb42706b116d1a4acb001d47

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.306607+00:00.

Case digest / fafa98be0103c53902f156be1792dc8fa1ff2600c1eb0a1efb939121e2d4fd0f