std.time — Zeit, Datum und Timer

Zurück zur Unit-Übersicht

Drei Aufgabenbereiche in einer Unit: Kalenderrechnung (Datum ↔ Tageszahl, Schaltjahre, Wochentag), Uhrzeit lesen aus verschiedenen Systemuhren und Warten — von einfachem Sleep bis zu Kernel-Timern über Dateideskriptoren, die sich in eine epoll-Schleife einhängen lassen.

Alle Zeitquellen sind Syscalls (clock_gettime, nanosleep, timerfd_*); es wird keine C-Bibliothek gebraucht, das Programm bleibt statisch linkbar.

Einsatzbereiche: Zeitstempel für Protokolle und Dateiformate, Laufzeitmessung, Scheduling, Rate-Limiting, Timeouts.

Autor: Andreas Röne
Copyright: 2024–2025 Andreas Röne
Quelle: std/time.lyx

<WRAP info> Die frühere Einheitenfalle (#1465) ist behoben: Die Zeitkonstanten tragen ihre Einheit jetzt im Namen — US_SECONDUS_DAY für Mikrosekunden, MS_SECONDMS_DAY für Millisekunden. Sleep(MS_SECOND) wartet damit tatsächlich eine Sekunde. </WRAP>

Import

import std.time;


Typen

Alle vier Typen sind int64-Aliase — sie dokumentieren die Bedeutung, der Compiler unterscheidet sie nicht. Ein date lässt sich also versehentlich dort einsetzen, wo ein datetime erwartet wird.

Typ Bedeutung Nullpunkt
date Tage 1970-01-01 = 0
time Sekunden seit Mitternacht 00:00:00 = 0
datetime Unix-Sekunden 1970-01-01 00:00:00 UTC
timestamp Unix-Mikrosekunden dito

TimeZone ist ein Struct aus name: pchar und offset_seconds: int64.

Weil date eine schlichte Tageszahl ist, rechnet man mit Datumsangaben ganz normal: tag + 7 ist eine Woche später, endeJahr - tag die Zahl der verbleibenden Tage.


Kalenderrechnung

Signatur Beschreibung
DateFromYmd(year, month, day: int64): date Datum in Tageszahl
YearFromDate(d: date): int64 Jahr aus Tageszahl
MonthFromDate(d: date): int64 Monat (1–12)
DayFromDate(d: date): int64 Tag im Monat
DaysFromCivil(year, month, day: int64): int64 Rechenkern hinter DateFromYmd
CivilYearFromDays(days: int64): int64 Rechenkern hinter YearFromDate
CivilMonthFromDays(days: int64): int64 Rechenkern hinter MonthFromDate
CivilDayFromDays(days: int64): int64 Rechenkern hinter DayFromDate
DayOfWeekFromDate(d: date): int64 Wochentag, 0 = Sonntag bis 6 = Samstag
IsLeapYear(y: int64): bool Schaltjahr nach gregorianischer Regel
DaysInMonth(year, month: int64): int64 Tage im Monat, Schaltjahr berücksichtigt

Die Civil*-Funktionen sind das gleiche wie die *FromDate-Varianten — letztere sind nur dünne Hüllen. Negative Tageszahlen (vor 1970) und weit zurückliegende Jahre funktionieren; geprüft bis Jahr 1.

Uhrzeit innerhalb eines Tages

Signatur Beschreibung
TimeFromHms(hour, minute, second: int64): time Sekunden seit Mitternacht
HourFromTime(t: time): int64 Stunde
MinuteFromTime(t: time): int64 Minute
SecondFromTime(t: time): int64 Sekunde

Umrechnung zwischen den Typen

Signatur Beschreibung
DatetimeFromUnixSeconds(sec: int64): datetime Identität — dient der Lesbarkeit
DatetimeToUnixSeconds(dt: datetime): int64 Identität
TimestampFromUnixSeconds(sec: int64): timestamp Sekunden → Mikrosekunden
UnixSecondsFromTimestamp(ts: timestamp): int64 Mikrosekunden → Sekunden

Uhrzeit lesen

Signatur Uhr Einheit
Now(): datetime CLOCK_REALTIME Sekunden
NowMs(): int64 CLOCK_REALTIME Millisekunden
NowUs(): timestamp CLOCK_REALTIME Mikrosekunden
GetUnixMs(): int64 gettimeofday Millisekunden
GetTimeNs(clockId: int64): int64 frei wählbar Nanosekunden
GetTimeMs(): int64 CLOCK_MONOTONIC Millisekunden
GetTimeOfDay(tvSecOut, tvUsecOut: int64): int64 gettimeofday schreibt Sekunden und Mikrosekunden in zwei Puffer
Uhr Wert Eigenschaft
CLOCK_REALTIME 0 Wanduhrzeit; springt bei Zeitumstellung und NTP-Korrektur
CLOCK_MONOTONIC 1 läuft seit Systemstart gleichmäßig vorwärts, springt nie; die richtige Wahl für Zeitmessungen
CLOCK_PROCESS_CPUTIME_ID 2 verbrauchte CPU-Zeit des Prozesses
CLOCK_THREAD_CPUTIME_ID 3 verbrauchte CPU-Zeit des Threads
CLOCK_MONOTONIC_RAW 4 wie MONOTONIC, ohne NTP-Feinregelung
CLOCK_BOOTTIME 7 wie MONOTONIC, zählt Suspend-Zeiten mit

Der Unterschied ist messbar: auf einem System, das zwischendurch im Ruhezustand war, meldete CLOCK_MONOTONIC 81561 Sekunden, CLOCK_BOOTTIME dagegen 93227 — passend zu /proc/uptime.


Warten

Signatur Einheit Beschreibung
Sleep(ms: int64): int64 Millisekunden Rückgabe 0 bei Erfolg, negativer Fehlercode sonst
SleepNs(ns: int64): int64 Nanosekunden dito
SleepUntil(clockId, absNs: int64): int64 absolute Nanosekunden schläft bis zu einem Zeitpunkt statt einer Dauer

Sleep(0) kehrt sofort zurück, Sleep(-5) liefert -22 (EINVAL) ohne zu warten. Ein Signal kann den Schlaf vorzeitig beenden — dann kommt ein negativer Wert zurück, und die restliche Zeit ist nicht abgewartet.

SleepUntil ist die richtige Wahl für gleichmäßige Takte: bei wiederholtem Sleep(100) summiert sich die Bearbeitungszeit zwischen den Aufrufen auf, bei absoluten Zielzeitpunkten nicht.

Hilfskonstanten

Konstante Wert Bedeutung
MS_SECOND 1000 eine Sekunde in Millisekunden — passt zu Sleep
MS_MINUTE 60000 eine Minute in Millisekunden
MS_HOUR 3600000 eine Stunde in Millisekunden
MS_DAY 86400000 ein Tag in Millisekunden
US_SECOND 1000000 eine Sekunde in Mikrosekunden — passt zu timestamp
US_MINUTE 60000000 eine Minute in Mikrosekunden
US_HOUR 3600000000 eine Stunde in Mikrosekunden
US_DAY 86400000000 ein Tag in Mikrosekunden
NS_PER_MS 1000000 Nanosekunden je Millisekunde — für SleepNs
NS_PER_SEC 1000000000 Nanosekunden je Sekunde
US_PER_SEC 1000000 Mikrosekunden je Sekunde

Das Präfix nennt die Einheit: MS_* für Sleep, NS_* für SleepNs und SleepUntil, US_* für Werte vom Typ timestamp.


Kernel-Timer (timerfd)

Ein timerfd ist ein Timer, der sich wie eine Datei verhält: Er wird lesbar, wenn er abläuft. Damit lässt er sich zusammen mit Sockets in einer epoll-Schleife überwachen — der übliche Weg, Timeouts und periodische Aufgaben in einem Server unterzubringen, ohne einen eigenen Thread dafür zu opfern.

Signatur Beschreibung
TimerFdCreate(clockId, flags: int64): int64 Erzeugt den Timer, liefert einen fd
TimerFdSetTime(fd, intervalNs, valueNs: int64): int64 Stellt ihn: valueNs bis zum ersten Ablauf, intervalNs als Wiederholung (0 = einmalig)
TimerFdGetTime(fd: int64): int64 Verbleibende Nanosekunden bis zum nächsten Ablauf
TimerFdRead(fd: int64): int64 Blockiert bis zum Ablauf, liefert die Zahl der seither vergangenen Abläufe
Flag Wert Bedeutung
TFD_CLOEXEC 524288 fd bei execve schließen
TFD_NONBLOCK 2048 TimerFdRead blockiert nicht
TFD_TIMER_ABSTIME 1 Zeitangabe absolut statt relativ
TFD_TIMER_CANCEL_ON_SET 2 Timer abbrechen, wenn die Systemzeit gestellt wird

Der Rückgabewert von TimerFdRead ist wichtig: Wenn das Programm mit der Bearbeitung nicht hinterherkommt, liefert er die Zahl der verpassten Abläufe. Eine Uhr, die 3 zurückbekommt, hat zwei Takte übersprungen.


Beispiele

Mit Datumsangaben rechnen

import std.time;

fn main(): int64 {
    // Datum zerlegen und wieder zusammensetzen
    var tag: date := DateFromYmd(2026, 8, 13);
    PrintLn("2026-08-13 = Tag ", IntToStr(tag), " seit 1970-01-01");
    PrintLn("zurueck: ", IntToStr(YearFromDate(tag)), "-",
            IntToStr(MonthFromDate(tag)), "-", IntToStr(DayFromDate(tag)));

    // Wochentag (0 = Sonntag)
    PrintLn("Wochentag: ", IntToStr(DayOfWeekFromDate(tag)), "  (4 = Donnerstag)");

    // Kalenderrechnen ist Ganzzahlrechnen
    var inEinerWoche: date := tag + 7;
    PrintLn("in einer Woche: ", IntToStr(YearFromDate(inEinerWoche)), "-",
            IntToStr(MonthFromDate(inEinerWoche)), "-", IntToStr(DayFromDate(inEinerWoche)));
    PrintLn("Tage bis Jahresende: ", IntToStr(DateFromYmd(2026, 12, 31) - tag));

    // Schaltjahre und Monatslaengen
    PrintLn("2024 Schaltjahr? ", IntToStr(IsLeapYear(2024) as int64),
            "   1900? ", IntToStr(IsLeapYear(1900) as int64),
            "   2000? ", IntToStr(IsLeapYear(2000) as int64));
    PrintLn("Februar 2024 hat ", IntToStr(DaysInMonth(2024, 2)), " Tage");
    return 0;
}

Ausgabe:

2026-08-13 = Tag 20678 seit 1970-01-01
zurueck: 2026-8-13
Wochentag: 4  (4 = Donnerstag)
in einer Woche: 2026-8-20
Tage bis Jahresende: 140
2024 Schaltjahr? 1   1900? 0   2000? 1
Februar 2024 hat 29 Tage

Der Rundlauf Datum → Tageszahl → Datum wurde über den ganzen Bereich geprüft, einschließlich 1969-12-31 (Tag −1), Schalttagen und Jahr 1.

Aktuelle Zeit in Datum und Uhrzeit zerlegen

import std.time;

fn main(): int64 {
    var jetzt: datetime := Now();
    PrintLn("Unix-Sekunden: ", IntToStr(jetzt));

    // Unix-Sekunden in Datum und Uhrzeit zerlegen (UTC)
    var tage: int64    := jetzt / 86400;
    var restSek: int64 := jetzt % 86400;
    PrintLn("Datum (UTC):  ", IntToStr(CivilYearFromDays(tage)), "-",
            IntToStr(CivilMonthFromDays(tage)), "-", IntToStr(CivilDayFromDays(tage)));
    PrintLn("Uhrzeit (UTC): ", IntToStr(HourFromTime(restSek)), ":",
            IntToStr(MinuteFromTime(restSek)), ":", IntToStr(SecondFromTime(restSek)));

    // Ortszeit: Offset selbst waehlen — Sommerzeit wird nicht erkannt
    var lokal: datetime := ApplyTimeZone(jetzt, ZoneCEST());
    var lr: int64 := lokal % 86400;
    PrintLn("Uhrzeit (CEST): ", IntToStr(HourFromTime(lr)), ":", IntToStr(MinuteFromTime(lr)));
    return 0;
}

Ausgabe (Gegenprobe mit date -u in derselben Sekunde):

Unix-Sekunden: 1786647280
Datum (UTC):  2026-8-13
Uhrzeit (UTC): 18:54:40
Uhrzeit (CEST): 20:54
Referenz UTC: 2026-08-13 18:54

Das Zerlegen läuft über / 86400 und % 86400 — es gibt keine fertige Funktion dafür. Für negative Unix-Zeiten (vor 1970) müsste man den Rest zusätzlich korrigieren, weil % in Lyx dem Vorzeichen des Dividenden folgt.

Laufzeit messen und warten

import std.time;

fn main(): int64 {
    // Laufzeit messen: immer die MONOTONE Uhr, nie CLOCK_REALTIME
    var start: int64 := GetTimeNs(CLOCK_MONOTONIC);

    var summe: int64 := 0;
    var i: int64 := 0;
    while (i < 3000000) { summe := summe + i; i := i + 1; }

    var dauer: int64 := GetTimeNs(CLOCK_MONOTONIC) - start;
    PrintLn("Schleife: ", IntToStr(dauer / NS_PER_MS), " ms (", IntToStr(dauer), " ns)");
    PrintLn("Summe = ", IntToStr(summe));

    // Schlafen
    var a: int64 := GetTimeMs();
    Sleep(150);
    PrintLn("Sleep(150) dauerte ", IntToStr(GetTimeMs() - a), " ms");

    var b: int64 := GetTimeMs();
    SleepNs(50 * NS_PER_MS);
    PrintLn("SleepNs(50 ms) dauerte ", IntToStr(GetTimeMs() - b), " ms");
    return 0;
}

Ausgabe:

Schleife: 36 ms (36095621 ns)
Summe = 4499998500000
Sleep(150) dauerte 150 ms
SleepNs(50 ms) dauerte 50 ms

Für Messungen ist CLOCK_MONOTONIC zwingend: CLOCK_REALTIME kann durch NTP oder eine Zeitumstellung springen und dann negative Differenzen liefern.

Periodischer Kernel-Timer

import std.time;

fn main(): int64 {
    // Wiederholender Kernel-Timer: alle 100 ms
    var fd: int64 := TimerFdCreate(CLOCK_MONOTONIC, 0);
    if (fd < 0) { PrintLn("timerfd nicht verfuegbar"); return 1; }
    TimerFdSetTime(fd, 100 * NS_PER_MS, 100 * NS_PER_MS);
    PrintLn("Timer laeuft, erster Ablauf in ", IntToStr(TimerFdGetTime(fd) / NS_PER_MS), " ms");

    var start: int64 := GetTimeNs(CLOCK_MONOTONIC);
    var i: int64 := 0;
    while (i < 3) {
        var n: int64 := TimerFdRead(fd);           // blockiert bis zum Ablauf
        var t: int64 := (GetTimeNs(CLOCK_MONOTONIC) - start) / NS_PER_MS;
        PrintLn("  Tick ", IntToStr(i + 1), ": ", IntToStr(n), " Ablauf nach ", IntToStr(t), " ms");
        i := i + 1;
    }
    return 0;
}

Ausgabe:

Timer laeuft, erster Ablauf in 99 ms
  Tick 1: 1 Ablauf nach 99 ms
  Tick 2: 1 Ablauf nach 199 ms
  Tick 3: 1 Ablauf nach 300 ms

Die Takte bleiben auf dem 100-ms-Raster — der Kernel rechnet vom ursprünglichen Startzeitpunkt aus, nicht vom letzten Lesen. Mit TFD_NONBLOCK und std.net.epoll lässt sich derselbe fd in eine Ereignisschleife neben Netzwerkverbindungen hängen.


Fallstricke

Geprüft mit lyxc 1.0.21A.

Die Einheit steht im Namen

Sleep(MS_SECOND);             // eine Sekunde
Sleep(MS_SECOND / 10);        // 100 ms  — gemessen: 100 ms
SleepNs(1 * NS_PER_SEC);      // dieselbe Sekunde in Nanosekunden
SleepNs(500 * NS_PER_MS);     // eine halbe Sekunde

Vor dem Fix von #1465 hießen die Konstanten SECOND, MINUTE, HOUR und DAY und standen für MikrosekundenSleep(SECOND) wartete deshalb gut 16 Minuten statt einer Sekunde. Die alten Namen gibt es nicht mehr; Code, der sie verwendet, meldet jetzt undefined symbol statt still zu hängen.

ApplyTimeZone kennt keine Sommerzeit

ApplyTimeZone addiert schlicht den Offset. Welche Zone gerade gilt, muss der Aufrufer selbst entscheiden — auch ZoneByCode liefert nur feste Versätze, keine Umschaltautomatik: Standard- und Sommerzeit haben eigene Kürzel (CET/CEST). Es gibt keine Zeitzonendatenbank und keine Auswertung von TZ oder /etc/localtime.

Für Protokolle und Dateiformate ist das kein Nachteil: dort schreibt man ohnehin UTC. Erst bei der Anzeige für Menschen braucht es die richtige Zone, und die muss von außen kommen.

Mitgeliefert sind seit #1600 alle 117 gebräuchlichen Zonen: ZoneByCode(„NPT“c) liefert die Zone zum Kürzel, ZoneCodeKnown sagt, ob es das Kürzel gibt, ZoneMake(name, offset) baut eine eigene. Dazu 33 Versatz-Konstanten (TZ_OFF_PLUS_0545 = 20700 usw.), die elf fehlenden Sommerzeiten (ZoneEest, ZoneNzdt, …) und eindeutig benannte Funktionen für die drei doppelt belegten Kürzel (ZoneIndia/ZoneIsrael, ZoneGulf/ZoneSouthGeorgia, ZoneIndochina/ZoneChagos). Die vollständige Liste steht in der Zeitzonen-Tabelle.

Zweiter Punkt: Das Ergebnis von ApplyTimeZone ist wieder ein datetime — also formal Unix-Sekunden, tatsächlich aber verschobene Ortszeit. Wer diesen Wert weiterreicht, transportiert eine Zeit ohne Zonenangabe. Deshalb erst unmittelbar vor der Ausgabe umrechnen.

Die Typen schützen nicht

date, time, datetime und timestamp sind allesamt int64. Ein datetime (Unix-Sekunden, derzeit ~1,79 Milliarden) an YearFromDate übergeben — das eine Tageszahl erwartet — liefert kein Fehlersignal, sondern ein Jahr in ferner Zukunft. Die Bedeutung steckt allein in der Benennung.

Sleep kann vorzeitig enden

Ein Signal unterbricht nanosleep. Sleep gibt dann einen negativen Wert zurück und die restliche Zeit ist nicht abgewartet. Wer eine Mindestwartezeit braucht, prüft den Rückgabewert und wiederholt — oder nimmt SleepUntil mit einem absoluten Zielzeitpunkt, das sich gefahrlos wiederholen lässt.

Kein Zeitformat, kein Parser

Die Unit erzeugt keine Zeichenketten und liest keine. Für ISO-8601, RFC 2822 oder eigene Formate ist std.datetime zuständig.


Verwandte Units

  • std.datetime — Zeitangaben formatieren und einlesen
  • std.net.epolltimerfd in einer Ereignisschleife überwachen
  • std.os — Prozess- und Systeminformationen