====== std.time — Zeit, Datum und Timer ======
→ [[lyx_-_programmiersprache:units|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''
Die frühere Einheitenfalle (#1465) ist **behoben**: Die Zeitkonstanten tragen ihre Einheit jetzt im Namen — ''US_SECOND'' … ''US_DAY'' für Mikrosekunden, ''MS_SECOND'' … ''MS_DAY'' für Millisekunden. ''Sleep(MS_SECOND)'' wartet damit tatsächlich eine Sekunde.
===== 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 [[lyx_-_programmiersprache:units:net:epoll|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 **Mikrosekunden** — ''Sleep(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 [[https://github.com/SEOLizer/LyX-Compiler/issues/1600|#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 [[lyx_-_programmiersprache:units:time:zeitzonen|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 [[lyx_-_programmiersprache:units:datetime|std.datetime]] zuständig.
----
===== Verwandte Units =====
* [[lyx_-_programmiersprache:units:datetime|std.datetime]] — Zeitangaben formatieren und einlesen
* [[lyx_-_programmiersprache:units:net:epoll|std.net.epoll]] — ''timerfd'' in einer Ereignisschleife überwachen
* [[lyx_-_programmiersprache:units:os|std.os]] — Prozess- und Systeminformationen