Files
TerminalBasic/openspec/changes/phase-3-ortszeit/design.md
Chili Palmer 52ccbb5848 Phase 3 (Kern): Laufzeitbibliothek, Bildschirm und Datei-E/A
Setzt den OpenSpec-Change phase-3-runtime-bildschirm um (77/77 Aufgaben).
Abdeckung laut Inventar: 195 implementiert, 37 offen, 53 Non-Feature.

Vollstaendigkeits-Inventar
- docs/inventar.md mit 285 Eintraegen aus den Themenlisten von
  bas7advr.hlp und qb45advr.hlp, je mit Status und Fundstelle
- crates/tb-frontend/tests/inventar.rs haelt die Tabelle in beide
  Richtungen gegen den Code; der Abdeckungsstand kann nicht veralten

Bruchschritt (Puffer, Host, Korpus)
- Cell/TextScreen ziehen ratatui-frei nach tb-runtime::screen; tb-ui
  behaelt Farbabbildung, Widget und den neuen Terminal-Host
- Host wechselt vom Zeichenstrom auf Anzeige des Zellenpuffers plus
  Ereignisse (Taste, Groessenaenderung, Abbruch)
- Korpusvergleich auf getrimmten Bildschirm-Snapshot; die acht
  bestehenden Sollausgaben blieben dabei unveraendert

Groessenunabhaengigkeit
- 80x25 ist nirgends mehr eine feste Grenze; Groessenaenderungen waehrend
  der Ausfuehrung werden zugestellt (Inhalt oben links erhalten, Cursor
  und VIEW PRINT geklemmt)
- Korpusnachweis: dasselbe Programm bei 80x25 und 120x40 mit je eigener
  Sollausgabe, plus ein Programm mit Groessenwechsel mitten im Lauf

Bibliothek
- Breite Unicode-Zeichen belegen zwei Zellen (Cursor, POS, Randumbruch)
- Bildschirm: CLS, COLOR, LOCATE, WIDTH, VIEW PRINT, SCREEN, CSRLIN, POS
- Tastatur: INKEY$, INPUT$, Funktionstasten-Makros (KEY n / LIST / ON)
- PRINT USING, LPRINT USING, FORMAT$, SetFormatCC
- Mathematik mit kompatiblem PRNG (gleiche Saat, gleiche Folge)
- Datum und Zeit mit Serienwerten ab 1899
- Finanzmathematik: FV#, PV#, Pmt#, IPmt#, PPmt#, NPer#, Rate#, NPV#,
  IRR#, MIRR#, SLN#, SYD#, DDB#
- Datei-E/A: sequenziell, RANDOM (Recordpuffer und UDT-Variablen),
  BINARY, FIELD/LSET/RSET, Statusfunktionen, Dateisystem, MK$/CV
- System: ENVIRON, FRE, CLEAR, TRON/TROFF, STACK, ERDEV, ERR-Anweisung

Altlasten aus Phase 2
- ON ERROR GOTO auf Modulebene ist aus Prozeduren ansprechbar
  (prozeduruebergreifender Fixup im Codegenerator)
- DATA behaelt seinen Rohtext (Gross-/Kleinschreibung, innerer Leerraum)
- Die vier TODO-verify-Zellen der Konvertierungsmatrix sind aufgeloest

Vom Inventar aufgedeckte Fehler
- Zwoelf Non-Features wies der Compiler entgegen der Phase-1-Spec nicht
  ab (CALLS, SSEG, POINT, VIEW, COM, PEN, STRIG, STICK, die
  String*-Routinen, LINE und OPEN "COMn:") -- geschlossen
- Drei Gruppen fehlten im urspruenglichen Umfang: Finanzmathematik,
  Record-Konvertierung, Rest der Dateisystemfunktionen
- bas7advr.hlp allein ist keine vollstaendige Quelle; das Inventar
  bildet die Vereinigung mit qb45advr.hlp

Neue Changes
- phase-3-isam: schliesst Phase 3 ab (Speicherschicht redb)
- phase-3-ortszeit: zieht die UTC-Abweichung zurueck

Neue Abhaengigkeit: unicode-width.
Dokumentiert: sprachreferenz.md und tbvm-design.md sind TODO-frei,
docs/bibliothek.md neu, PLAN.md fortgeschrieben.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 06:54:19 +02:00

7.4 KiB

Context

Siehe proposal.md — Why. Der Ist-Zustand ist eng umrissen:

  • tb-runtime::datetime rechnet vollständig selbst: Kalenderumrechnung (Howard Hinnant), serielle Datumswerte ab dem 30.12.1899, WEEKDAY, Textformen. Diese Rechnung ist getestet und bleibt.
  • Der einzige Punkt, an dem die Zeitzone eine Rolle spielt, ist jetzt_sekunden() — Sekunden seit der Unix-Epoche. Alles Weitere (jetzt_serial, DATE$, TIME$, TIMER, NOW) baut darauf auf.
  • tb-runtime hat bislang eine einzige Abhängigkeit mit fachlichem Gewicht: unicode-width.

Damit ist die Aufgabe klein: eine Zahl — der UTC-Versatz zu einem Zeitpunkt — kommt künftig von außen.

Goals / Non-Goals

Goals:

  • Zeitfunktionen liefern das, was die Uhr des Rechners zeigt.
  • Sommerzeit wird zum jeweiligen Zeitpunkt berücksichtigt.
  • Die Berührungsfläche zur Bibliothek bleibt eine Funktion, damit sie austauschbar ist und die getestete Kalenderrechnung unberührt bleibt.
  • Zeitverhalten bleibt automatisiert prüfbar, ohne von der Zone des Testrechners abzuhängen.

Non-Goals:

  • Zeitzonenbehaftete Datentypen im Dialekt — das Vorbild kennt nur Wanduhrzeit.
  • Eine Sprachanbindung an die Bibliothek (Zonennamen, Umrechnung zwischen Zonen). Das ist der Stufe-2-Eintrag im PLAN, nicht dieser Change.
  • Historische Zonendaten jenseits dessen, was die Systemdatenbank hergibt.

Decisions

D1: jiff als Zeitzonenbibliothek

Gewählt: jiff — sie liest die Zeitzonendatenbank des Systems und liefert den Versatz zu einem Zeitpunkt ohne Einschränkung.

Alternativen:

Bibliothek Bewertung
jiff Für genau diese Frage gebaut; Systemzonendatenbank; keine Sicherheitsauflage. Gewählt.
chrono (≥ 0.4.20) Gleichwertig sicher, seit 0.4.20 ohne localtime_r (Zonenermittlung über iana-time-zone, Datenbank über einen tz-rs-Zweig). Etablierter, bringt aber Serde-/Ökosystem-Anbindung mit, die hier niemand braucht. Tragfähige zweite Wahl.
time Der lokale Versatz ist auf Unix nur nach ausdrücklichem Opt-in in möglicherweise undefiniertes Verhalten verfügbar (set_soundness). Verworfen — das ist kein Preis für eine Uhrzeit.
Eigenbau (iana-time-zone + TZif-Parser) Hieße, die Zonendatenbank selbst zu lesen und dauerhaft nachzupflegen. Genau das, wofür man eine Bibliothek nimmt. Verworfen.

Warum die Wahl leicht revidierbar bleibt: Die Bibliothek wird an genau einer Stelle aufgerufen (D2). Ein Wechsel zu chrono wäre ein Austausch dieser einen Funktion.

D2: Berührungsfläche ist eine Funktion

tb-runtime::datetime

    utc_versatz(zeitpunkt: i64) -> Option<i32>     // Sekunden, None = Zone unbekannt
        └── einziger Aufruf der Bibliothek

    jetzt_sekunden()  ─┐
    jetzt_serial()     ├── rechnen weiter selbst
    civil_from_days()  │
    weekday() …       ─┘

Die Kalenderrechnung sieht die Bibliothek nie. Sie bekommt nach wie vor Sekunden seit der Epoche — nur eben um den Versatz verschobene.

Warum nicht die Bibliothek durchreichen: Ihre Datums-/Zeittypen wären ein zweites Kalendermodell neben dem des Dialekts (der bei 1899 zählt, WEEKDAY = 1 für Sonntag, CURRENCY-nahe Serienwerte). Zwei Modelle nebeneinander wären eine dauerhafte Fehlerquelle; die vorhandene Rechnung ist getestet und bleibt maßgeblich.

D3: Der Versatz gilt je Zeitpunkt, nicht je Programmlauf

utc_versatz bekommt den Zeitpunkt als Argument. Ein Programm, das über eine Zeitumstellung hinweg läuft, folgt ihr; ein Programm, das mit DATE$ = in einen anderen Teil des Jahres springt, bekommt den dort gültigen Versatz.

Preis: Eine Zonenabfrage je Zeitabfrage. Bei den Aufrufmengen eines Terminalprogramms belanglos; die Bibliothek hält die Zonendaten selbst vor.

D4: Zonenquelle ist umschaltbar — damit Tests nicht von der Maschine abhängen

pub enum Zeitzone {
    /// Zone des Rechners (Vorgabe).
    System,
    /// Fester Versatz in Sekunden — für Tests und den Korpus.
    Fest(i32),
    /// Zone nicht ermittelbar; es gilt UTC.
    Unbekannt,
}

RtState hält eine Zeitzone. Die Vorgabe ist System; schlägt die Ermittlung fehl, wird daraus Unbekannt. Tests und Korpusprogramme setzen Fest(n).

Warum nicht TZ in den Tests setzen: Umgebungsvariablen sind prozessweit, Tests laufen nebenläufig, und set_var ist seit Rust 2024 unsafe. Ein Zustand im RtState ist der offensichtliche Weg — er ist genau so weit sichtbar wie nötig.

Korpus: Das Harness bekommt eine Kopfzeilen-Direktive analog zu ' tb-screen: und ' tb-tempdir:

' tb-tz: +02:00

Damit bleibt die Sollausgabe eines Zeitprogramms unabhängig davon, wo der Testrechner steht.

D5: Rückfallebene ist sichtbar, nicht still

Ohne ermittelbare Zone gilt UTC — ein Abbruch wäre unverhältnismäßig, ein stiller Wechsel aber ein Verstoß gegen den Guiding Principle. Die Laufzeit führt den Zustand mit, und der Zustand ist abfragbar; die Sprachreferenz beschreibt ihn.

Wie ein Programm es erfährt: Über die vorhandene Systemabfrage ENVIRON$ wäre es geraten. Stattdessen bekommt die Laufzeit einen benannten Zustand, den der Runner beim Start auf die Fehlerausgabe schreibt, wenn er Unbekannt ist — einmal, nicht je Abfrage. Ein Programm, das es programmatisch braucht, bekommt es in Stufe 2 über die Bibliotheksanbindung.

D6: Reihenfolge der Umsetzung

  1. Abhaengigkeit + utc_versatz()          -> isoliert testbar
  2. jetzt_sekunden/jetzt_serial umstellen  -> DATE$/TIME$/TIMER/NOW folgen
  3. Zeitzone im RtState + Korpusdirektive  -> Tests werden maschinenunabhaengig
  4. Doku: Abweichung zurueckziehen, PLAN Stufe 2

Risks / Trade-offs

  • Erste schwere Abhängigkeit der Laufzeit → bewusst entschieden (2026-09-04). Die Berührungsfläche ist eine Funktion (D2); ein Wechsel der Bibliothek bleibt ein lokaler Eingriff.
  • Zeitabhängige Tests werden flatterig → deshalb D4: kein Test und kein Korpusprogramm fragt die echte Zone ab. Was die Systemzone betrifft, wird über Fest(n) und Unbekannt geprüft.
  • Zeitumstellung erzeugt mehrdeutige Wanduhrzeiten (Rückstellung) und nicht existierende (Vorstellung) → betrifft nur die Richtung Wanduhr → Zeitpunkt. Die Zeitfunktionen dieses Dialekts gehen die andere Richtung (Zeitpunkt → Wanduhr) und sind davon nicht betroffen. DATEVALUE/TIMEVALUE lesen Wanduhrzeit und rechnen gar nicht um. DATE$ =/TIME$ = setzen einen Versatz gegen die aktuelle Wanduhrzeit — auch das ist eindeutig.
  • Die Systemzonendatenbank kann fehlen (schlanke Container) → D5.
  • Bauzeit und Binärgröße wachsen → für ein Werkzeug, dessen Programme Uhrzeiten anzeigen, ist eine falsche Uhrzeit der größere Preis.

Migration Plan

Rein verhaltensändernd, ohne Formatbruch: .tbc-Dateien, Korpus-Golden- Files und Schnittstellen bleiben unberührt. Das einzige beobachtbare Verhalten, das sich ändert, ist der Wert von DATE$, TIME$, TIMER und NOW — und zwar zum Richtigen hin.

Rücknahme: Ein Zurücksetzen auf UTC wäre Zeitzone::Unbekannt als Vorgabe; die Abhängigkeit bliebe ungenutzt.

Open Questions

  • Ob die Bibliotheksanbindung in Stufe 2 (Zonennamen, Umrechnung zwischen Zonen als BASIC-Funktionen) auf derselben Bibliothek aufsetzt oder eine eigene Wahl trifft, entscheidet Stufe 2. Für diesen Change ohne Wirkung — hier wird nur der Versatz gebraucht.