Die für die Software-Uhr beschriebenen Mechanismen sind komplett im Unix kernel implementiert. Diese Uhr stellt die Zeit als die Anzahl der Sekunden dar, die seit einem bestimmten, in POSIX festgelegten Zeitpunkt vergangen sind. Damit ist diese Uhr komplett unabhängig von Zeitzonen und sollte auf jedem Unix-Rechner den gleichen Wert haben. Außerdem ist mit Zeiten dieser Uhr leicht umzugehen, da eine Zeitangabe einfach nur aus einer ganzen Zahl besteht, und nicht aus mehreren Angaben für Jahr, Monat, Tag, Stunde, Minute und Sekunde.
Auch in normalen Programmen können Zeiten natürlich in diesem Format dargestellt und bearbeitet werden, wie sie sie vom kernel bekommen. Für die Ein- und Ausgabe von Zeiten vom bzw. zum Benutzer sollten die Zeitangaben normalerweise in die gängige Darstellung gebracht werden und zwar abhängig von der lokalen Zeitzone.
Dieses Umrechnen kann sehr aufwendig werden, wenn die Regeln für die lokale Zeitzone berücksichtigt werden sollen und weil verschieden lange Monate und Schaltjahre berücksichtigt werden müssen.
Damit diese Routinen nicht für jedes Programm, das mit Zeiten zu tun hat, neu implementiert werden müssen, sind eine Reihe von Routinen schon in der Standard C library enthalten. Diese Routinen laufen also komplett im user space ab, d.h. der Unix kernel hat hiermit absolut nichts mehr zu tun. Außer dem Typ
typedef long time_t;mit dem die Zeiten in der Systemzeit des kernels dargestellt werden, wird für die Routinen der C library ein neuer Typ definiert:
typedef struct tm { int tm_sec; /* seconds 0..61 */ int tm_min; /* minutes 0..59 */ int tm_hour; /* hours 0..23 */ int tm_mday; /* day of the month 1..31 */ int tm_mon; /* month 0..11 */ int tm_year; /* year - 1900*/ int tm_wday; /* day of the week 0..6 */ int tm_yday; /* day in the year 0..365 */ int tm_isdst; /* daylight saving time */ };
Die C library stellt nun folgende Routinen zur Verfügung:
struct tm *gmtime(const time_t *tp); struct tm *localtime(const time_t *tp);Diese beiden Funktionen wandeln eine Zeitangabe in Form eines
time_t
in Datum und Uhrzeit in Form eines struct
tm
um. gmtime()
liefert UTC, während
localtime()
die Zeit der lokalen Zeitzone berechnet, wenn
die lokale Zeitzone richtig eingestellt wurde.
Die beiden Funktionen
char *asctime(const struct tm *tp); char *ctime(const time_t *tp);erwarten eine Zeitangabe in Form eines
time_t
bzw. struct tm
und liefern die entsprechende Darstellung
der Zeit als String der Form
Wed Jun 30 21:49:08 1993\nDie Funktion
ctime()
wandelt dazu zunächst den
time_t
in die Zeit der lokalen Zeitzone um und hängt
damit natürlich auch von der korrekten Einstellung der Zeitzone
ab. ctime(tp)
ist äquivalent zu
asctime(localtime(tp))
.
Die Funktion
time_t mktime(struct tm *tp);ist die inverse Funktion zu
localtime()
. Sie erwartet
eine Zeitangabe der eingestellten lokalen Zeitzone und berechnet
daraus die Zeitangabe in Form eines time_t
.
Eine weitere interessante Funktion ist
size_t strftime(const char *s, size_t maxsize, const char *format, const struct tm *tp);Diese Funktion erwartet einen Zeiger auf eine als
struct
tm
angegebene Zeit und konvertiert sie in einen String. Im
Gegensatz zu asctime
(3) und ctime
(3) kann
bei strftime
(3) jedoch ein beliebiges Format,
ähnlich wie bei *printf
(3), angegeben werden. Das
Format von asctime
(3) und ctime
(3)
erhält man mit der Format-Spezifikation
"%a %b %d %H:%M:%S %Y\n"während die Spezifikation
"%Y-%m-%d %H:%M:%S"die Zeit im ISO 8601-Format liefert.
Beispiele für die Verwendung der hier beschriebenen Funktionen sind in hier zu finden. Es handelt sich um ein paar kleine Programme
what-time-is-it
liefert die aktuelle Zeit als
time_t
print-utc
und print-localtime
wandeln
eine beliebige Zeitangabe als time_t
in UTC oder in die
Repräsentation der lokalen Zeitzone um
make-time
wandelt eine Zeitangabe der lokalen
Zeitzone der Form YYYY-MM-DD HH:MM:SS in einen time_t
um.
ex
und
time-of-unix
, die ein paar Beispiele mit diesen kleinen
Programmen zeigen.
Ein minimales date
-Kommando habe ich auch noch auf die
Schnelle implementiert, mit einem kleinen Beispiel-Script
date-ex
. Der Programmcode date.c
ist
ausführlich kommentiert (hat am meisten Zeit gekostet :-) und
ist damit kein real-world-program mehr :-)
Die Umrechnung von Zeitdarstellungen in Form eines
time_t
und lokaler Zeitzonen ist natürlich von der
richtigen Einstellung der Zeitzone abhängig. Außerdem
müssen die für die Umrechnung verantwortlichen Funktionen
Informationen über die lokale Zeitzone und Dinge wie Sommerzeit
und Winterzeit (Daylight Saving Time in den USA) haben.
Eine Möglichkeit, die auch auf manchen Unixen noch verwendet wird, obwohl sie zu unflexibel ist, besteht darin, diese Informationen in die environment-Variable TZ (timezone) zu schreiben.
In der einfachsten Form sieht das dann so aus:
TZ=PST8PDT; export TZfür eine zur Bourne shell (
sh
) kompatible shell
(z.B. bash
, zsh
, ksh
) bzw.
setenv TZ PST8PDTin der C shell (
csh
) und deren Derivate
(z.B. tcsh
).
Damit werden die Namen der Standardzeit (hier PST für Pacific
Standard Time), der Offset gegenüber UTC (hier 8 Stunden), sowie
der Name einer alternativen Zeitrepräsentation (hier PDT für
Pacific Daylight Saving Time) angegeben.
Es wird per default davon ausgegangen, daß die alternative Zeit um eine Stunde gegenüber der Standardzeit verschoben ist, und daß sie immer zwischen dem ersten Sonntag im April und dem letzten Sonntag im Oktober, jeweils 2:00:00 Uhr Standardzeit gilt. Das sind nämlich die Regeln für die Daylight Saving Time in den USA.
In der Mitteleuropäischen Zeit gelten jedoch andere Regeln,
die man auch in der Variable TZ
formulieren kann:
TZ=CET-1CEST,M3.5.0/2:00,M10.5.0/3:00Diese Angabe bedeutet, daß
Diese Methode hat aber immer noch den Nachteil, daß sie
Änderungen der Regeln nicht berücksichtigen kann. So galt
z.B. vor 1996 in der Mitteleuropäischen Zeit, daß die
Sommerzeit am letzten Sonntag im September endet. D.h. time
stamps, die älter sind als 1996, z.B. modification dates an einer
Datei, werden evtl. nicht richtig umgewandelt. Hier ein kleines
Beispiel (mit dem GNU ls, das die --full-time
option
kennt). Ich habe eine Datei, die am 17. Oktober 1993 um 13:04:29 UTC
erzeugt wurde:
$ TZ=UTC ls -l --full-time bib -rw-r--r-- 1 urs users 11370 Sun Oct 17 13:04:29 1993 bibMit der Zeitzonenbeschreibung von oben mit den Regeln ab 1996 wird die Zeit aber falsch anzeigt:
$ TZ=CET-1CEST,M3.5.0/2:00,M10.5.0/3:00 ls -l --full-time bib -rw-r--r-- 1 urs users 11370 Sun Oct 17 15:04:29 1993 bibAm 17. Oktober 1993 war die Sommerzeit des Jahres 1993 schon vorbei, so daß der offset gegenüber UTC nur 1 Stunde war. Hier wird aber Sommerzeit und damit ein Unterschied von 2 Stunden angenommen.
Außerdem gab es in verschiedenen Gebieten während des zweiten Weltkriegs und in den ersten Jahren danach ganz unregelmäßige Zeitzonen. Informationen zu Sommerzeiten in Deutschland sind bei der PTB zu finden: vor 1950 und nach 1980.
In den USA gilt während der Präsidentschaftswahlen im ganzen Land einheitlich die "US Presidential Election Time", damit'se da alle gleichzeitig vor der Glotze hocken, und es gibt noch 'ne Menge mehr Unregelmäßigkeiten.
Der bessere Weg ist also, die Informationen über solche Umrechnungen in Dateien abzulegen, in einem Format, das alle Unregelmäßigkeiten und Änderungen leicht erfassen kann.
Genau das macht die Implementierung der oben beschriebenen Funktionen in der library von Arthur David Olson, die auch in der libc-5 und in der glibc-2 (aka libc-6) für Linux übernommen wurde.
Für jede Zeitzone existiert eine eigene Datei im directory
/usr/share/zoneinfo
(bei glibc-2) bzw. in
/usr/lib/zoneinfo
(bei libc-5). In diesen Dateien
können alle oben genannten Regeln mit beliebig häufigen
Änderungen beschrieben und von den library-Funktionen benutzt
werden.
Welche dieser Dateien benutzt wird, kann mit der
environment-Variable TZ
festgelegt werden.
TZ
braucht dazu einfach nur auf den Namen der zu
benutzenden Datei, relativ zu /usr/share/zoneinfo,
gesetzt werden. Mit
TZ=CET datewird das Datum und Uhrzeit in Mitteleuropäischer Zeit, die in
/usr/share/zoneinfo/CET
beschrieben ist, angezeigt. Mit
TZ=US/Pacific date TZ=Iran datewird die Zeit an der Westküste der USA (beschrieben in
/usr/share/zoneinfo/US/Pacific
) bzw. im Iran
(/usr/share/zoneinfo/Iran
) angezeigt. Genauso gilt das
natürlich für alle anderen tools, die Zeiten anzeigen oder
einlesen, wie z.B. ls -l
, touch
,
at
, tar tv
, emacs
(in der mode
line), etc. Möchte man also beim Hacken oder mail Schreiben
immer sehen, wie spät es gerade in Japan ist, kein Problem:
TZ=Japan emacs
.
Um alle Zeitangaben in der eigenen, lokalen Zeitzone passend
angezeigt zu bekommen, braucht man aber nicht für alle Prozesse
(also z.B. in ~/.bash_login) die Variable TZ
zu setzen.
Ist TZ
nämlich nicht gesetzt, wird
/etc/localtime
(bzw. /usr/lib/zoneinfo/localtime
bei libc-5) gelesen.
Daher muß nur ein symbolischer link von der gewünschten
Zeitzonenbeschreibung in /usr/share/zoneinfo
nach
/etc/localtime
gesetzt zu werden (bzw. entsprechend
für libc-5), also z.B. durch
ln -s /usr/share/zoneinfo/CET /etc/localtimeIn den Konfigurationstools der Linux-Distributionen kann man die gewünschte Zeitzone meist in einem Menü eintragen, und das Konfigurationstool setzt dann genau diesen symbolischen link.
(Es kann unter Umständen auch sinnvoll sein, die Datei
zu kopieren statt einen symlink zu setzen, damit die Information auch
schon beim booten zugreifbar sind, wenn /usr
noch nicht
gemounted ist.)
So, neugierig geworden, wie denn nun unsere Regeln für
Sommerzeit tatsächlich definiert sind? ... less
/usr/share/zoneinfo/CET
... )-: Sieht ja ziemlich unleserlich
aus. Wer hat das bloß geschrieben?
Einfache Antwort: zic
hat das geschrieben.
zic
ist der zone info compiler, der die Beschreibungen
der Regeln in einer bestimmten Syntax liest und die zoneinfo files in
/usr/share/zoneinfo
daraus erzeugt. Die Beschreibungen,
die zic
einliest, sind z.B. in den glibc-sourcen
enthalten, bei mir also in
/usr/src/glibc-2.1.3/timezone/
. Sie sind mit etlichen
Kommentaren zu Quellen der Zeitinformationen versehen. Ist wirklich
interessant zu lesen, besonders der Abschnitt über die
Festlegungen für Great Britain, ... die spinnen die Briten.
Das von zic
erzeugte file kann man sich auch noch
nachträglich ansehen, dann natürlich ohne die Kommentare und
die genaue Formulierung der Regeln. Das geht mit zdump
,
z.B. durch den Aufruf zdump -v CET
.
Wer jetzt tatsächlich mal etwas in
/usr/share/zoneinfo
herumgeguckt hat, wird dort evtl. (je
nach Installation) zwei subdirectories entdeckt haben:
posix/
und right/
.
Im directory posix/
sind nocheinmal alle Dateien aus
/usr/share/zoneinfo/
enthalten. Sie enthalten die eben
beschrieben Informationen, wobei
Schaltsekunden nach POSIX nicht
berücksichtigt werden. Im directory right/
sind
Dateien desselben Namens zu finden. Sie enthalten aber Informationen,
wann Schaltsekunden in UTC auftreten, und diese sind natürlich
auch bei den Angaben über Zeitpunkte des Wechsels von
Sommer-/Winterzeit berücksichtigt. Den Namen right/
halte ich zur Abgrenzung zu posix/
für richtig
gewählt :-)
Man kann auch die Dateien im directory right/
benutzen, z.B. mit TZ=right/CET date
und wird dann den
Unterschied von zur Zeit 22 Sekunden sehen (gut zu sehen ist der
Unterschied mit date; TZ=right/CET date
), falls die
zoneinfo files aktuell genug sind (wie gesagt werden Schaltsekunden erst ca. ein
halbes Jahr im voraus festgelegt). Das alles nützt aber wenig,
wenn man nicht mit der restlichen Welt inkompatibel sein will,
z.B. bei time stamps in tar files, weil die restliche Unix-Welt die
brain-damaged POSIX time stamps verwendet.