Zeitrechnungen

Martin Kompf

Auf welchen Wochentag fiel der 1. Februar? Wieviele Tage sind es noch bis Weihnachten? Die Standard C Library enthält alle zur Berechnung notwendigen Routinen.

Die zur Datumsberechnung erforderlichen Routinen und Typvereinbarungen sind im Header <time.h> definiert. Dabei arbeitet die C Library mit zwei unterschiedlichen Repräsentationen von Datum und Uhrzeit: Zum einen lässt sich jede Zeit als Anzahl der Sekunden ausdrücken, die seit einer bestimmten Startzeit vergangen sind. Als Startzeit wird auf den meisten Unix und Windows Systemen der 1. Januar 1970 0:00 Uhr verwendet. Dieser Zeitzähler kann in einer Variablen vom Typ time_t gespeichert werden.

Zum anderen gibt es zur Darstellung des Datums den Datentyp struct tm. Diese Struktur enthält Datum und Uhrzeit in einer für den Menschen besser auswertbaren Form. Die Strukturelemente bedeuten im einzelnen:

Aufbau von struct tm

Name Bedeutung Bereich
tm_sec Sekunden nach der vollen Minute 0..61
tm_min Minuten nach der vollen Stunde 0..59
tm_hour Stunden seit Mitternacht 0..23
tm_mday Tag im Monat 1..31
tm_mon Monate seit Januar 0..11
tm_year Jahre seit 1900 >= 0
tm_wday Tage seit Sonntag 0..6
tm_yday Tage seit dem 1. Januar 0..365
tm_isdst Ist Sommerzeit aktiv? >0 ja; 0 nein; <0 unbekannt

Konvertierungen

Zum Konvertieren zwischen den beiden Darstellungsformen gibt es drei Funktionen: gmtime und localtime bekommen als Parameter einen Zeiger auf eine Variable vom Typ time_t und liefern einen Zeiger auf struct tm zurück. Die beiden Funktionen unterscheiden sich darin, dass gmtime die Konvertierung auf der Basis der Greenwich mean time (GMT) vornimmt, während localtime zur Berechnung die lokale Zeitzone und die Sommerzeit berücksichtigt.

Den entgegengesetzten Weg schlägt mktime ein: Sie bekommt einen Zeiger auf struct tm und konvertiert das darin enthaltene Datum und die Uhrzeit in einen Sekundenwert vom Typ time_t. Interessant dabei ist, dass mktime mit unrichtigen oder unvollständigen Angaben in struct tm umgehen kann. So wird zum Beispiel die (falsche) Datumsangabe 31. September in 1. Oktober korrigiert. Zum anderen werden die Felder tm_wday und tm_yday durch mktime gesetzt.

Bestimmung des Wochentages

Mittels dieser Funktionalität lässt sich der Wochentag für ein beliebiges Datum, welches allerdings nach dem 1. Janauar 1970 liegen muss, bestimmen:

#include <locale.h>
#include <stdio.h>
#include <time.h>
#include <string.h>

int main()
{
    time_t t1, t2;
    struct tm date1, date2;
    char msg[1024];

    /* setze Locale */
    if (NULL == setlocale( LC_ALL, "")) {
        printf( "unable to set locale\n");
    }

    /* Initialisiere struct tm mit dem 1.2.2001 */
    memset( &date1, 0, sizeof(date1));
    date1.tm_mday = 1;
    date1.tm_mon = 2 - 1; /* Februar */
    date1.tm_year = 2001 - 1900;

    /* Wochentag und Tag des Jahres bestimmen */
    mktime( &date1);

    strftime( msg, sizeof(msg), "Der %d.%m.%Y war ein %A.\n", &date1);
    printf( msg);
    strftime( msg, sizeof(msg), "Das war der %j. Tag des Jahres.\n", &date1);
    printf( msg);

    return 0;
}

Zur Ausgabe eines in struct tm stehenden Datums kann man sich - wie im Beispiel gezeigt - der Funktion strftime bedienen. Sie kann unter anderem Wochentage mit ihrem vollem Namen ausgeben; vorausgesetzt, dass die Locale richtig gesetzt ist, sogar in der jeweiligen Landessprache! Dabei verwendet strftime ähnlich wie sprintf bestimmte Kodierungen zur Ausgabesteurung:

Codes für strftime

Code Ersetzung
%a abgekürzter Wochentag
%A gesamter Wochentag
%b abgekürzter Monatsname
%B voller Monatsname
%c Lokale spezifisches Datum und Uhrzeit
%d Tag im Monat als ganze Zahl (1 - 31)
%H Stunde als ganze Zahl (00 - 23)
%I Stunde als ganze Zahl (01 - 12)
%j Tag im Jahr als ganze Zahl (001 - 366)
%m Monat als ganze Zahl (01 - 12)
%M Minute als ganze Zahl
%p AM oder PM
%S Sekunde als ganze Zahl
%U Wochennummer des aktuellen Jahres als ganze Zahl, beginnend mit dem ersten Sonntag als erster Tag der ersten Woche
%W Wochennummer des aktuellen Jahres als ganze Zahl, beginnend mit dem ersten Montag als erster Tag der ersten Woche
%w Wochentag als ganze Zahl, Sonntag beginnt mit 0
%x Lokale spezifisches Datum
%X Lokale spezifisches Uhrzeit
%y Jahr als ganze Zahl ohne Jahrhundert (00 - 99)
%Y Jahr als ganze Zahl mit Jahrhundert
%Z Zeitzone oder deren Abkürzung
%% Zeichen %

Zeitdifferenzen

Da mktime auch die Zahl der Sekunden seit dem 1. Januar 1970 zurückliefert, kann die zwischen zwei Terminen verstrichene Zeit in Sekunden nun durch Differenzbildung bestimmt werden. Wenn man dann noch weiss, dass ein Tag (ohne Berücksichtigung von Schaltsekunden) 24*60*60 = 86400 Sekunden lang ist, kann die Zeitdifferenz auch in Tagen angegeben werden. Zur Differenzbildung muss allerdings die Funktion difftime verwendet werden, da der Programmierer nicht weiss, von welchem konkreten Typ time_t ist.

Im abschließenden Beispiel wird auf diese Weise die Anzahl der bis Weihnachten verbleibenden Tage berechnet. Das momentane Datum wird dabei mittels der Funktion time bestimmt:

    /* ... */

    time_t t1, t2;
    struct tm date1, date2;
    char msg[1024];

    /* Differenz in Tagen von heute bis Weihnachten bestimmen */
    time( &t1); /* aktuelles Datum in Sekunden */

    /* Konvertiere Sekunden nach struct tm */
    date2 = *localtime( &t1);
    /* Überschreibe Tag und Monat */
    date2.tm_mday = 24;
    date2.tm_mon = 12 - 1; /* Dezember */

    /* Konvertiere wieder von struct tm in Sekunden zurück */
    t2 = mktime( &date2);

    strftime( msg, sizeof(msg), "Heute ist der %d.%m.%Y.\n", localtime(&t1));
    printf( msg);
    printf( "Es sind noch %3.0f Tage bis Weihnachten!\n",
            difftime(t2, t1) / (24*60*60));
    /* ein Tag = 24 Stunden je 60 Minuten je 60 Sekunden */

    /* ... */