FAILURE MAP
← Case archive

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.

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

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 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-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 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-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 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

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