A moved recurring event keeps its original recurrence identity
A copyable UTC-only JSON model reproduces a single moved weekly occurrence while retaining its original RECURRENCE-ID, with three result rows and twelve scope guards; no calendar-client test.
Immutable revision: 3828b93963377c74845ba72bf270841d9f1c886512149948f0cce4204fdbd023. Publication and independent validation are separate; inspect metadata and provenance before use.
A moved recurring event keeps its original recurrence identity
When the October 12 occurrence moves to October 13, its RECURRENCE-ID stays October 12. The exception's DTSTART supplies the new start. RFC 5545 identifies an occurrence using UID, SEQUENCE and its original start value; for a given UID/SEQUENCE pair, the recurrence identifier remains fixed. Its value type must match the recurring component, preserving local-versus-UTC semantics. RFC 5545 §3.8.4.4
This fictional example holds UID and SEQUENCE constant and uses UTC DATE-TIME throughout. A three-occurrence weekly series begins October 5 at 09:00Z. The anchor counts as occurrence one. §3.3.10 Only the second occurrence moves, to October 13 at 11:00Z; the third stays October 19 at 09:00Z. The case excludes RANGE edits and changes between revision sequences.
| Original slot / identity | Scheduled start after the edit | Result |
|---|---|---|
| October 5, 09:00Z | October 5, 09:00Z | unchanged |
| October 12, 09:00Z | October 13, 11:00Z | moved occurrence |
| October 19, 09:00Z | October 19, 09:00Z | unchanged |
An inspection that replaces the recurrence identifier with October 13 loses the original-slot reference. In this local model that value is rejected because it matches none of the three generated slots. A different UID is rejected too: this model will not attach another series' exception. If a mistaken identifier names another valid slot, the model edits that other occurrence; membership alone cannot recover the author's intent. These are bounded model outcomes, not observed calendar-client error messages.
Reproduce the identity check
The files below form an original, deliberately small JSON teaching model. They are not an .ics file and cannot be imported into a calendar. JSON keys represent the named properties; value_type and timezone explicitly describe this fixture's representation. No VEVENT validity, DTSTAMP serialization or wire-level parsing is claimed.
Save the five named fences in an empty disposable directory, preserving UTF-8 without BOM, LF endings and exactly one final LF. Use CPython 3.12.3 and its standard library; there are no packages, credentials or network dependencies. Run python3 check.py > replay-report.json. It launches the model, writes observed.json, compares every ordered field and exact byte against the separately specified expected.json, and exits 0 only on equality with no child stderr. Output names are overwritten. Cleanup is deletion of this disposable directory after keeping any desired results.
The model generates the three original slots by seven-day UTC increments, then replaces the scheduled start only where the original slot equals the supplied recurrence identifier. Output order follows original slots, not a sort by moved start. Every output row retains its UID, sequence, original slot and scheduled start, plus a UTC local display, +00:00 offset and UTC instant. No local-zone conversion occurs.
Twelve mutations exercise wrong identity/UID, DATE versus DATE-TIME, floating values, named zones, gap/fold wall-clock strings, RANGE, another rule, another sequence, an impossible date and an extra property. Rejections enforce this tool's supported scope; they do not imply that every rejected form is invalid iCalendar. Named-zone, floating, fold/gap, leap-second, duration, cancellation and scheduling-update behavior remain outside this experiment. Only the exact positive fixture is checked by the embedded baseline; this is not a general-purpose recurrence library.
The author actually executed the included checker. The captured run below records UTC time, runtime, exit status, empty stderr and exact output digest: all three instance rows and twelve guard results matched. Expected and observed bytes share one accessible expected.json fence, but remain distinct expectation and execution claims. Report timestamps change on replay. Hashes identify bytes, not source authenticity or independent validation. No calendar application or invitation was used, and no independent editorial review is claimed.
Case profile and exact files
The two instance arrays use payload-reference objects: locator, format, encoding, bytes, sha256 and field_scope identify the complete named fence and fields. basis separates the hand-specified expectation from actual execution; actual_run identifies the captured execution report.
{
"schema": "calendar-case-v1",
"tags": [
"calendar",
"recurrence",
"timezone"
],
"product": "Wiki5 bounded recurrence identity model",
"client_version": "reproduce.py v1; CPython 3.12.3 standard library",
"api_or_ui": "python3 check.py > replay-report.json",
"checked_at": "2026-10-05T04:33:06.039938Z",
"tzdb_version": "not_applicable: UTC fixed offset only; no zone database lookup",
"timezone_id": "UTC",
"intent": "fixed-instant",
"input_fixture": {
"description": "JSON property model: DTSTART 20261005T090000Z, FREQ=WEEKLY;COUNT=3, one edited original slot 20261012T090000Z moved to 20261013T110000Z. No exclusions. UTC DATE-TIME throughout; UID and SEQUENCE preserved.",
"files": [
{
"file": "fixture.json",
"locator": "named fixture.json fence in this article",
"format": "JSON",
"encoding": "UTF-8 without BOM; LF; exactly one final LF",
"bytes": 456,
"sha256": "2c89b26dd3b5dc826f65879608aa64b9047e7e7b9779416099a3afdd60d14828"
},
{
"file": "reproduce.py",
"locator": "named reproduce.py fence in this article",
"format": "Python 3 source",
"encoding": "UTF-8 without BOM; LF; exactly one final LF",
"bytes": 3860,
"sha256": "e832c541d015e9754911fd248deb54e026c1ce7f52f9f814a6c8d52349523de7"
},
{
"file": "check.py",
"locator": "named check.py fence in this article",
"format": "Python 3 source",
"encoding": "UTF-8 without BOM; LF; exactly one final LF",
"bytes": 1348,
"sha256": "a324716a3e5adac5b4407f9bfe54b0f9fbbaa88111c7452e39fb2501494c53b1"
}
],
"icalendar_text": null,
"reproduction_command": "python3 check.py > replay-report.json"
},
"expected_instances": [
{
"file": "expected.json",
"locator": "named expected.json fence in this article",
"format": "JSON",
"encoding": "UTF-8 without BOM; LF; exactly one final LF",
"bytes": 1877,
"sha256": "f79b260d119327467eb27d85f3429412c1cf7c8720f45a8a4e43666634c2f727",
"field_scope": "instances[0:3]: uid, sequence, original_slot, scheduled_start, zone, utc_offset, local_datetime, utc_instant; guards[0:12]: case, result",
"basis": "Hand-specified dates and guard expectations; not generated by reproduce.py"
}
],
"observed_instances": [
{
"file": "expected.json",
"locator": "named expected.json fence in this article",
"format": "JSON",
"encoding": "UTF-8 without BOM; LF; exactly one final LF",
"bytes": 1877,
"sha256": "f79b260d119327467eb27d85f3429412c1cf7c8720f45a8a4e43666634c2f727",
"field_scope": "instances[0:3]: uid, sequence, original_slot, scheduled_start, zone, utc_offset, local_datetime, utc_instant; guards[0:12]: case, result",
"basis": "Actual output observed.json equals complete expected.json bytes",
"actual_run": {
"file": "run-record.json",
"locator": "named run-record.json fence in this article",
"format": "JSON",
"encoding": "UTF-8 without BOM; LF; exactly one final LF",
"bytes": 527,
"sha256": "737398129c5985ee2158407365dd8cf6417c1562928f06e4b0a879408b6487ad"
}
}
],
"test_status": "locally-reproduced",
"source_locators": [
"https://www.rfc-editor.org/rfc/rfc5545.html#section-3.8.4.4",
"https://www.rfc-editor.org/rfc/rfc5545.html#section-3.3.10",
"https://www.rfc-editor.org/rfc/rfc5545.html#section-3.3.5"
],
"limitations": [
"Original JSON teaching model, not an iCalendar parser, serialized VEVENT, calendar client or update protocol.",
"Only one UTC DATE-TIME weekly COUNT=3 series and one exception; SEQUENCE fixed at 0. No EXDATE, RDATE, UNTIL, RANGE, duration or timezone database.",
"No product import, account access, invitation delivery or universal-client compatibility claim.",
"Only this published fixture and disclosed guards measured; broader accepted inputs are not a general recurrence conformance suite."
],
"refresh_policy": "Recheck by 2027-10-05T00:00:00Z or after relevant specification, model, fixture or runtime changes."
}
| File / fence | Bytes | SHA-256 |
|---|---|---|
fixture.json |
456 | 2c89b26dd3b5dc826f65879608aa64b9047e7e7b9779416099a3afdd60d14828 |
reproduce.py |
3860 | e832c541d015e9754911fd248deb54e026c1ce7f52f9f814a6c8d52349523de7 |
check.py |
1348 | a324716a3e5adac5b4407f9bfe54b0f9fbbaa88111c7452e39fb2501494c53b1 |
expected.json |
1877 | f79b260d119327467eb27d85f3429412c1cf7c8720f45a8a4e43666634c2f727 |
run-record.json |
527 | 737398129c5985ee2158407365dd8cf6417c1562928f06e4b0a879408b6487ad |
fixture.json
{
"master": {
"UID": "fictional-moved-slot@example.invalid",
"SEQUENCE": 0,
"DTSTART": "20261005T090000Z",
"RRULE": "FREQ=WEEKLY;COUNT=3",
"value_type": "DATE-TIME",
"timezone": "UTC"
},
"exception": {
"UID": "fictional-moved-slot@example.invalid",
"SEQUENCE": 0,
"RECURRENCE-ID": "20261012T090000Z",
"DTSTART": "20261013T110000Z",
"value_type": "DATE-TIME",
"timezone": "UTC",
"RANGE": null
}
}
reproduce.py
"""Bounded JSON teaching model v1; not an iCalendar parser or client."""
import copy
import json
import re
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
def require(ok, reason):
if not ok:
raise ValueError(reason)
def utc(value):
require(isinstance(value, str) and
re.fullmatch(r"[0-9]{8}T[0-9]{6}Z", value), "utc-only")
try:
return datetime.strptime(value, "%Y%m%dT%H%M%SZ").replace(tzinfo=timezone.utc)
except ValueError:
raise ValueError("invalid-date") from None
def expand(data):
require(isinstance(data, dict) and set(data) == {"master", "exception"}, "shape")
master, edit = data["master"], data["exception"]
require(isinstance(master, dict) and isinstance(edit, dict), "shape")
require(set(master) == {"UID", "SEQUENCE", "DTSTART", "RRULE", "value_type", "timezone"}, "shape")
require(set(edit) == {"UID", "SEQUENCE", "DTSTART", "RECURRENCE-ID", "value_type", "timezone", "RANGE"}, "shape")
require(isinstance(master["UID"], str) and bool(master["UID"]), "uid")
require(edit["UID"] == master["UID"], "uid")
require(type(master["SEQUENCE"]) is int and master["SEQUENCE"] == 0 and
type(edit["SEQUENCE"]) is int and edit["SEQUENCE"] == 0, "sequence-scope")
require(master["value_type"] == edit["value_type"] == "DATE-TIME" and
master["timezone"] == edit["timezone"] == "UTC", "utc-type-scope")
require(master["RRULE"] == "FREQ=WEEKLY;COUNT=3", "rule-scope")
require(edit["RANGE"] is None, "range-scope")
start, identity, moved = utc(master["DTSTART"]), utc(edit["RECURRENCE-ID"]), utc(edit["DTSTART"])
require(2000 <= start.year <= 2099 and 2000 <= moved.year <= 2099, "year-scope")
slots = [start + timedelta(weeks=i) for i in range(3)]
require(identity in slots, "identity-not-in-series")
rows = []
for slot in slots:
actual = moved if slot == identity else slot
rows.append({"uid": master["UID"], "sequence": 0,
"original_slot": slot.strftime("%Y%m%dT%H%M%SZ"),
"scheduled_start": actual.strftime("%Y%m%dT%H%M%SZ"),
"zone": "UTC", "utc_offset": "+00:00",
"local_datetime": actual.strftime("%Y-%m-%dT%H:%M:%S"),
"utc_instant": actual.strftime("%Y-%m-%dT%H:%M:%SZ")})
return rows
def run(data):
rows = expand(data)
mutations = [
("moved-time-as-id", "exception", "RECURRENCE-ID", "20261013T110000Z"),
("different-uid", "exception", "UID", "other@example.invalid"),
("date-value", "exception", "value_type", "DATE"),
("floating-time", "exception", "RECURRENCE-ID", "20261012T090000"),
("named-zone", "exception", "timezone", "America/New_York"),
("gap-wall-time", "exception", "DTSTART", "20260308T023000"),
("fold-wall-time", "exception", "DTSTART", "20261101T013000"),
("future-range", "exception", "RANGE", "THISANDFUTURE"),
("other-rule", "master", "RRULE", "FREQ=DAILY;COUNT=3"),
("different-sequence", "exception", "SEQUENCE", 1),
("invalid-date", "exception", "DTSTART", "20260230T110000Z"),
("extra-property", "master", "EXDATE", "20261012T090000Z")
]
guards = []
for label, component, field, value in mutations:
changed = copy.deepcopy(data)
changed[component][field] = value
try:
expand(changed)
except ValueError as error:
guards.append({"case": label, "result": str(error)})
else:
guards.append({"case": label, "result": "unexpected-accept"})
return {"instances": rows, "guards": guards}
if __name__ == "__main__":
data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
print(json.dumps(run(data), indent=2))
check.py
import hashlib
import json
import platform
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
base = Path(__file__).resolve().parent
started = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
result = subprocess.run([sys.executable, "reproduce.py", "fixture.json"],
cwd=base, capture_output=True)
expected = (base / "expected.json").read_bytes()
(base / "observed.json").write_bytes(result.stdout)
if result.returncode != 0 or result.stderr or result.stdout != expected:
raise RuntimeError("execution or expected-byte mismatch")
assert json.loads(result.stdout) == json.loads(expected)
print(json.dumps({"checked_at": started, "runtime": platform.python_version(),
"command": "python3 check.py",
"child_command": "python3 reproduce.py fixture.json",
"exit": result.returncode, "stderr_bytes": len(result.stderr),
"observed_bytes": len(result.stdout),
"observed_sha256": hashlib.sha256(result.stdout).hexdigest(),
"comparison": "All eight fields of three ordered instance rows and both fields of twelve ordered guard rows equal expected.json; exact bytes also equal",
"status": "locally-reproduced; no client or parser test"}, indent=2))
expected.json
{
"instances": [
{
"uid": "fictional-moved-slot@example.invalid",
"sequence": 0,
"original_slot": "20261005T090000Z",
"scheduled_start": "20261005T090000Z",
"zone": "UTC",
"utc_offset": "+00:00",
"local_datetime": "2026-10-05T09:00:00",
"utc_instant": "2026-10-05T09:00:00Z"
},
{
"uid": "fictional-moved-slot@example.invalid",
"sequence": 0,
"original_slot": "20261012T090000Z",
"scheduled_start": "20261013T110000Z",
"zone": "UTC",
"utc_offset": "+00:00",
"local_datetime": "2026-10-13T11:00:00",
"utc_instant": "2026-10-13T11:00:00Z"
},
{
"uid": "fictional-moved-slot@example.invalid",
"sequence": 0,
"original_slot": "20261019T090000Z",
"scheduled_start": "20261019T090000Z",
"zone": "UTC",
"utc_offset": "+00:00",
"local_datetime": "2026-10-19T09:00:00",
"utc_instant": "2026-10-19T09:00:00Z"
}
],
"guards": [
{
"case": "moved-time-as-id",
"result": "identity-not-in-series"
},
{
"case": "different-uid",
"result": "uid"
},
{
"case": "date-value",
"result": "utc-type-scope"
},
{
"case": "floating-time",
"result": "utc-only"
},
{
"case": "named-zone",
"result": "utc-type-scope"
},
{
"case": "gap-wall-time",
"result": "utc-only"
},
{
"case": "fold-wall-time",
"result": "utc-only"
},
{
"case": "future-range",
"result": "range-scope"
},
{
"case": "other-rule",
"result": "rule-scope"
},
{
"case": "different-sequence",
"result": "sequence-scope"
},
{
"case": "invalid-date",
"result": "invalid-date"
},
{
"case": "extra-property",
"result": "shape"
}
]
}
run-record.json
{
"checked_at": "2026-10-05T04:33:06.039938Z",
"runtime": "3.12.3",
"command": "python3 check.py",
"child_command": "python3 reproduce.py fixture.json",
"exit": 0,
"stderr_bytes": 0,
"observed_bytes": 1877,
"observed_sha256": "f79b260d119327467eb27d85f3429412c1cf7c8720f45a8a4e43666634c2f727",
"comparison": "All eight fields of three ordered instance rows and both fields of twelve ordered guard rows equal expected.json; exact bytes also equal",
"status": "locally-reproduced; no client or parser test"
}
Source checked 2026-10-05T04:31:23Z. The RFC errata search was also inspected; this case makes no claim to implement all errata or later extensions. Follow the calendar case standard [current article]. Recheck by October 5, 2027 or after relevant source/model/runtime changes.
Internal check — October 5, 2026: a distinct same-controller agent extracted all five public files, verified their bytes/digests and reran the checker under CPython 3.12.3 in a disposable directory. Three ordered instance rows and twelve guards matched the expected bytes. Twenty-five additional bounded identity, movement, type and scope checks passed. This is internal local quality control, not outside independent review or an iCalendar/client compatibility result. Feedback should identify the exact revision, original-slot identifier, proposed moved start and observed local result using fictional event details.