Inhaltsverzeichnis

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.

std.time · std.datetime · 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“

<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>


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 #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 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 #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 (de17.08.2026 14:30:00, en_US8/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

<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


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

<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>

 
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

  1. std.time und std.datetime vertragen sich nicht — auch transitiv, etwa über std.cloud.s3.
  2. DiffSeconds(a, b) ist b − a.
  3. ParseFlexible kann ISO-mit-Zeit, RFC 2822, ISO-Datum und die deutsche Schreibweise — nicht aber 8/17/2026. Bei Misserfolg steht 0 im Puffer.
  4. Now() ist die Wanduhr und kann springen; Dauern gehören auf GetTimeMs().
  5. 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.