====== Zeit & Datum ======
Zeitpunkte bilden, formatieren, einlesen, Differenzen rechnen und prüfen, ob eine Frist abgelaufen ist. Alle Beispiele sind mit ''lyxc 1.1.0A'' übersetzt und ausgeführt; die gezeigten Ausgaben sind die echten.
→ [[lyx_-_programmiersprache:units:time|std.time]] · [[lyx_-_programmiersprache:units:datetime|std.datetime]] · [[lyx_-_programmiersprache:guides:welche-unit|Welche Unit?]]
----
===== Zwei Units, klare Arbeitsteilung =====
^ ^ ''std.time'' ^ ''std.datetime'' ^
| Ebene | untere | obere |
| Typen | ''date'', ''time'', ''datetime'', ''timestamp'' | rechnet auf ''int64'' |
| Kann | Zeitpunkt holen, Kalenderrechnung, Zeitzonen, ''Sleep'', Timer | Formatieren, Einlesen, Datumsarithmetik, Prüfen |
| Wofür | „wie spät ist es", „wie viele Tage bis…", „warte 100 ms" | „als Text ausgeben", „Text einlesen", „plus drei Monate" |
**Die beiden lassen sich nicht gemeinsam importieren.** Beide exportieren ''DaysInMonth'':
sema error: mehrdeutiges Symbol 'DaysInMonth' — exportiert von 'std.time' und 'std.datetime'
Das trifft auch **transitiv**: ''std.cloud.s3'' zieht über ''std.cloud.aws.transport'' das ''std.time'' mit — ein S3-Programm kann ''std.datetime'' deshalb nicht importieren, obwohl im Quelltext nichts darauf hindeutet. Einen Ausweg über Alias oder qualifizierten Zugriff gibt es nicht (→ [[lyx_-_programmiersprache:sprache:module-und-importe|Module & Importe]]).
**Also: eine der beiden wählen.** Wer beides braucht, trennt den Code in zwei Units.
----
===== Die Typen von ''std.time'' =====
Alle vier sind ''int64''-Aliase — die Namen sagen, was der Wert **bedeutet**:
^ Typ ^ Einheit ^ Nullpunkt ^
| ''date'' | Tage | 1970-01-01 |
| ''time'' | Sekunden | Mitternacht |
| ''datetime'' | Sekunden | Unix-Epoche |
| ''timestamp'' | **Mikrosekunden** | Unix-Epoche |
Weil es Aliase sind, warnt der Compiler nicht, wenn man sie verwechselt. Ein ''timestamp'', der als ''datetime'' gelesen wird, liegt um den Faktor eine Million daneben.
----
===== Einen Zeitpunkt holen =====
unit main;
import std.io;
import std.string;
import std.time;
fn main(): int64 {
Print("Now() Unix-Sekunden : "); PrintLn(IntToStr(Now() as int64));
Print("NowMs() : "); PrintLn(IntToStr(NowMs()));
Print("GetTimeMs() : "); PrintLn(IntToStr(GetTimeMs()));
return 0;
}
Now() Unix-Sekunden : 1786940893
NowMs() : 1786940893001
GetTimeMs() : 281969849
> **Die dritte Zahl ist kleiner — und das ist der Punkt.** ''Now()'' und ''NowMs()'' lesen die **Wanduhr** (Sekunden seit 1970), ''GetTimeMs()'' die **monotone Uhr** (seit Systemstart).
>
> Die Wanduhr kann springen: NTP korrigiert sie, Sommerzeit verschiebt sie, der Benutzer stellt sie. Für „wie lange hat das gedauert" und „ist die Frist abgelaufen" ist sie deshalb **falsch** — dort gehört die monotone Uhr hin. Umgekehrt taugt die monotone Uhr nicht zum Speichern: ihr Nullpunkt ändert sich beim nächsten Neustart.
----
===== Ist eine Zeit vergangen? =====
unit main;
import std.io;
import std.string;
import std.time;
fn main(): int64 {
var t0: int64 := GetTimeMs(); // monotone Uhr
Sleep(120);
var t1: int64 := GetTimeMs();
Print("nach Sleep(120): "); Print(IntToStr(t1 - t0)); PrintLn(" ms vergangen");
var frist: int64 := t0 + 100;
if (t1 >= frist) { PrintLn("Frist von 100 ms ist abgelaufen"); }
else { PrintLn("Frist laeuft noch"); }
return 0;
}
nach Sleep(120): 120 ms vergangen
Frist von 100 ms ist abgelaufen
**Das Muster ist immer dasselbe:** Startzeit merken, Frist als ''start + dauer'' ausrechnen, mit ''>='' vergleichen. Niemals die Differenz gegen die Wanduhr bilden.
^ Genauigkeit ^ Funktion ^
| Millisekunden | ''GetTimeMs()'' |
| Nanosekunden | ''GetTimeNs(CLOCK_MONOTONIC)'' |
| Mikrosekunden, Wanduhr | ''NowUs()'' |
Für periodische Aufgaben gibt es ''SleepUntil(clockId, absNs)'' — es schläft bis zu einem **absoluten** Zeitpunkt und vermeidet damit das Driften, das bei wiederholtem ''Sleep(interval)'' entsteht. Wer einen Dateideskriptor braucht, den ''Poll'' überwachen kann, nimmt ''TimerFdCreate''.
----
===== Kalenderrechnung mit ''std.time'' =====
Für „welcher Wochentag", „wie viele Tage bis" braucht es keine Uhrzeit — dafür ist ''date'' da:
unit main;
import std.io;
import std.string;
import std.time;
fn Z(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }
fn main(): int64 {
var d: date := DateFromYmd(2026, 8, 17);
Z("date als Tageszahl : ", d as int64);
Z(" Jahr : ", YearFromDate(d));
Z(" Monat : ", MonthFromDate(d));
Z(" Tag : ", DayFromDate(d));
Z(" Wochentag (0=So) : ", DayOfWeekFromDate(d));
var d2: date := DateFromYmd(2026, 12, 24);
Z("Tage bis Heiligabend: ", (d2 as int64) - (d as int64));
Z("IsLeapYear(2024) : ", IsLeapYear(2024) as int64);
Z("IsLeapYear(2100) : ", IsLeapYear(2100) as int64);
return 0;
}
date als Tageszahl : 20682
Jahr : 2026
Monat : 8
Tag : 17
Wochentag (0=So) : 1
Tage bis Heiligabend: 129
IsLeapYear(2024) : 1
IsLeapYear(2100) : 0
**Ein ''date'' ist eine Tageszahl** — die Differenz zweier Daten ist deshalb schlicht eine Subtraktion. Das ist der bequemste Weg für Fristen in Tagen, und er kennt keine Sommerzeitprobleme.
''IsLeapYear(2100)'' ist ''0'': durch 100 teilbar, aber nicht durch 400.
----
===== Zeitzonen =====
var utc: datetime := DatetimeFromUnixSeconds(1786977000);
var cet: datetime := ApplyTimeZone(utc, ZoneCEST());
UTC : 1786977000
CEST (UTC+2) : 1786984200
Unterschied in s : 7200
''ApplyTimeZone'' **verschiebt den Wert** um den Zonenversatz. Fertig mitgeliefert sind seit [[https://github.com/SEOLizer/LyX-Compiler/issues/1600|#1600]] alle 117 gebräuchlichen Zonen — ''ZoneByCode("CEST"c)'' statt Selbstbau; ''ZoneUTC()'', ''ZoneCET()'' und ''ZoneCEST()'' gibt es weiterhin.
==== Andere Zonen: selbst bauen ====
''TimeZone'' ist ein gewöhnliches Struct aus ''name: pchar'' und ''offset_seconds: int64'' — die drei Funktionen oben füllen es nur aus. Jede andere Zone ist deshalb vier Zeilen Arbeit:
unit main;
import std.io;
import std.string;
import std.time;
fn main(): int64 {
var utc: datetime := DatetimeFromUnixSeconds(1786977000);
var ist: datetime := ApplyTimeZone(utc, ZoneByCode("IST"c)); // Indien, +05:30
Print("UTC : "); PrintLn(IntToStr(utc as int64));
Print("IST +05:30 : "); PrintLn(IntToStr(ist as int64));
Print("Differenz : "); PrintLn(IntToStr((ist as int64) - (utc as int64)));
return 0;
}
UTC : 1786977000
IST +05:30 : 1786996800
Differenz : 19800
Halbe und dreiviertel Stunden sind dabei kein Sonderfall — der Versatz sind schlicht Sekunden. Indien +05:30 = 19800, Nepal +05:45 = 20700, Neufundland −03:30 = −12600. Wer sie doch selbst hinschreibt, nimmt die Konstanten: ''TZ_OFF_PLUS_0530'', ''TZ_OFF_PLUS_0545''.
**Die Versätze aller 117 Zonen stehen fertig in der [[lyx_-_programmiersprache:units:time:zeitzonen|Zeitzonen-Tabelle]]** — von ''SST'' bei −11:00 bis ''LINT'' bei +14:00, jeweils schon in Sekunden. Dort auch die drei Kürzel, die doppelt belegt sind (''IST'', ''GST'', ''ICT''): **den Versatz speichern, nicht das Kürzel.**
==== Zone aus der Umgebung: ''GetTimezoneOffset'' ====
''std.datetime'' geht einen Schritt weiter und liest die Umgebungsvariable ''TZ'' im POSIX-Format:
^ ''TZ'' ^ ''GetTimezoneOffset()'' ^ ''FormatTimezoneOffset'' ^
| //nicht gesetzt// | 0 | ''+00:00'' |
| ''CET-1'' | 3600 | ''+01:00'' |
| ''EST5'' | −18000 | ''-05:00'' |
| ''IST-5:30'' | 19800 | ''+05:30'' |
| ''NPT-5:45'' | 20700 | ''+05:45'' |
| ''CET-1CEST,M3.5.0,M10.5.0/3'' | 3600 | ''+01:00'' |
**Zwei Dinge sind an dieser Tabelle wichtig.**
Erstens das **POSIX-Vorzeichen**: es zählt //westlich// von Greenwich positiv, also genau andersherum, als man es schreibt. ''CET-1'' — mit Minus — ergibt **+1 Stunde**. ''GetTimezoneOffset'' dreht das um und liefert bereits die übliche Zählung („östlich von UTC positiv"), sodass der Rückgabewert direkt addiert werden kann.
Zweitens die **letzte Zeile**: die vollständige Angabe mit Sommerzeitregel liefert ''3600'', nicht ''7200'' — die Regel hinter dem Zonennamen (''M3.5.0,M10.5.0/3'') wird **nicht ausgewertet**. Zurück kommt der Standardversatz der Zone, nie die gerade geltende Ortszeit.
> **Es gibt keine Zeitzonendatenbank und keine automatische Sommerzeit-Umschaltung.** Weder ''ZoneByCode'' noch ''GetTimezoneOffset'' wissen, welche der beiden gerade gilt; ''/etc/localtime'' wird nirgends gelesen. Wer echte Ortszeit braucht, muss die Umschaltregel selbst anwenden.
>
> **Intern immer in UTC rechnen** und erst bei der Ausgabe umrechnen — das erspart die ganze Klasse von Fehlern, bei denen eine Stunde doppelt oder gar nicht vorkommt.
>
> Und: ''ApplyTimeZone'' liegt in ''std.time'', ''GetTimezoneOffset'' in ''std.datetime''. Wegen der Kollision oben sind beide nicht gemeinsam nutzbar — auf der ''std.datetime''-Seite ist die Verschiebung aber ohnehin eine Addition: ''dt + GetTimezoneOffset()''.
----
===== Formatieren mit ''std.datetime'' =====
unit main;
import std.io;
import std.string;
import std.alloc;
import std.datetime;
fn S(t: pchar, b: int64): void { Print(t); PrintLn(b as pchar); }
fn main(): int64 {
var buf: int64 := alloc(64);
var t: int64 := FromUnixTimestamp(1786977000); // 2026-08-17 14:30 UTC
FormatIso(t, buf as pchar); S("FormatIso : ", buf);
FormatRfc2822(t, buf as pchar); S("FormatRfc2822 : ", buf);
Format(t, "%Y-%m-%d %H:%M"c, buf as pchar); S("Format Y-m-d H:M : ", buf);
FormatDuration(93784, buf as pchar); S("Dauer 93784 s : ", buf);
FormatDurationShort(93784, buf as pchar); S(" kurz : ", buf);
FormatDuration(45, buf as pchar); S("Dauer 45 s : ", buf);
return 0;
}
FormatIso : 2026-08-17T14:30:00Z
FormatRfc2822 : Mon, 17 Aug 2026 14:30:00 +0000
Format Y-m-d H:M : 2026-08-17 14:30
Dauer 93784 s : 26h 03m 04s
kurz : 26:03:04
Dauer 45 s : 45s
**Der Zielpuffer gehört dem Aufrufer.** Jede ''Format*''-Funktion schreibt hinein und gibt einen Status zurück, keinen Text — dieselbe Konvention wie in ''std.string''. 64 Byte reichen für alle gezeigten Formate.
**Negative Dauern und Dauern ab 1000 Stunden** ergaben bis [[https://github.com/SEOLizer/LyX-Compiler/issues/1605|#1605]] stumm Müllzeichen. Jetzt: ''FormatDuration(-93784)'' → ''-26h 03m 04s'', ''FormatDurationShort(-45)'' → ''-00:45'', ''FormatDuration(12345*3600)'' → ''12345h''.
Dazu ''FormatRelative'' („vor 3 Stunden"), ''FormatLocale'' — das seit [[https://github.com/SEOLizer/LyX-Compiler/issues/1415|#1415]] wirklich lokalisiert (''de'' → ''17.08.2026 14:30:00'', ''en_US'' → ''8/17/2026 2:30:00 PM'') — sowie ''WeekdayLong''/''WeekdayShort'' und ''MonthLong''/''MonthShort'' für einzelne Namen.
----
===== Einlesen =====
unit main;
import std.io;
import std.string;
import std.alloc;
import std.datetime;
fn T(t: pchar, s: pchar): void {
var e: int64 := alloc(8);
poke64(e, -1); // Marker: unberuehrt
var rc: int64 := ParseFlexible(s, e);
Print(t); Print(" rc="); Print(IntToStr(rc));
Print(" Wert="); PrintLn(IntToStr(peek64(e)));
}
fn main(): int64 {
T("ISO mit Zeit 2026-08-17T14:30:00Z", "2026-08-17T14:30:00Z"c);
T("nur Datum 2026-08-17 ", "2026-08-17"c);
T("deutsch 17.08.2026 ", "17.08.2026"c);
T("deutsch+Zeit 17.08.2026 14:30 ", "17.08.2026 14:30"c);
T("RFC 2822 Mon, 17 Aug 2026 … ", "Mon, 17 Aug 2026 14:30:00 +0000"c);
T("US 8/17/2026 ", "8/17/2026"c);
T("Unsinn kein Datum ", "kein Datum"c);
return 0;
}
ISO mit Zeit 2026-08-17T14:30:00Z rc=1 Wert=1786977000
nur Datum 2026-08-17 rc=1 Wert=1786924800
deutsch 17.08.2026 rc=1 Wert=1786924800
deutsch+Zeit 17.08.2026 14:30 rc=1 Wert=1786977000
RFC 2822 Mon, 17 Aug 2026 … rc=1 Wert=1786977000
US 8/17/2026 rc=0 Wert=0
Unsinn kein Datum rc=0 Wert=0
**''ParseFlexible'' probiert vier Formate** ([[https://github.com/SEOLizer/LyX-Compiler/issues/1603|#1603]]), in dieser Reihenfolge: ISO 8601 mit Zeitanteil, RFC 2822, ISO-Datum ohne Zeit (Zeitanteil ''00:00:00'') und die deutsche Schreibweise ''17.08.2026'' mit optionalem '' 14:30'' oder '' 14:30:00''. Die amerikanische Schreibweise ''8/17/2026'' scheitert weiterhin.
**Bei Misserfolg steht jetzt 0 im Zielpuffer**, nicht mehr der vorherige Inhalt: der Marker ''−1'' von oben ist weg. Ein übersehener ''rc''-Test endet damit bei ''1970-01-01'' — falsch, aber offensichtlich falsch. Dasselbe gilt für ''ParseIso'', ''ParseRfc2822'', ''ParseDate'' und ''ParseTime''.
''rc == 1'' heißt Erfolg, ''rc == 0'' Misserfolg. Trotzdem immer prüfen.
Für Datum und Uhrzeit getrennt gibt es ''ParseDate'' und ''ParseTime'', die die Einzelfelder in drei Puffer schreiben:
var y: int64 := alloc(8); var mo: int64 := alloc(8); var d: int64 := alloc(8);
ParseDate("2026-08-17"c, y, mo, d);
// peek64(y) = 2026, peek64(mo) = 8, peek64(d) = 17
----
===== Rechnen mit Datumswerten =====
unit main;
import std.io;
import std.string;
import std.alloc;
import std.datetime;
fn Z(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }
fn S(t: pchar, b: int64): void { Print(t); PrintLn(b as pchar); }
fn main(): int64 {
var buf: int64 := alloc(64);
var start: int64 := 1786977000; // 2026-08-17 14:30:00 UTC
var ende: int64 := 1787216400; // 2026-08-20 09:00:00 UTC
Z("DiffSeconds(start, ende) : ", DiffSeconds(start, ende));
Z(" in Stunden : ", DiffSeconds(start, ende) / 3600);
Z("DiffDays kalendarisch : ", DiffDays(2026, 8, 17, 2026, 8, 20));
FormatIso(AddDays(start, 30), buf as pchar); S("start + 30 Tage : ", buf);
FormatIso(AddMonths(start, 6), buf as pchar); S("start + 6 Monate : ", buf);
FormatIso(AddYears(start, 1), buf as pchar); S("start + 1 Jahr : ", buf);
var jan31: int64 := 1769817600; // 2026-01-31
FormatIso(AddMonths(jan31, 1), buf as pchar); S("31.01. + 1 Monat : ", buf);
return 0;
}
DiffSeconds(start, ende) : 239400
in Stunden : 66
DiffDays kalendarisch : 3
start + 30 Tage : 2026-09-16T14:30:00Z
start + 6 Monate : 2027-02-17T14:30:00Z
start + 1 Jahr : 2027-08-17T14:30:00Z
31.01. + 1 Monat : 2026-02-28T00:00:00Z
**''DiffSeconds(a, b)'' rechnet ''b − a''**, nicht ''a − b''. Es ist als „von ''a'' bis ''b''" zu lesen. Wer die Argumente in der intuitiven Reihenfolge „später, früher" übergibt, bekommt das **negative** Ergebnis:
DiffSeconds(start, ende) = 239400
DiffSeconds(ende, start) = -239400
Bei einer Abfrage wie ''%%if (DiffSeconds(x, y) > frist)%%'' entscheidet das über wahr und falsch.
> **''AddMonths'' klemmt auf das Monatsende.** Der 31. Januar plus ein Monat ergibt den **28. Februar**, nicht den 3. März. Das ist die übliche und meist gewünschte Festlegung — sie ist aber **nicht umkehrbar**: ''AddMonths(AddMonths(jan31, 1), -1)'' führt nicht zum 31. Januar zurück. Wer Monatsfristen über mehrere Schritte fortschreibt, rechnet vom **ursprünglichen** Datum aus, nicht vom jeweils letzten Ergebnis.
''DiffDays'' nimmt **sechs Einzelwerte** (Jahr, Monat, Tag zweimal), keine Zeitstempel — und zählt Kalendertage, nicht Sekunden geteilt durch 86400. Für Fristen in Tagen ist das die richtige Wahl, weil Sommerzeitsprünge keine Rolle spielen.
----
===== Prüfen vor dem Rechnen =====
IsValidDate(2026, 2, 30) // 0 — den gibt es nicht
IsValidDate(2024, 2, 29) // 1 — Schaltjahr
IsValidTime(25, 0, 0) // 0
IsValidDate 2026-02-30: 0
IsValidDate 2024-02-29: 1
IsValidTime 25:00:00 : 0
Dazu ''IsValidDateTime'' für beides zusammen. **Eingaben aus Formularen oder Dateien gehören durch diese Prüfung**, bevor sie in ''FromUnixTimestamp'' oder eine Rechnung wandern — die Arithmetikfunktionen prüfen ihre Argumente nicht.
----
===== Wiederkehrende Aufgaben auf einen Blick =====
^ Ich will … ^ So geht es ^
| die aktuelle Zeit speichern | ''Now()'' aus ''std.time'' — Unix-Sekunden |
| eine Dauer messen | ''GetTimeMs()'' vorher und nachher, **nie** ''Now()'' |
| prüfen, ob eine Frist abgelaufen ist | ''%%GetTimeMs() >= start + dauer%%'' |
| Tage zwischen zwei Daten | ''date''-Differenz (''std.time'') oder ''DiffDays'' (''std.datetime'') |
| Sekunden zwischen zwei Zeitpunkten | ''DiffSeconds(frueher, spaeter)'' — Reihenfolge beachten |
| einen Monat weiterrechnen | ''AddMonths'' — klemmt auf das Monatsende |
| ein Datum ausgeben | ''FormatIso'' oder ''Format'' mit Muster |
| eine Dauer lesbar ausgeben | ''FormatDuration'' (''26h 03m 04s'') |
| einen ISO-Zeitstempel einlesen | ''ParseIso'', Rückgabewert prüfen |
| eine Eingabe validieren | ''IsValidDate'' / ''IsValidTime'' |
| periodisch etwas tun | ''SleepUntil'' mit absolutem Zeitpunkt, nicht ''Sleep'' in der Schleife |
| einen Timer als Dateideskriptor | ''TimerFdCreate'' + ''Poll'' |
----
===== Die fünf Stolpersteine =====
- **''std.time'' und ''std.datetime'' vertragen sich nicht** — auch transitiv, etwa über ''std.cloud.s3''.
- **''DiffSeconds(a, b)'' ist ''b − a''.**
- **''ParseFlexible'' kann ISO-mit-Zeit, RFC 2822, ISO-Datum und die deutsche Schreibweise** — nicht aber ''8/17/2026''. Bei Misserfolg steht 0 im Puffer.
- **''Now()'' ist die Wanduhr** und kann springen; Dauern gehören auf ''GetTimeMs()''.
- **''timestamp'' sind Mikrosekunden**, ''datetime'' Sekunden — beide sind ''int64'', der Compiler warnt nicht.
----
Letzte Aktualisierung: 2026-08-18 — ''ParseFlexible'' auf vier Formate und genulltes Ziel nachgezogen ([[https://github.com/SEOLizer/LyX-Compiler/issues/1603|#1603]]), Zeitzonen auf ''ZoneByCode'' umgestellt ([[https://github.com/SEOLizer/LyX-Compiler/issues/1600|#1600]]), Dauerausgabe mit Vorzeichen ergänzt ([[https://github.com/SEOLizer/LyX-Compiler/issues/1605|#1605]]); Beispiele gegen die Repo-Fassung der Standardbibliothek mit ''lyxc 1.1.2E'' übersetzt.
Vorherige Aktualisierung: 2026-08-17 · alle Beispiele mit ''lyxc 1.1.0A'' übersetzt und ausgeführt; Argumentreihenfolge von ''DiffSeconds'', Verhalten von ''ParseFlexible'' und ''AddMonths'' sowie der Unterschied zwischen Wanduhr und monotoner Uhr einzeln nachgemessen.