Ortszeit statt UTC
Setzt den OpenSpec-Change phase-3-ortszeit um (21/21 Aufgaben) und zieht
damit die in Phase 3 eingegangene Abweichung zurueck: DATE$, TIME$, TIMER
und NOW rechnen in der Zeitzone des Rechners, TIMER zaehlt ab der lokalen
Mitternacht.
Versatzermittlung
- jiff 0.2.35 als Abhaengigkeit von tb-runtime
- datetime::utc_versatz(zeitpunkt) ist der einzige Aufrufpunkt der
Bibliothek; die Kalenderrechnung darunter bleibt Eigenimplementierung
und sieht sie nie -- zwei Kalendermodelle nebeneinander waeren eine
dauerhafte Fehlerquelle
- Der Versatz gilt je Zeitpunkt, nicht je Programmlauf: ein Programm, das
ueber eine Zeitumstellung hinweg laeuft, folgt ihr
Zonenzustand
- Zeitzone { System, Fest(i32), Unbekannt } im RtState
- Fest(n) macht Tests und Korpusprogramme unabhaengig von der Zone des
Testrechners; TZ waere prozessweit und set_var seit Rust 2024 unsafe
Rueckfallebene
- Ohne ermittelbare Zone gilt UTC, das Programm laeuft weiter
- tbc run meldet den Rueckfall einmal beim Start auf der Fehlerausgabe,
nicht je Abfrage -- ein stiller Wechsel waere ein Verstoss gegen den
Guiding Principle
Korpus
- Neue Direktive ' tb-tz: +-HH:MM im Harness
- zeitzone.bas prueft Ortszeit, TIMER ab lokaler Mitternacht und den
Programmversatz gegen eine gesetzte Uhrzeit
- datumzeit.bas auf die Direktive umgestellt; Sollausgabe unveraendert
Nebenbefund
- TIMER trennt jetzt ganze Sekunden (Ortszeit) vom Bruchteil
(Systemuhr). Ohne die Trennung waere TIMER in Zonen mit halbstuendigem
Versatz gesprungen.
Dokumentation: die Abweichung "Uhr laeuft in UTC" entfaellt aus
sprachreferenz.md; bibliothek.md und PLAN.md nachgezogen, jiff steht im
Stufe-2-Ideenspeicher neben crossterm.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -660,3 +660,72 @@ fn lokaler_handler_hat_vorrang() {
|
||||
END SUB\n";
|
||||
assert_eq!(out(src), "Lokal\n");
|
||||
}
|
||||
|
||||
// ---- Ortszeit (Spec konsolen-basisbibliothek) ------------------------------
|
||||
|
||||
use tb_runtime::datetime::Zeitzone;
|
||||
|
||||
/// Programm mit festgelegter Zeitzone laufen lassen.
|
||||
fn out_mit_zone(src: &str, zone: Zeitzone) -> String {
|
||||
use tb_runtime::host::CaptureHost;
|
||||
let module = tb_vm::compile_source("TEST", src).unwrap_or_else(|d| panic!("{d:?}"));
|
||||
let mut vm = Vm::new(module);
|
||||
vm.rt.zeitzone = zone;
|
||||
let mut host = CaptureHost::default();
|
||||
assert_eq!(vm.run(&mut host), RunEvent::Ended);
|
||||
tb_runtime::snapshot::text(&vm.rt.screen)
|
||||
}
|
||||
|
||||
/// `TIME$` und `HOUR(NOW)` nennen dieselbe Stunde — sie rechnen beide
|
||||
/// über den Zonenzustand.
|
||||
#[test]
|
||||
fn time_und_now_stimmen_ueberein() {
|
||||
let t = out_mit_zone("PRINT LEFT$(TIME$, 2); HOUR(NOW)", Zeitzone::Fest(7200));
|
||||
let zeile = t.lines().next().unwrap();
|
||||
let aus_time: i32 = zeile[..2].parse().unwrap();
|
||||
let aus_now: i32 = zeile[2..].trim().parse().unwrap();
|
||||
assert_eq!(aus_time, aus_now, "{zeile:?}");
|
||||
}
|
||||
|
||||
/// Ein fester Versatz verschiebt die Uhr um genau diesen Betrag.
|
||||
#[test]
|
||||
fn fester_versatz_verschiebt_die_uhr() {
|
||||
let src = "PRINT INT(TIMER)";
|
||||
let ohne: f64 = out_mit_zone(src, Zeitzone::Unbekannt).trim().parse().unwrap();
|
||||
let mit: f64 = out_mit_zone(src, Zeitzone::Fest(3600)).trim().parse().unwrap();
|
||||
// Modulo Tageslänge, damit ein Mitternachtsübergang nichts kaputt macht.
|
||||
let diff = (mit - ohne).rem_euclid(86_400.0);
|
||||
assert!((diff - 3600.0).abs() <= 2.0, "Differenz {diff}");
|
||||
}
|
||||
|
||||
/// `TIMER` zählt ab der lokalen Mitternacht, nicht ab der UTC-Mitternacht.
|
||||
#[test]
|
||||
fn timer_zaehlt_ab_lokaler_mitternacht() {
|
||||
let t = out_mit_zone("PRINT INT(TIMER / 3600); VAL(LEFT$(TIME$, 2))", Zeitzone::Fest(-18000));
|
||||
let zahlen: Vec<f64> = t
|
||||
.split_whitespace()
|
||||
.filter_map(|w| w.parse().ok())
|
||||
.collect();
|
||||
assert_eq!(zahlen.len(), 2, "{t:?}");
|
||||
assert_eq!(zahlen[0], zahlen[1], "TIMER-Stunde ≠ TIME$-Stunde: {t:?}");
|
||||
}
|
||||
|
||||
/// `TIME$ =` wirkt auf die Ortszeit und alle davon abgeleiteten Werte.
|
||||
#[test]
|
||||
fn programmversatz_wirkt_auf_alle_zeitfunktionen() {
|
||||
let t = out_mit_zone(
|
||||
"TIME$ = \"10:20:30\"\nDATE$ = \"01-02-1993\"\nPRINT TIME$; \" \"; DATE$; HOUR(NOW); YEAR(NOW)",
|
||||
Zeitzone::Fest(3600),
|
||||
);
|
||||
assert!(t.starts_with("10:20:30 01-02-1993 10 1993"), "{t:?}");
|
||||
}
|
||||
|
||||
/// Ohne ermittelbare Zone gilt UTC — ohne Fehler.
|
||||
#[test]
|
||||
fn rueckfall_auf_utc_laeuft_fehlerfrei() {
|
||||
let t = out_mit_zone(
|
||||
"PRINT LEN(DATE$); LEN(TIME$); TIMER >= 0; YEAR(NOW) > 2000",
|
||||
Zeitzone::Unbekannt,
|
||||
);
|
||||
assert_eq!(t.trim(), "10 8 -1 -1", "{t:?}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user