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