FAILURE MAP
← Case archive

FA-77011 / Calendar recurrence rules / Open access

Splitting a series for a this-and-following edit: tail start · case 01

The new tail series begins on a date that was never an occurrence.

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

ROOT CAUSE

The split occurrence is located using the new interval instead of the original one.

THE FAILURE

The split occurrence is located using the new interval instead of the original one.

Unsuccessful approach: The partial repair `start + dt.timedelta(days=k)` ignores the interval entirely.

Case contract

A daily-interval series (dtstart, interval, and either count or until). Editing from 0-based occurrence k splits it: the head keeps occurrences 0..k-1 (count k, or until = its last retained occurrence date) and the tail starts at the original date of occurrence k with new_interval and the remaining count (or the original until). k <= 0 makes the head None; a split at or past the end makes the tail None. Return {"head": [dtstart, count, until] or None, "tail": [start, interval, count, until] or None}.

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, k, new_interval):
    start = dt.date.fromisoformat(dtstart)
    until_d = dt.date.fromisoformat(until) if until else None
    tail_start = start + dt.timedelta(days=k * new_interval)
    if (count is not None and k >= count) or (until_d is not None and tail_start > until_d):
        tail = None
    else:
        tail = [tail_start.isoformat(), new_interval, count - k if count is not None else None, until]
    if k <= 0:
        head = None
    elif count is not None:
        head = [dtstart, k, None]
    else:
        head = [dtstart, None, (start + dt.timedelta(days=(k - 1) * interval)).isoformat()]
    return {'head': head, 'tail': tail}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: count series split at 3',
   ['2024-01-01', 2, 10, None, 3, 7],
   {'head': ['2024-01-01', 3, None], 'tail': ['2024-01-07', 7, 7, None]}),
  ('until series split at 2',
   ['2024-01-01', 3, None, '2024-02-01', 2, 1],
   {'head': ['2024-01-01', None, '2024-01-04'], 'tail': ['2024-01-07', 1, None, '2024-02-01']}),
  ('split at first occurrence',
   ['2024-01-01', 1, 5, None, 0, 2],
   {'head': None, 'tail': ['2024-01-01', 2, 5, None]}),
  ('until split at first',
   ['2024-03-01', 7, None, '2024-06-01', 0, 14],
   {'head': None, 'tail': ['2024-03-01', 14, None, '2024-06-01']}),
  ('split exactly at count',
   ['2024-01-01', 1, 4, None, 4, 2],
   {'head': ['2024-01-01', 4, None], 'tail': None}),
  ('split past until',
   ['2024-01-01', 5, None, '2024-01-20', 5, 2],
   {'head': ['2024-01-01', None, '2024-01-21'], 'tail': None}),
  ('split at last until occurrence',
   ['2024-01-01', 5, None, '2024-01-21', 4, 1],
   {'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-21', 1, None, '2024-01-21']})],
 [('split at first occurrence',
   ['2024-01-01', 1, 5, None, 0, 2],
   {'head': None, 'tail': ['2024-01-01', 2, 5, None]}),
  ('until split at first',
   ['2024-03-01', 7, None, '2024-06-01', 0, 14],
   {'head': None, 'tail': ['2024-03-01', 14, None, '2024-06-01']}),
  ('split exactly at count',
   ['2024-01-01', 1, 4, None, 4, 2],
   {'head': ['2024-01-01', 4, None], 'tail': None}),
  ('split past until',
   ['2024-01-01', 5, None, '2024-01-20', 5, 2],
   {'head': ['2024-01-01', None, '2024-01-21'], 'tail': None}),
  ('split at last until occurrence',
   ['2024-01-01', 5, None, '2024-01-21', 4, 1],
   {'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-21', 1, None, '2024-01-21']}),
  ('count split near end',
   ['2024-05-10', 1, 6, None, 5, 3],
   {'head': ['2024-05-10', 5, None], 'tail': ['2024-05-15', 3, 1, None]}),
  ('until split mid',
   ['2024-05-10', 4, None, '2024-07-01', 6, 2],
   {'head': ['2024-05-10', None, '2024-05-30'], 'tail': ['2024-06-03', 2, None, '2024-07-01']})],
 [('split exactly at count',
   ['2024-01-01', 1, 4, None, 4, 2],
   {'head': ['2024-01-01', 4, None], 'tail': None}),
  ('split past until',
   ['2024-01-01', 5, None, '2024-01-20', 5, 2],
   {'head': ['2024-01-01', None, '2024-01-21'], 'tail': None}),
  ('split at last until occurrence',
   ['2024-01-01', 5, None, '2024-01-21', 4, 1],
   {'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-21', 1, None, '2024-01-21']}),
  ('count split near end',
   ['2024-05-10', 1, 6, None, 5, 3],
   {'head': ['2024-05-10', 5, None], 'tail': ['2024-05-15', 3, 1, None]}),
  ('until split mid',
   ['2024-05-10', 4, None, '2024-07-01', 6, 2],
   {'head': ['2024-05-10', None, '2024-05-30'], 'tail': ['2024-06-03', 2, None, '2024-07-01']}),
  ('interval 1 count split',
   ['2024-12-30', 1, 8, None, 2, 1],
   {'head': ['2024-12-30', 2, None], 'tail': ['2025-01-01', 1, 6, None]}),
  ('split beyond count', ['2024-01-01', 1, 3, None, 7, 2], {'head': ['2024-01-01', 7, None], 'tail': None})],
 [('split at last until occurrence',
   ['2024-01-01', 5, None, '2024-01-21', 4, 1],
   {'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-21', 1, None, '2024-01-21']}),
  ('count split near end',
   ['2024-05-10', 1, 6, None, 5, 3],
   {'head': ['2024-05-10', 5, None], 'tail': ['2024-05-15', 3, 1, None]}),
  ('until split mid',
   ['2024-05-10', 4, None, '2024-07-01', 6, 2],
   {'head': ['2024-05-10', None, '2024-05-30'], 'tail': ['2024-06-03', 2, None, '2024-07-01']}),
  ('interval 1 count split',
   ['2024-12-30', 1, 8, None, 2, 1],
   {'head': ['2024-12-30', 2, None], 'tail': ['2025-01-01', 1, 6, None]}),
  ('split beyond count', ['2024-01-01', 1, 3, None, 7, 2], {'head': ['2024-01-01', 7, None], 'tail': None}),
  ('regression: count series split at 3',
   ['2024-01-01', 2, 10, None, 3, 7],
   {'head': ['2024-01-01', 3, None], 'tail': ['2024-01-07', 7, 7, None]}),
  ('until series split at 2',
   ['2024-01-01', 3, None, '2024-02-01', 2, 1],
   {'head': ['2024-01-01', None, '2024-01-04'], 'tail': ['2024-01-07', 1, None, '2024-02-01']})],
 [('until split mid',
   ['2024-05-10', 4, None, '2024-07-01', 6, 2],
   {'head': ['2024-05-10', None, '2024-05-30'], 'tail': ['2024-06-03', 2, None, '2024-07-01']}),
  ('interval 1 count split',
   ['2024-12-30', 1, 8, None, 2, 1],
   {'head': ['2024-12-30', 2, None], 'tail': ['2025-01-01', 1, 6, None]}),
  ('split beyond count', ['2024-01-01', 1, 3, None, 7, 2], {'head': ['2024-01-01', 7, None], 'tail': None}),
  ('regression: count series split at 3',
   ['2024-01-01', 2, 10, None, 3, 7],
   {'head': ['2024-01-01', 3, None], 'tail': ['2024-01-07', 7, 7, None]}),
  ('until series split at 2',
   ['2024-01-01', 3, None, '2024-02-01', 2, 1],
   {'head': ['2024-01-01', None, '2024-01-04'], 'tail': ['2024-01-07', 1, None, '2024-02-01']}),
  ('split at first occurrence',
   ['2024-01-01', 1, 5, None, 0, 2],
   {'head': None, 'tail': ['2024-01-01', 2, 5, None]}),
  ('until split at first',
   ['2024-03-01', 7, None, '2024-06-01', 0, 14],
   {'head': None, 'tail': ['2024-03-01', 14, None, '2024-06-01']})]]
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 series split at 3{'head': ['2024-01-01', 3, None], 'tail': ['2024-01-22', 7, 7, None]}{'head': ['2024-01-01', 3, None], 'tail': ['2024-01-07', 7, 7, None]}Failed
until series split at 2{'head': ['2024-01-01', None, '2024-01-04'], 'tail': ['2024-01-03', 1, None, '2024-02-01']}{'head': ['2024-01-01', None, '2024-01-04'], 'tail': ['2024-01-07', 1, None, '2024-02-01']}Failed
split at first occurrence{'head': None, 'tail': ['2024-01-01', 2, 5, None]}{'head': None, 'tail': ['2024-01-01', 2, 5, None]}Passed
until split at first{'head': None, 'tail': ['2024-03-01', 14, None, '2024-06-01']}{'head': None, 'tail': ['2024-03-01', 14, None, '2024-06-01']}Passed
split exactly at count{'head': ['2024-01-01', 4, None], 'tail': None}{'head': ['2024-01-01', 4, None], 'tail': None}Passed
split past until{'head': ['2024-01-01', None, '2024-01-21'], 'tail': ['2024-01-11', 2, None, '2024-01-20']}{'head': ['2024-01-01', None, '2024-01-21'], 'tail': None}Failed
split at last until occurrence{'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-05', 1, None, '2024-01-21']}{'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-21', 1, None, '2024-01-21']}Failed

SHA-256 / 9dd3162bbd3fc46715d6791652d099301f7b257a7eaca606631d04dcc1682e2f

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, k, new_interval):
    start = dt.date.fromisoformat(dtstart)
    until_d = dt.date.fromisoformat(until) if until else None
    tail_start = start + dt.timedelta(days=k)
    if (count is not None and k >= count) or (until_d is not None and tail_start > until_d):
        tail = None
    else:
        tail = [tail_start.isoformat(), new_interval, count - k if count is not None else None, until]
    if k <= 0:
        head = None
    elif count is not None:
        head = [dtstart, k, None]
    else:
        head = [dtstart, None, (start + dt.timedelta(days=(k - 1) * interval)).isoformat()]
    return {'head': head, 'tail': tail}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: count series split at 3',
   ['2024-01-01', 2, 10, None, 3, 7],
   {'head': ['2024-01-01', 3, None], 'tail': ['2024-01-07', 7, 7, None]}),
  ('until series split at 2',
   ['2024-01-01', 3, None, '2024-02-01', 2, 1],
   {'head': ['2024-01-01', None, '2024-01-04'], 'tail': ['2024-01-07', 1, None, '2024-02-01']}),
  ('split at first occurrence',
   ['2024-01-01', 1, 5, None, 0, 2],
   {'head': None, 'tail': ['2024-01-01', 2, 5, None]}),
  ('until split at first',
   ['2024-03-01', 7, None, '2024-06-01', 0, 14],
   {'head': None, 'tail': ['2024-03-01', 14, None, '2024-06-01']}),
  ('split exactly at count',
   ['2024-01-01', 1, 4, None, 4, 2],
   {'head': ['2024-01-01', 4, None], 'tail': None}),
  ('split past until',
   ['2024-01-01', 5, None, '2024-01-20', 5, 2],
   {'head': ['2024-01-01', None, '2024-01-21'], 'tail': None}),
  ('split at last until occurrence',
   ['2024-01-01', 5, None, '2024-01-21', 4, 1],
   {'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-21', 1, None, '2024-01-21']})],
 [('split at first occurrence',
   ['2024-01-01', 1, 5, None, 0, 2],
   {'head': None, 'tail': ['2024-01-01', 2, 5, None]}),
  ('until split at first',
   ['2024-03-01', 7, None, '2024-06-01', 0, 14],
   {'head': None, 'tail': ['2024-03-01', 14, None, '2024-06-01']}),
  ('split exactly at count',
   ['2024-01-01', 1, 4, None, 4, 2],
   {'head': ['2024-01-01', 4, None], 'tail': None}),
  ('split past until',
   ['2024-01-01', 5, None, '2024-01-20', 5, 2],
   {'head': ['2024-01-01', None, '2024-01-21'], 'tail': None}),
  ('split at last until occurrence',
   ['2024-01-01', 5, None, '2024-01-21', 4, 1],
   {'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-21', 1, None, '2024-01-21']}),
  ('count split near end',
   ['2024-05-10', 1, 6, None, 5, 3],
   {'head': ['2024-05-10', 5, None], 'tail': ['2024-05-15', 3, 1, None]}),
  ('until split mid',
   ['2024-05-10', 4, None, '2024-07-01', 6, 2],
   {'head': ['2024-05-10', None, '2024-05-30'], 'tail': ['2024-06-03', 2, None, '2024-07-01']})],
 [('split exactly at count',
   ['2024-01-01', 1, 4, None, 4, 2],
   {'head': ['2024-01-01', 4, None], 'tail': None}),
  ('split past until',
   ['2024-01-01', 5, None, '2024-01-20', 5, 2],
   {'head': ['2024-01-01', None, '2024-01-21'], 'tail': None}),
  ('split at last until occurrence',
   ['2024-01-01', 5, None, '2024-01-21', 4, 1],
   {'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-21', 1, None, '2024-01-21']}),
  ('count split near end',
   ['2024-05-10', 1, 6, None, 5, 3],
   {'head': ['2024-05-10', 5, None], 'tail': ['2024-05-15', 3, 1, None]}),
  ('until split mid',
   ['2024-05-10', 4, None, '2024-07-01', 6, 2],
   {'head': ['2024-05-10', None, '2024-05-30'], 'tail': ['2024-06-03', 2, None, '2024-07-01']}),
  ('interval 1 count split',
   ['2024-12-30', 1, 8, None, 2, 1],
   {'head': ['2024-12-30', 2, None], 'tail': ['2025-01-01', 1, 6, None]}),
  ('split beyond count', ['2024-01-01', 1, 3, None, 7, 2], {'head': ['2024-01-01', 7, None], 'tail': None})],
 [('split at last until occurrence',
   ['2024-01-01', 5, None, '2024-01-21', 4, 1],
   {'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-21', 1, None, '2024-01-21']}),
  ('count split near end',
   ['2024-05-10', 1, 6, None, 5, 3],
   {'head': ['2024-05-10', 5, None], 'tail': ['2024-05-15', 3, 1, None]}),
  ('until split mid',
   ['2024-05-10', 4, None, '2024-07-01', 6, 2],
   {'head': ['2024-05-10', None, '2024-05-30'], 'tail': ['2024-06-03', 2, None, '2024-07-01']}),
  ('interval 1 count split',
   ['2024-12-30', 1, 8, None, 2, 1],
   {'head': ['2024-12-30', 2, None], 'tail': ['2025-01-01', 1, 6, None]}),
  ('split beyond count', ['2024-01-01', 1, 3, None, 7, 2], {'head': ['2024-01-01', 7, None], 'tail': None}),
  ('regression: count series split at 3',
   ['2024-01-01', 2, 10, None, 3, 7],
   {'head': ['2024-01-01', 3, None], 'tail': ['2024-01-07', 7, 7, None]}),
  ('until series split at 2',
   ['2024-01-01', 3, None, '2024-02-01', 2, 1],
   {'head': ['2024-01-01', None, '2024-01-04'], 'tail': ['2024-01-07', 1, None, '2024-02-01']})],
 [('until split mid',
   ['2024-05-10', 4, None, '2024-07-01', 6, 2],
   {'head': ['2024-05-10', None, '2024-05-30'], 'tail': ['2024-06-03', 2, None, '2024-07-01']}),
  ('interval 1 count split',
   ['2024-12-30', 1, 8, None, 2, 1],
   {'head': ['2024-12-30', 2, None], 'tail': ['2025-01-01', 1, 6, None]}),
  ('split beyond count', ['2024-01-01', 1, 3, None, 7, 2], {'head': ['2024-01-01', 7, None], 'tail': None}),
  ('regression: count series split at 3',
   ['2024-01-01', 2, 10, None, 3, 7],
   {'head': ['2024-01-01', 3, None], 'tail': ['2024-01-07', 7, 7, None]}),
  ('until series split at 2',
   ['2024-01-01', 3, None, '2024-02-01', 2, 1],
   {'head': ['2024-01-01', None, '2024-01-04'], 'tail': ['2024-01-07', 1, None, '2024-02-01']}),
  ('split at first occurrence',
   ['2024-01-01', 1, 5, None, 0, 2],
   {'head': None, 'tail': ['2024-01-01', 2, 5, None]}),
  ('until split at first',
   ['2024-03-01', 7, None, '2024-06-01', 0, 14],
   {'head': None, 'tail': ['2024-03-01', 14, None, '2024-06-01']})]]
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 series split at 3{'head': ['2024-01-01', 3, None], 'tail': ['2024-01-04', 7, 7, None]}{'head': ['2024-01-01', 3, None], 'tail': ['2024-01-07', 7, 7, None]}Failed
until series split at 2{'head': ['2024-01-01', None, '2024-01-04'], 'tail': ['2024-01-03', 1, None, '2024-02-01']}{'head': ['2024-01-01', None, '2024-01-04'], 'tail': ['2024-01-07', 1, None, '2024-02-01']}Failed
split at first occurrence{'head': None, 'tail': ['2024-01-01', 2, 5, None]}{'head': None, 'tail': ['2024-01-01', 2, 5, None]}Passed
until split at first{'head': None, 'tail': ['2024-03-01', 14, None, '2024-06-01']}{'head': None, 'tail': ['2024-03-01', 14, None, '2024-06-01']}Passed
split exactly at count{'head': ['2024-01-01', 4, None], 'tail': None}{'head': ['2024-01-01', 4, None], 'tail': None}Passed
split past until{'head': ['2024-01-01', None, '2024-01-21'], 'tail': ['2024-01-06', 2, None, '2024-01-20']}{'head': ['2024-01-01', None, '2024-01-21'], 'tail': None}Failed
split at last until occurrence{'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-05', 1, None, '2024-01-21']}{'head': ['2024-01-01', None, '2024-01-16'], 'tail': ['2024-01-21', 1, None, '2024-01-21']}Failed

SHA-256 / 65f4a6a6f7754917b2e5c4f9496b629e8edb0fb3e8d9adb103cc0802ab6c28e6

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 7 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

Verification & scope

Stipulated, bounded recurrence-expansion model evaluated offline on explicit fixtures; it is not a complete iCalendar implementation and makes no claim of standards conformance. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:49:21.809009+00:00.

Case digest / d21d1582e3caf5bace8d621ecee1aea60ea8a400c8433984a5177d1a3ab79fe4