"""Generated clock-label/elapsed-time cases. No private calendars, files or network calls."""
import datetime as dt
import hashlib
import json
from pathlib import Path
from zoneinfo import ZoneInfo, TZPATH
UTC=dt.timezone.utc

def regional(text, name, fold=None):
    naive=dt.datetime.fromisoformat(text)
    if naive.tzinfo is not None or naive.second or naive.microsecond:raise ValueError('local minute input required')
    zone=ZoneInfo(name);candidates={}
    for f in (0,1):
        aware=naive.replace(tzinfo=zone,fold=f);back=aware.astimezone(UTC).astimezone(zone)
        if back.replace(tzinfo=None)==naive and back.fold==f:candidates[f]=aware
    if not candidates:raise ValueError('nonexistent local time')
    if len(candidates)>1 and fold is None:raise ValueError('ambiguous: choose fold')
    if fold is not None and fold not in candidates:raise ValueError('invalid fold')
    return candidates[fold] if fold is not None else next(iter(candidates.values()))

def measure(start,end):
    if start.utcoffset() is None or end.utcoffset() is None:raise ValueError('offset required')
    wall=int((end.replace(tzinfo=None)-start.replace(tzinfo=None)).total_seconds()/60)
    elapsed=int((end.astimezone(UTC)-start.astimezone(UTC)).total_seconds()/60)
    if elapsed<0 or elapsed>31*1440:raise ValueError('range')
    change=int((end.utcoffset()-start.utcoffset()).total_seconds()/60)
    assert elapsed==wall-change
    return {'start':start.isoformat(timespec='minutes'),'end':end.isoformat(timespec='minutes'),'wall_minutes':wall,'offset_change_minutes':change,'elapsed_minutes':elapsed,'direct_python_subtraction_minutes':int((end-start).total_seconds()/60),'start_utc':start.astimezone(UTC).isoformat(timespec='minutes'),'end_utc':end.astimezone(UTC).isoformat(timespec='minutes')}

def run():
    cases=[
        ('spring',regional('2026-03-08T01:30','America/New_York'),regional('2026-03-08T03:30','America/New_York'),120,60),
        ('fall-span',regional('2026-11-01T00:30','America/New_York'),regional('2026-11-01T02:30','America/New_York'),120,180),
        ('fall-repeat',regional('2026-11-01T01:30','America/New_York',0),regional('2026-11-01T01:30','America/New_York',1),0,60),
        ('midnight',regional('2026-10-24T23:30','Asia/Seoul'),regional('2026-10-25T00:30','Asia/Seoul'),60,60),
        ('same-instant',regional('2026-10-25T09:00','Asia/Seoul'),regional('2026-10-25T00:00','UTC'),-540,0),
        ('ordinary',regional('2026-10-25T09:00','Asia/Seoul'),regional('2026-10-25T10:00','Asia/Seoul'),60,60),
        ('quarter-offset',regional('2026-10-25T09:00','Asia/Kathmandu'),regional('2026-10-25T03:30','UTC'),-330,15),
    ]
    results=[]
    for identity,start,end,wall,elapsed in cases:
        row=measure(start,end);row['id']=identity
        assert(row['wall_minutes'],row['elapsed_minutes'])==(wall,elapsed)
        # Independent fixed-offset UTC strings avoid shared ZoneInfo subtraction semantics.
        a=dt.datetime.fromisoformat(row['start']);b=dt.datetime.fromisoformat(row['end'])
        assert int((b.astimezone(UTC)-a.astimezone(UTC)).total_seconds()/60)==elapsed
        results.append(row)
    invalid=[('nonexistent-spring',lambda:regional('2026-03-08T02:30','America/New_York')),('ambiguous-without-fold',lambda:regional('2026-11-01T01:30','America/New_York')),('bad-date',lambda:regional('2026-02-30T09:00','Asia/Seoul')),('bad-fold',lambda:regional('2026-11-01T01:30','America/New_York',3)),('missing-offset',lambda:measure(dt.datetime(2026,1,1),dt.datetime(2026,1,2))),('reversed-utc',lambda:measure(cases[0][2],cases[0][1]))]
    rejected=[]
    for name,check in invalid:
        try:check()
        except ValueError as error:rejected.append({'id':name,'result':'expected_rejection','reason':str(error)})
        else:raise AssertionError(name)
    zones=[]
    for name in ['America/New_York','Asia/Seoul','Asia/Kathmandu','UTC']:
        for directory in TZPATH:
            path=Path(directory)/name
            if path.is_file():zones.append({'zone':name,'sha256':hashlib.sha256(path.read_bytes()).hexdigest()});break
    return {'scope':'Seven generated minute-precision cases, four named regions/UTC using this machine\'s bundled zone files. Not a real meeting or current global timezone-rules certification. No installation or calendar access.','zone_files':zones,'cases':results,'invalid_inputs':rejected,'python_semantics_note':'Direct subtraction with the same tzinfo intentionally follows clock labels; convert to UTC for elapsed-time comparison. This observed difference is documented Python behavior, not a program failure.'}

if __name__=='__main__':print(json.dumps(run(),ensure_ascii=False,indent=2))
