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.
→ std.time · std.datetime · Welche Unit?
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“ |
<WRAP alert>
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 (→ Module & Importe).
Also: eine der beiden wählen. Wer beides braucht, trennt den Code in zwei Units. </WRAP>
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.
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()undNowMs()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.
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.
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.
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 #1600 alle 117 gebräuchlichen Zonen — ZoneByCode(„CEST“c) statt Selbstbau; ZoneUTC(), ZoneCET() und ZoneCEST() gibt es weiterhin.
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 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.
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. WederZoneByCodenochGetTimezoneOffsetwissen, welche der beiden gerade gilt;/etc/localtimewird 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:ApplyTimeZoneliegt instd.time,GetTimezoneOffsetinstd.datetime. Wegen der Kollision oben sind beide nicht gemeinsam nutzbar — auf derstd.datetime-Seite ist die Verschiebung aber ohnehin eine Addition:dt + GetTimezoneOffset().
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 #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 #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.
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
<WRAP alert>
ParseFlexible probiert vier Formate (#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.
</WRAP>
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
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
<WRAP alert>
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.
</WRAP>
AddMonthsklemmt 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.
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.
| 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 |
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 (#1603), Zeitzonen auf ZoneByCode umgestellt (#1600), Dauerausgabe mit Vorzeichen ergänzt (#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.