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:
2026-09-04 07:01:53 +02:00
parent d0461bea34
commit a0fb41420e
15 changed files with 433 additions and 65 deletions

View File

@@ -45,8 +45,11 @@ pub struct RtState {
pub dir_index: usize,
/// Versatz der Programmuhr in Sekunden. `DATE$ =` und `TIME$ =` können
/// die Systemuhr nicht stellen; sie verschieben stattdessen diesen
/// Versatz (dokumentierte Abweichung).
/// Versatz (dokumentierte Abweichung). Er wirkt auf die Ortszeit.
pub uhr_offset: i64,
/// Woher der UTC-Versatz kommt. Vorgabe ist die Zone des Rechners;
/// ist sie nicht ermittelbar, steht hier `Unbekannt` und es gilt UTC.
pub zeitzone: dt::Zeitzone,
rng: u32,
rnd_last: f32,
pub command: String,
@@ -70,6 +73,7 @@ impl Default for RtState {
dir_treffer: Vec::new(),
dir_index: 0,
uhr_offset: 0,
zeitzone: dt::Zeitzone::ermitteln(),
// Startzustand des Vorbild-PRNG; die exakte
// PRNG-Kompatibilität ist Aufgabe in PLAN.md Phase 3
// („RND/RANDOMIZE — kompatibler PRNG").
@@ -1444,23 +1448,24 @@ fn bi_input_s(st: &mut RtState, host: &mut dyn Host, a: &mut [Value]) -> Result<
// ---- Sonstiges ------------------------------------------------------------------
fn bi_timer(_: &mut RtState, _: &mut dyn Host, _: &mut [Value]) -> Result<Option<Value>, RuntimeError> {
// Sekunden seit Mitternacht (UTC-basiert; lokale Zeitzone: Phase 3).
let secs = std::time::SystemTime::now()
fn bi_timer(st: &mut RtState, _: &mut dyn Host, _: &mut [Value]) -> Result<Option<Value>, RuntimeError> {
// Sekunden seit der **lokalen** Mitternacht, mit Bruchteil.
let bruchteil = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs_f64() % 86_400.0)
.map(|d| d.as_secs_f64().fract())
.unwrap_or(0.0);
Ok(Some(Value::Sng(secs as f32)))
let ganze = dt::jetzt_sekunden(st.zeitzone, st.uhr_offset).rem_euclid(86_400);
Ok(Some(Value::Sng(ganze as f32 + bruchteil as f32)))
}
fn bi_date(st: &mut RtState, _: &mut dyn Host, _: &mut [Value]) -> Result<Option<Value>, RuntimeError> {
let secs = dt::jetzt_sekunden() + st.uhr_offset;
let secs = dt::jetzt_sekunden(st.zeitzone, st.uhr_offset);
let (y, m, d) = dt::civil_from_days(secs.div_euclid(86_400));
s_ok(format!("{m:02}-{d:02}-{y:04}"))
}
fn bi_time(st: &mut RtState, _: &mut dyn Host, _: &mut [Value]) -> Result<Option<Value>, RuntimeError> {
let s = (dt::jetzt_sekunden() + st.uhr_offset).rem_euclid(86_400);
let s = dt::jetzt_sekunden(st.zeitzone, st.uhr_offset).rem_euclid(86_400);
s_ok(format!("{:02}:{:02}:{:02}", s / 3600, (s / 60) % 60, s % 60))
}
@@ -1468,7 +1473,7 @@ fn bi_date_set(st: &mut RtState, _: &mut dyn Host, a: &mut [Value]) -> Result<Op
let t = arg_str(a, 0)?;
let ziel = dt::datevalue(&t).ok_or(RuntimeError::ILLEGAL_FUNCTION_CALL)?;
// Nur den Datumsanteil verschieben, die Tageszeit bleibt.
let jetzt = dt::jetzt_serial(st.uhr_offset);
let jetzt = dt::jetzt_serial(st.zeitzone, st.uhr_offset);
let diff_tage = ziel.floor() - jetzt.floor();
st.uhr_offset += (diff_tage * 86_400.0) as i64;
Ok(None)
@@ -1477,14 +1482,14 @@ fn bi_date_set(st: &mut RtState, _: &mut dyn Host, a: &mut [Value]) -> Result<Op
fn bi_time_set(st: &mut RtState, _: &mut dyn Host, a: &mut [Value]) -> Result<Option<Value>, RuntimeError> {
let t = arg_str(a, 0)?;
let ziel = dt::timevalue(&t).ok_or(RuntimeError::ILLEGAL_FUNCTION_CALL)?;
let jetzt = dt::jetzt_serial(st.uhr_offset);
let jetzt = dt::jetzt_serial(st.zeitzone, st.uhr_offset);
let diff = ziel - (jetzt - jetzt.floor());
st.uhr_offset += (diff * 86_400.0).round() as i64;
Ok(None)
}
fn bi_now(st: &mut RtState, _: &mut dyn Host, _: &mut [Value]) -> Result<Option<Value>, RuntimeError> {
Ok(Some(Value::Dbl(dt::jetzt_serial(st.uhr_offset))))
Ok(Some(Value::Dbl(dt::jetzt_serial(st.zeitzone, st.uhr_offset))))
}
fn bi_date_serial(_: &mut RtState, _: &mut dyn Host, a: &mut [Value]) -> Result<Option<Value>, RuntimeError> {