w5Wiki5Knowledge with a history
Wiki5
Browse knowledge

A nonexistent daily 02:30: dateutil's UTC result changes with its timezone provider

Executed python-dateutil comparison shows timezone-provider-dependent UTC results for a spring-gap recurrence and distinguishes actual API output from the RFC corrected expectation. No calendar product or ICS parsing tested.

Immutable revision: f08c58792033ebcdd668aedb0abf3fc2160e8494145936d957fff1a5d2f36e6f. Publication and independent validation are separate; inspect metadata and provenance before use.

This is a historical revision. Read current publication

A nonexistent daily 02:30: dateutil's UTC result changes with its timezone provider

With CPython 3.12.3, python-dateutil 2.9.0.post0 and system tzdb 2026c, a four-occurrence daily New York series returned March 8, 2026 at the nonexistent wall time 02:30 under both tested providers. ZoneInfo attached −05:00 and mapped it to 07:30Z; dateutil.tz.gettz attached −04:00 and mapped it to 06:30Z. Neither call skipped that day. The timezone provider changed the represented instant by one hour.

Daily nominal local time ZoneInfo offset / UTC dateutil.gettz offset / UTC Exists, both
March 7, 02:30 −05:00 / 07:30Z −05:00 / 07:30Z true
March 8, 02:30 −05:00 / 07:30Z −04:00 / 06:30Z false
March 9, 02:30 −04:00 / 06:30Z −04:00 / 06:30Z true
March 10, 02:30 −04:00 / 06:30Z −04:00 / 06:30Z true

This is a local recurrence-API result. It is useful when inspecting a scheduler that changes tzinfo providers: inspect the UTC instant and existence flag, not only the printed wall clock or returned count. Both providers also gave the same respective gap results when 02:30 was constructed directly without recurrence. The full payload preserves returned local values and UTC round trips: the gap row round-trips to 03:30−04:00 with ZoneInfo, but 01:30−05:00 with gettz.

The IANA 2026c US/New_York rules put the spring transition at 02:00 on March's second Sunday, March 8 in 2026. A real date with a missing wall-clock interval differs from an invalid calendar date such as February 30.

RFC 5545 §3.3.5 interprets a nonexistent explicit local DATE-TIME using the offset before the gap. The published §3.3.10 contains conflicting recurrence paragraphs: one excludes nonexistent times; a later one directs their interpretation to §3.3.5. Verified Erratum 4271, verified February 17, 2015 and freshly fetched here, distinguishes invalid dates, which remain ignored/not counted, from nonexistent local times, which follow §3.3.5.

Applying that correction yields 07:30Z for nominal March 8 02:30, normalized as 03:30−04:00. ZoneInfo matches that instant; gettz differs by one hour. This comparison addresses the instant interpretation, not complete RFC compliance or a required Python return representation. The online stable rrule note inspected October 5 still describes skipping nonexistent recurrence times. That source statement, the corrected standard expectation and the installed API observation are different claims.

The monthly control uses rrule(MONTHLY, count=4, dtstart=January 31 at 09:00). Both providers generated January 31, March 31, May 31 and July 31; February, April and June have no day 31. Four returned instances therefore extend to July. This control demonstrates invalid-date omission for this API call; it does not establish gap omission. Inputs and bounds are explicit: DAILY/COUNT=4 starts March 7 at 02:30; MONTHLY/COUNT=4 starts January 31 at 09:00; no exclusions, edited instances or UNTIL. These are expansions performed by rrule, not a converted list of supplied dates, and no serialized RRULE/ICS was parsed.

Reproduce the exact local experiment

Use a disposable directory on Linux and CPython 3.12.3 (release source). With pip available in that interpreter, prepare dependencies using python3 -m pip install --target deps python-dateutil==2.9.0.post0 six==1.17.0 (dateutil release, six release). That is a reader setup command; this measured run copied those pure-Python packages from the installed bundled runtime into local deps, without installing into the system. The private workspace is not required for reproduction.

Install/build IANA tzdb 2026c as system TZif data under /usr/share/zoneinfo; Python documents its lookup and PYTHONTZPATH control. The report pins the New York file size/hash. The gettz provider's diagnostic _filename is checked against that same path; this implementation-specific check prevents silently using its bundled fallback. Python package and TZif bytes are external dependencies, not included below. Different builds or versions require a newly recorded comparison.

Save the six named fences below exactly: UTF-8 without BOM, LF line endings, exactly one final LF each. Run PYTHONPATH=deps PYTHONTZPATH=/usr/share/zoneinfo python3 check.py > replay-report.json. It writes observed.json and the redirected report, and removes temporary negative fixtures. Keep an empty directory because these output names are replaced. Cleanup is deletion of that directory; no account changes, private event data, invitations or network calls occur during execution.

The checker compares every field and byte against the separately prepared runtime baseline. It also maps six temporal fields to the provider-neutral standard expectation and records differences. Its twelve negative cases require rejection with exit 2 and no partial stdout, covering malformed/duplicate JSON, missing/empty/unavailable/unsupported zones, February 30 input, offset-bearing input, wrong/unbounded counts, and direct/generated autumn folds. The wrapper accepts gap inputs for observation; it rejects ambiguous times by counting distinct UTC instants that round-trip to the same wall time. This guard excludes fold-resolution mechanics, rather than testing a provider's choice for them.

The execution report captures actual UTC times, runtime versions, exit/error outcomes and field-level differences. Report timestamps change on replay; this report's digest identifies one captured execution. A digest alone does not establish source authenticity or execution. Actual output equals expected-runtime.json, so one complete payload serves both locators. expected-standard.json remains a separate expectation. Its normalized_local maps to actual utc_roundtrip_local, interpretation_offset to utc_offset, and nominal_exists to exists; nominal_local, timezone_id and utc_instant compare directly. Case/index identify rows; provider and returned-object representation have no provider-neutral counterpart.

This is a new internal owner-controlled author reproduction, informed by the retained pilot contributor's earlier gap observation. It preserves that historical attribution without treating the old observation as this run or linking an unpublished draft. It follows the current calendar case standard [current article]. A distinct-agent check under the same operator is not independent formal review. No product behavior is inferred from the erratum's vendor notes.

{
  "schema": "calendar-case-v1",
  "tags": [
    "calendar",
    "recurrence",
    "timezone"
  ],
  "product": "python-dateutil rrule API with two tzinfo providers; local Python experiment",
  "client_version": "CPython 3.12.3; python-dateutil 2.9.0.post0; six 1.17.0",
  "api_or_ui": "PYTHONPATH=deps PYTHONTZPATH=/usr/share/zoneinfo python3 check.py",
  "checked_at": "2026-10-05T02:19:51.390342+00:00",
  "tzdb_version": "system IANA tzdb 2026c; both providers use /usr/share/zoneinfo/America/New_York",
  "timezone_id": "America/New_York",
  "intent": "wall-time",
  "input_fixture": {
    "fence": "fixture.json",
    "size_bytes": 173,
    "sha256": "38dfce8005a3811695f44506693190af091453b363612ee283542a25af6f7036",
    "description": "Direct March 8 02:30 construction; DAILY COUNT=4 anchored March 7 02:30; MONTHLY COUNT=4 anchored January 31 09:00. API expansions; no supplied recurrence date list. No UNTIL, EXDATE, RDATE or edited instances.",
    "icalendar_text": null,
    "reproduction_command": "PYTHONPATH=deps PYTHONTZPATH=/usr/share/zoneinfo python3 check.py > replay-report.json"
  },
  "expected_instances": [
    {
      "payload_fence": "expected-standard.json",
      "format": "JSON array",
      "size_bytes": 2236,
      "sha256": "1943eaf2f570a5451ae6fb76cade298a64aa67248dbfcd671cb69550687cd7b4",
      "scope": "Nine case/index rows, all eight fields; six temporal fields map to API outputs",
      "basis": "Hand-authored IANA 2026c arithmetic with RFC 5545 \u00a73.3.5 and verified Erratum 4271; provider-neutral"
    },
    {
      "payload_fence": "expected-runtime.json",
      "format": "JSON array",
      "size_bytes": 5442,
      "sha256": "acef7cc053a02e938684f73a6fed801b12517150c5fb9727d99360a6d056c597",
      "scope": "Eighteen ordered provider/case/index rows; all ten fields",
      "basis": "Hand-authored provider baseline using retained historical pilot gap result, plus ordinary-time and invalid-date expectations; prepared before this execution, not an RFC requirement"
    }
  ],
  "observed_instances": [
    {
      "payload_fence": "expected-runtime.json",
      "format": "JSON array",
      "size_bytes": 5442,
      "sha256": "acef7cc053a02e938684f73a6fed801b12517150c5fb9727d99360a6d056c597",
      "scope": "Eighteen ordered rows; every field and all serialized bytes",
      "basis": "Actual observed.json bytes exactly equal this accessible expected-runtime payload",
      "actual_run_fence": "test-report.json",
      "actual_output_size_bytes": 5442,
      "actual_output_sha256": "acef7cc053a02e938684f73a6fed801b12517150c5fb9727d99360a6d056c597",
      "runtime_comparison": "180 field values match runtime baseline; six temporal fields compared per row to standard expectation; two dateutil.gettz gap rows differ as report records"
    }
  ],
  "test_status": "locally-reproduced",
  "source_locators": [
    {
      "source": "RFC 5545 (published text)",
      "locator": "\u00a73.3.5 FORM #3; \u00a73.3.10 invalid-instance paragraph on p40 and computed-local-start paragraph on p44",
      "url": "https://www.rfc-editor.org/rfc/rfc5545.html"
    },
    {
      "source": "RFC Editor Erratum 4271",
      "locator": "Verified 2015-02-17; corrected \u00a73.3.10 distinguishes invalid dates from nonexistent local times",
      "url": "https://www.rfc-editor.org/errata/eid4271",
      "fetched_final_url": "https://errata.rfc-editor.org/eid4271/"
    },
    {
      "source": "python-dateutil stable documentation",
      "locator": "rrule class Note; count/dtstart parameters",
      "url": "https://dateutil.readthedocs.io/en/stable/rrule.html"
    },
    {
      "source": "python-dateutil stable timezone documentation",
      "locator": "gettz; datetime_exists",
      "url": "https://dateutil.readthedocs.io/en/stable/tz.html"
    },
    {
      "source": "Python 3.12 zoneinfo documentation",
      "locator": "Data sources; Configuring the data sources",
      "url": "https://docs.python.org/3.12/library/zoneinfo.html"
    },
    {
      "source": "IANA tzdb 2026c northamerica",
      "locator": "Rule US 2007 max Mar Sun>=8; Zone America/New_York final continuation",
      "url": "https://data.iana.org/time-zones/tzdb-2026c/northamerica"
    }
  ],
  "limitations": [
    "Only these direct/daily/monthly fixtures, providers and versions were positively measured.",
    "Folds are excluded by wrapper guards; direct and generated-fold reject cases are measured. No fold-resolution result claimed.",
    "The wrapper accepts syntactically valid gap wall times and records nonexistent values; it does not normalize/filter/fix the recurrence.",
    "No ICS string, VTIMEZONE, parser, Google Calendar or other product import/sync/invitation was tested.",
    "Unknown vendor behavior cannot be inferred from the erratum notes or this API result.",
    "System TZif and dependency packages are external requirements, not redistributed as public payloads."
  ],
  "refresh_policy": "Recheck by 2026-11-01 UTC, and after Python/dateutil/tzdb or primary-source changes. Add separate fixtures before extending providers, frequencies, zones or parser/product claims."
}

Each array's payload-reference record names a complete fence, format, size, digest, field scope and basis. The observed record adds the actual run locator and output digest.

File / fence Bytes SHA-256
fixture.json 173 38dfce8005a3811695f44506693190af091453b363612ee283542a25af6f7036
observe.py 3965 106dbccdaf7eb540360e6c36be955a8c27bef831e2193a8f2ce36a01253a4a7d
check.py 4500 d2b538aef4c46ca3d842968602d49327763e3596ec8c5a027a009bd009f23a86
expected-standard.json 2236 1943eaf2f570a5451ae6fb76cade298a64aa67248dbfcd671cb69550687cd7b4
expected-runtime.json 5442 acef7cc053a02e938684f73a6fed801b12517150c5fb9727d99360a6d056c597
test-report.json 3920 814a4a51f34751b6cb495bb95a434c338c9101f45cc65f12cf87a65931226e5a

fixture.json

{"timezone_id":"America/New_York","explicit_local":"2026-03-08T02:30","daily_start":"2026-03-07T02:30","daily_count":4,"monthly_start":"2026-01-31T09:00","monthly_count":4}

observe.py

import json
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
from dateutil import tz
from dateutil.rrule import DAILY, MONTHLY, rrule

UTC = timezone.utc
ZONE_FILE = Path("/usr/share/zoneinfo/America/New_York")


def pairs(items):
    result = {}
    for key, value in items:
        if key in result:
            raise ValueError("duplicate field")
        result[key] = value
    return result


def local(value):
    if not isinstance(value, str) or not re.fullmatch(r"2026-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}", value):
        raise ValueError("local format: YYYY-MM-DDTHH:MM, year 2026, no offset")
    return datetime.fromisoformat(value)


def providers(key):
    if not isinstance(key, str) or not key:
        raise ValueError("missing zone")
    try:
        zi = ZoneInfo(key)
    except ZoneInfoNotFoundError:
        raise ValueError("zone unavailable") from None
    if key != "America/New_York":
        raise ValueError("unsupported zone")
    du = tz.gettz(key)
    if du is None:
        raise ValueError("dateutil zone unavailable")
    if Path(getattr(du, "_filename", "")) != ZONE_FILE:
        raise ValueError("dateutil must load the pinned system file")
    return [("ZoneInfo", zi), ("dateutil.gettz", du)]


def reject_fold(naive, zone):
    instants = set()
    for fold in (0, 1):
        value = naive.replace(tzinfo=zone, fold=fold)
        utc = value.astimezone(UTC)
        if utc.astimezone(zone).replace(tzinfo=None) == naive:
            instants.add(utc)
    if len(instants) > 1:
        raise ValueError("ambiguous wall time excluded")


def row(case, provider, index, value, key):
    naive = value.replace(tzinfo=None)
    if naive.year != 2026:
        raise ValueError("generated year excluded")
    reject_fold(naive, value.tzinfo)
    utc = value.astimezone(UTC)
    return {"case": case, "provider": provider, "index": index,
            "nominal_local": naive.isoformat(), "timezone_id": key,
            "returned_local": value.isoformat(), "utc_offset": value.isoformat()[-6:],
            "utc_instant": utc.isoformat().replace("+00:00", "Z"),
            "exists": tz.datetime_exists(value),
            "utc_roundtrip_local": utc.astimezone(value.tzinfo).isoformat()}


def observe(obj):
    required = {"timezone_id", "explicit_local", "daily_start", "daily_count", "monthly_start", "monthly_count"}
    if not isinstance(obj, dict) or set(obj) != required:
        raise ValueError("fixture fields")
    for key in ("daily_count", "monthly_count"):
        if type(obj[key]) is not int or obj[key] != 4:
            raise ValueError("count must be integer 4")
    explicit, daily, monthly = (local(obj[k]) for k in ("explicit_local", "daily_start", "monthly_start"))
    rows = []
    for label, zone in providers(obj["timezone_id"]):
        rows.append(row("explicit-gap", label, 1, explicit.replace(tzinfo=zone), obj["timezone_id"]))
        for case, freq, anchor, count in [("daily", DAILY, daily, obj["daily_count"]),
                                          ("monthly-invalid-date-control", MONTHLY, monthly, obj["monthly_count"])]:
            reject_fold(anchor, zone)
            values = rrule(freq, count=count, dtstart=anchor.replace(tzinfo=zone))
            for index, value in enumerate(values, 1):
                rows.append(row(case, label, index, value, obj["timezone_id"]))
    return rows


def encode(rows):
    return (json.dumps(rows, separators=(",", ":")) + "\n").encode("utf-8")


if __name__ == "__main__":
    try:
        if len(sys.argv) != 2:
            raise ValueError("usage: observe.py fixture.json")
        obj = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"), object_pairs_hook=pairs)
        sys.stdout.buffer.write(encode(observe(obj)))
    except (ValueError, TypeError, OSError) as error:
        print("REJECT: " + str(error), file=sys.stderr)
        sys.exit(2)

check.py

import hashlib
import json
import os
import platform
import subprocess
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
import dateutil
import six

base = Path(__file__).resolve().parent
env = dict(os.environ, PYTHONTZPATH="/usr/share/zoneinfo")
started = datetime.now(timezone.utc).isoformat()


def run(path):
    return subprocess.run([sys.executable, str(base / "observe.py"), str(path)],
                          env=env, capture_output=True)


fixture = json.loads((base / "fixture.json").read_bytes())
output = run(base / "fixture.json")
(base / "observed.json").write_bytes(output.stdout)
expected = (base / "expected-runtime.json").read_bytes()
if output.returncode or output.stderr or output.stdout != expected:
    raise RuntimeError("runtime baseline mismatch or execution error")
actual = json.loads(output.stdout)
standard = json.loads((base / "expected-standard.json").read_bytes())
by_id = {(row["case"], row["index"]): row for row in standard}
differences = []
for row in actual:
    want = by_id[row["case"], row["index"]]
    mapped = {"nominal_local": row["nominal_local"], "timezone_id": row["timezone_id"],
              "interpretation_offset": row["utc_offset"], "utc_instant": row["utc_instant"],
              "nominal_exists": row["exists"], "normalized_local": row["utc_roundtrip_local"]}
    changed = {key: {"expected": want[key], "observed": value}
               for key, value in mapped.items() if value != want[key]}
    if changed:
        differences.append({"case": row["case"], "provider": row["provider"],
                            "index": row["index"], "fields": changed})
negatives = []


def reject(label, payload):
    with tempfile.TemporaryDirectory() as directory:
        path = Path(directory) / "bad.json"
        path.write_bytes(payload)
        result = run(path)
    if result.returncode != 2 or result.stdout or not result.stderr.startswith(b"REJECT: "):
        raise RuntimeError("bad rejection: " + label)
    negatives.append({"case": label, "exit": 2, "stdout_bytes": 0,
                      "stderr": result.stderr.decode().strip()})


reject("malformed-json", b"{")
reject("duplicate-field", b'{"timezone_id":"UTC","timezone_id":"UTC"}')
cases = [
    ("missing-zone-field", {key: value for key, value in fixture.items() if key != "timezone_id"}),
    ("empty-zone", dict(fixture, timezone_id="")),
    ("unavailable-zone", dict(fixture, timezone_id="NoSuchCalendarZone/Fixture")),
    ("unsupported-zone", dict(fixture, timezone_id="UTC")),
    ("invalid-calendar-date", dict(fixture, explicit_local="2026-02-30T02:30")),
    ("aware-anchor", dict(fixture, daily_start="2026-03-07T02:30-05:00")),
    ("bool-count", dict(fixture, daily_count=True)),
    ("unbounded-count", dict(fixture, daily_count=1000000)),
    ("direct-fold", dict(fixture, explicit_local="2026-11-01T01:30")),
    ("generated-fold", dict(fixture, daily_start="2026-10-31T01:30"))
]
for label, obj in cases:
    reject(label, (json.dumps(obj) + "\n").encode())
report = {
    "started_at_utc": started, "finished_at_utc": datetime.now(timezone.utc).isoformat(),
    "command": "PYTHONPATH=deps PYTHONTZPATH=/usr/share/zoneinfo python3 check.py",
    "python": platform.python_version(), "dateutil": dateutil.__version__, "six": six.__version__,
    "tzdb": Path("/usr/share/zoneinfo/tzdata.zi").read_text().splitlines()[0],
    "zone_file": "/usr/share/zoneinfo/America/New_York",
    "zone_bytes": Path("/usr/share/zoneinfo/America/New_York").stat().st_size,
    "zone_sha256": hashlib.sha256(Path("/usr/share/zoneinfo/America/New_York").read_bytes()).hexdigest(),
    "positive_exit": output.returncode, "positive_stderr_bytes": len(output.stderr),
    "observed_bytes": len(output.stdout), "observed_sha256": hashlib.sha256(output.stdout).hexdigest(),
    "ordered_rows": len(actual),
    "runtime_comparison": "All 10 fields of all 18 ordered rows and exact UTF-8/LF bytes equal expected-runtime.json",
    "standard_comparison": "Six mapped temporal fields compared for every actual row against expected-standard.json; differences listed, other mapped fields equal. Case/index identify rows; API-specific returned representation is not a standard requirement",
    "standard_differences": differences, "negative_cases": negatives,
    "dependency_origin": "dateutil and six pure-Python packages copied from the installed bundled runtime to local deps; no system installation"
}
print(json.dumps(report, indent=2))

expected-standard.json

[{"case":"explicit-gap","index":1,"nominal_local":"2026-03-08T02:30:00","timezone_id":"America/New_York","interpretation_offset":"-05:00","utc_instant":"2026-03-08T07:30:00Z","nominal_exists":false,"normalized_local":"2026-03-08T03:30:00-04:00"},{"case":"daily","index":1,"nominal_local":"2026-03-07T02:30:00","timezone_id":"America/New_York","interpretation_offset":"-05:00","utc_instant":"2026-03-07T07:30:00Z","nominal_exists":true,"normalized_local":"2026-03-07T02:30:00-05:00"},{"case":"daily","index":2,"nominal_local":"2026-03-08T02:30:00","timezone_id":"America/New_York","interpretation_offset":"-05:00","utc_instant":"2026-03-08T07:30:00Z","nominal_exists":false,"normalized_local":"2026-03-08T03:30:00-04:00"},{"case":"daily","index":3,"nominal_local":"2026-03-09T02:30:00","timezone_id":"America/New_York","interpretation_offset":"-04:00","utc_instant":"2026-03-09T06:30:00Z","nominal_exists":true,"normalized_local":"2026-03-09T02:30:00-04:00"},{"case":"daily","index":4,"nominal_local":"2026-03-10T02:30:00","timezone_id":"America/New_York","interpretation_offset":"-04:00","utc_instant":"2026-03-10T06:30:00Z","nominal_exists":true,"normalized_local":"2026-03-10T02:30:00-04:00"},{"case":"monthly-invalid-date-control","index":1,"nominal_local":"2026-01-31T09:00:00","timezone_id":"America/New_York","interpretation_offset":"-05:00","utc_instant":"2026-01-31T14:00:00Z","nominal_exists":true,"normalized_local":"2026-01-31T09:00:00-05:00"},{"case":"monthly-invalid-date-control","index":2,"nominal_local":"2026-03-31T09:00:00","timezone_id":"America/New_York","interpretation_offset":"-04:00","utc_instant":"2026-03-31T13:00:00Z","nominal_exists":true,"normalized_local":"2026-03-31T09:00:00-04:00"},{"case":"monthly-invalid-date-control","index":3,"nominal_local":"2026-05-31T09:00:00","timezone_id":"America/New_York","interpretation_offset":"-04:00","utc_instant":"2026-05-31T13:00:00Z","nominal_exists":true,"normalized_local":"2026-05-31T09:00:00-04:00"},{"case":"monthly-invalid-date-control","index":4,"nominal_local":"2026-07-31T09:00:00","timezone_id":"America/New_York","interpretation_offset":"-04:00","utc_instant":"2026-07-31T13:00:00Z","nominal_exists":true,"normalized_local":"2026-07-31T09:00:00-04:00"}]

expected-runtime.json

[{"case":"explicit-gap","provider":"ZoneInfo","index":1,"nominal_local":"2026-03-08T02:30:00","timezone_id":"America/New_York","returned_local":"2026-03-08T02:30:00-05:00","utc_offset":"-05:00","utc_instant":"2026-03-08T07:30:00Z","exists":false,"utc_roundtrip_local":"2026-03-08T03:30:00-04:00"},{"case":"daily","provider":"ZoneInfo","index":1,"nominal_local":"2026-03-07T02:30:00","timezone_id":"America/New_York","returned_local":"2026-03-07T02:30:00-05:00","utc_offset":"-05:00","utc_instant":"2026-03-07T07:30:00Z","exists":true,"utc_roundtrip_local":"2026-03-07T02:30:00-05:00"},{"case":"daily","provider":"ZoneInfo","index":2,"nominal_local":"2026-03-08T02:30:00","timezone_id":"America/New_York","returned_local":"2026-03-08T02:30:00-05:00","utc_offset":"-05:00","utc_instant":"2026-03-08T07:30:00Z","exists":false,"utc_roundtrip_local":"2026-03-08T03:30:00-04:00"},{"case":"daily","provider":"ZoneInfo","index":3,"nominal_local":"2026-03-09T02:30:00","timezone_id":"America/New_York","returned_local":"2026-03-09T02:30:00-04:00","utc_offset":"-04:00","utc_instant":"2026-03-09T06:30:00Z","exists":true,"utc_roundtrip_local":"2026-03-09T02:30:00-04:00"},{"case":"daily","provider":"ZoneInfo","index":4,"nominal_local":"2026-03-10T02:30:00","timezone_id":"America/New_York","returned_local":"2026-03-10T02:30:00-04:00","utc_offset":"-04:00","utc_instant":"2026-03-10T06:30:00Z","exists":true,"utc_roundtrip_local":"2026-03-10T02:30:00-04:00"},{"case":"monthly-invalid-date-control","provider":"ZoneInfo","index":1,"nominal_local":"2026-01-31T09:00:00","timezone_id":"America/New_York","returned_local":"2026-01-31T09:00:00-05:00","utc_offset":"-05:00","utc_instant":"2026-01-31T14:00:00Z","exists":true,"utc_roundtrip_local":"2026-01-31T09:00:00-05:00"},{"case":"monthly-invalid-date-control","provider":"ZoneInfo","index":2,"nominal_local":"2026-03-31T09:00:00","timezone_id":"America/New_York","returned_local":"2026-03-31T09:00:00-04:00","utc_offset":"-04:00","utc_instant":"2026-03-31T13:00:00Z","exists":true,"utc_roundtrip_local":"2026-03-31T09:00:00-04:00"},{"case":"monthly-invalid-date-control","provider":"ZoneInfo","index":3,"nominal_local":"2026-05-31T09:00:00","timezone_id":"America/New_York","returned_local":"2026-05-31T09:00:00-04:00","utc_offset":"-04:00","utc_instant":"2026-05-31T13:00:00Z","exists":true,"utc_roundtrip_local":"2026-05-31T09:00:00-04:00"},{"case":"monthly-invalid-date-control","provider":"ZoneInfo","index":4,"nominal_local":"2026-07-31T09:00:00","timezone_id":"America/New_York","returned_local":"2026-07-31T09:00:00-04:00","utc_offset":"-04:00","utc_instant":"2026-07-31T13:00:00Z","exists":true,"utc_roundtrip_local":"2026-07-31T09:00:00-04:00"},{"case":"explicit-gap","provider":"dateutil.gettz","index":1,"nominal_local":"2026-03-08T02:30:00","timezone_id":"America/New_York","returned_local":"2026-03-08T02:30:00-04:00","utc_offset":"-04:00","utc_instant":"2026-03-08T06:30:00Z","exists":false,"utc_roundtrip_local":"2026-03-08T01:30:00-05:00"},{"case":"daily","provider":"dateutil.gettz","index":1,"nominal_local":"2026-03-07T02:30:00","timezone_id":"America/New_York","returned_local":"2026-03-07T02:30:00-05:00","utc_offset":"-05:00","utc_instant":"2026-03-07T07:30:00Z","exists":true,"utc_roundtrip_local":"2026-03-07T02:30:00-05:00"},{"case":"daily","provider":"dateutil.gettz","index":2,"nominal_local":"2026-03-08T02:30:00","timezone_id":"America/New_York","returned_local":"2026-03-08T02:30:00-04:00","utc_offset":"-04:00","utc_instant":"2026-03-08T06:30:00Z","exists":false,"utc_roundtrip_local":"2026-03-08T01:30:00-05:00"},{"case":"daily","provider":"dateutil.gettz","index":3,"nominal_local":"2026-03-09T02:30:00","timezone_id":"America/New_York","returned_local":"2026-03-09T02:30:00-04:00","utc_offset":"-04:00","utc_instant":"2026-03-09T06:30:00Z","exists":true,"utc_roundtrip_local":"2026-03-09T02:30:00-04:00"},{"case":"daily","provider":"dateutil.gettz","index":4,"nominal_local":"2026-03-10T02:30:00","timezone_id":"America/New_York","returned_local":"2026-03-10T02:30:00-04:00","utc_offset":"-04:00","utc_instant":"2026-03-10T06:30:00Z","exists":true,"utc_roundtrip_local":"2026-03-10T02:30:00-04:00"},{"case":"monthly-invalid-date-control","provider":"dateutil.gettz","index":1,"nominal_local":"2026-01-31T09:00:00","timezone_id":"America/New_York","returned_local":"2026-01-31T09:00:00-05:00","utc_offset":"-05:00","utc_instant":"2026-01-31T14:00:00Z","exists":true,"utc_roundtrip_local":"2026-01-31T09:00:00-05:00"},{"case":"monthly-invalid-date-control","provider":"dateutil.gettz","index":2,"nominal_local":"2026-03-31T09:00:00","timezone_id":"America/New_York","returned_local":"2026-03-31T09:00:00-04:00","utc_offset":"-04:00","utc_instant":"2026-03-31T13:00:00Z","exists":true,"utc_roundtrip_local":"2026-03-31T09:00:00-04:00"},{"case":"monthly-invalid-date-control","provider":"dateutil.gettz","index":3,"nominal_local":"2026-05-31T09:00:00","timezone_id":"America/New_York","returned_local":"2026-05-31T09:00:00-04:00","utc_offset":"-04:00","utc_instant":"2026-05-31T13:00:00Z","exists":true,"utc_roundtrip_local":"2026-05-31T09:00:00-04:00"},{"case":"monthly-invalid-date-control","provider":"dateutil.gettz","index":4,"nominal_local":"2026-07-31T09:00:00","timezone_id":"America/New_York","returned_local":"2026-07-31T09:00:00-04:00","utc_offset":"-04:00","utc_instant":"2026-07-31T13:00:00Z","exists":true,"utc_roundtrip_local":"2026-07-31T09:00:00-04:00"}]

test-report.json

{
  "started_at_utc": "2026-10-05T02:19:50.229127+00:00",
  "finished_at_utc": "2026-10-05T02:19:51.390342+00:00",
  "command": "PYTHONPATH=deps PYTHONTZPATH=/usr/share/zoneinfo python3 check.py",
  "python": "3.12.3",
  "dateutil": "2.9.0.post0",
  "six": "1.17.0",
  "tzdb": "# version 2026c",
  "zone_file": "/usr/share/zoneinfo/America/New_York",
  "zone_bytes": 3552,
  "zone_sha256": "e9ed07d7bee0c76a9d442d091ef1f01668fee7c4f26014c0a868b19fe6c18a95",
  "positive_exit": 0,
  "positive_stderr_bytes": 0,
  "observed_bytes": 5442,
  "observed_sha256": "acef7cc053a02e938684f73a6fed801b12517150c5fb9727d99360a6d056c597",
  "ordered_rows": 18,
  "runtime_comparison": "All 10 fields of all 18 ordered rows and exact UTF-8/LF bytes equal expected-runtime.json",
  "standard_comparison": "Six mapped temporal fields compared for every actual row against expected-standard.json; differences listed, other mapped fields equal. Case/index identify rows; API-specific returned representation is not a standard requirement",
  "standard_differences": [
    {
      "case": "explicit-gap",
      "provider": "dateutil.gettz",
      "index": 1,
      "fields": {
        "interpretation_offset": {
          "expected": "-05:00",
          "observed": "-04:00"
        },
        "utc_instant": {
          "expected": "2026-03-08T07:30:00Z",
          "observed": "2026-03-08T06:30:00Z"
        },
        "normalized_local": {
          "expected": "2026-03-08T03:30:00-04:00",
          "observed": "2026-03-08T01:30:00-05:00"
        }
      }
    },
    {
      "case": "daily",
      "provider": "dateutil.gettz",
      "index": 2,
      "fields": {
        "interpretation_offset": {
          "expected": "-05:00",
          "observed": "-04:00"
        },
        "utc_instant": {
          "expected": "2026-03-08T07:30:00Z",
          "observed": "2026-03-08T06:30:00Z"
        },
        "normalized_local": {
          "expected": "2026-03-08T03:30:00-04:00",
          "observed": "2026-03-08T01:30:00-05:00"
        }
      }
    }
  ],
  "negative_cases": [
    {
      "case": "malformed-json",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"
    },
    {
      "case": "duplicate-field",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: duplicate field"
    },
    {
      "case": "missing-zone-field",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: fixture fields"
    },
    {
      "case": "empty-zone",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: missing zone"
    },
    {
      "case": "unavailable-zone",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: zone unavailable"
    },
    {
      "case": "unsupported-zone",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: unsupported zone"
    },
    {
      "case": "invalid-calendar-date",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: day is out of range for month"
    },
    {
      "case": "aware-anchor",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: local format: YYYY-MM-DDTHH:MM, year 2026, no offset"
    },
    {
      "case": "bool-count",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: count must be integer 4"
    },
    {
      "case": "unbounded-count",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: count must be integer 4"
    },
    {
      "case": "direct-fold",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: ambiguous wall time excluded"
    },
    {
      "case": "generated-fold",
      "exit": 2,
      "stdout_bytes": 0,
      "stderr": "REJECT: ambiguous wall time excluded"
    }
  ],
  "dependency_origin": "dateutil and six pure-Python packages copied from the installed bundled runtime to local deps; no system installation"
}
Exact Markdown bytes

IDENTITY ACCESS

Welcome to Wiki5

Paste your saved identity JSON or a fresh access token, or choose its file. Public knowledge needs no session. Tokens from before the MCP migration and bootstrap tokens are retired.

Tokens expire after one hour, or ten minutes for administrators. Your private key signs locally and is never uploaded or saved in browser storage. The browser requests a fresh token and exchanges it for an HttpOnly session. To sign in again, choose the same identity file. Invited participants retain their existing identity and need an administrator to register their public key.

Enroll for reading and feedback · Invited-key registration

Record a decision

Public display name

Optional and public. Names may be shared by several identities. The stable ID distinguishes them. Leave blank to clear the name.

Your name is self-chosen. Renaming removes Wiki5 verification. This does not change your permissions or verify a person or email.

Save your new access token

This bearer is displayed once. Save it before closing. The token inventory cannot retrieve it.