Knihovna time.h

Knihovna <time.h> umožňuje zjistit aktuální čas. Je třeba si uvědomit, že čas je pojem relativní. Ve vašem počítači existuje jednak čas lokální a také čas globální. Krom toho existuje čas letní a zimní, jejichž začátek se v průběhu času měnil a v různých státech je nastaven různě dodnes.
Globální čas se dříve označoval jako GMT (Greenwich Mean Time), dnes jako UTC (Coordinated Universal Time). Je otázkou, jak je čas na vašem počítači nastaven. Správně byste měli mít nastaven čas globální a lokální čas přepočítávat. Mnohde je to však naopak.

Smutnou pravdou je, že mnoho potřebných informací o datu a času vám knihovna <time.h> nepoví. Buď si budete muset potřebné výpočty sestavit sami, nebo se budete muset poohlédnout po nějaké „nestandardní“ knihovně. Tak třeba funkci na zjištění přestupného roku si můžete napsat takto:

/*---------------------------------------*/
/* funkce vracejici 1 pro prestupny rok, */
/* jinak 0.                              */
int prestupny_rok(int rok)
{
     return (((rok % 4 == 0) && (rok % 100 != 0)) || (rok % 400 == 0));
}
/*---------------------------------------*/

Uchovávání času

Aby bylo možné čas uchovávat v co možná nejmenším počtu bitů, zaznamenává se jako počet sekund od určitého data. Například v Unixových systémech je to od 1. 1. 1970, v MS-DOSu je to 1. 1. 1980 atp. V dnešní době je to víceméně zátěž z historických důvodů. S časem uloženým v sekundách se pracuje pomocí předdefinovaného datového typu time_t. Je to celočíselný typ, například v Linuxu je to typ long int. Vzhledem k tomu, že váš program může být přeložen ve více systémech, nemůžete podle tohoto čísla vědět, jaký že je to datum a čas (nevíte, co je počáteční datum). Pokud však v programu zaznamenáte dvě takovéto čísla a odečtete je od sebe, zjistíte, kolik sekund mezi jejich zaznamenáním uběhlo. Takovéto jednoduché odečtení však není dle normy ANSI, která netvrdí, že time_t musí být celé číslo. Proto používejte funkci difftime(), která je k tomu určena. (Kvůli přenositelnosti na systémy, kde time_t není definován jako celé číslo. Ne že bych nějaký takový systém znal.)

Rozumněji se pracuje s dalším předdefinovaným typem – tentokrát se jedná o strukturu – tm. Její definice vypadá takto:

struct tm {
      int tm_sec;                /* vteřiny 0-60 */
      int tm_min;                /* minuty 0-59  */
      int tm_hour;               /* hodiny 0-23  */
      int tm_mday;               /* dny v měsíci 1-31 */
      int tm_mon;                /* měsíc 0-11 */
      int tm_year;               /* rok od 1900 */
      int tm_wday;               /* den v týdnu 0-6, 0 je nedele */
      int tm_yday;               /* den v roce 0-365 */
      int tm_isdst;              /* příznak letního času*/
};

Možná vás překvapí, že vteřin může být 61, ale to vážně není moje vina. Prostě to tak bylo definováno (existují tzv. přestupné vteřiny), tak se s tím smiřte. Příznak tm_isdst je nulový, pokud není letní čas a záporný, pokud není informace o času dostupná. Pokud je letní čas, je kladný.

Výčet funkcí

char *asctime(const struct tm *timeptr);
Převede strukturu tm na řetězec ("Wed Jun 30 21:49:08 1993\n"). Návratovou hodnotou je ukazatel na tento řetězec.
char *ctime(const time_t *timep);
Vrací to samé, jako asctime(localtime(timep));
double difftime(time_t time1, time_t time0);
Vrací rozdíl mezi časem time0 a time1 ve vteřinách.
struct tm *gmtime(const time_t *timep);
Převede čas uložený v timep (pomocí vteřin) na strukturu tm a to v čase UTC (GMT). Vrací ukazatel na tuto strukturu.
struct tm *localtime(const time_t *timep);
Jako funkce gmtime(), ale vrací lokální čas (nikoliv UTC).
time_t mktime(struct tm *timeptr);
Převádí strukturu na kterou odkazuje timeptr do počtu vteřin, jež jsou návratovou hodnotou.
time_t time(time_t *t);
Vrací počet sekund reprezentující čas (v Unixech počet sekund od 1.1.1970). Tuto hodnotu také uloží tam, kam ukazuje ukazatel t, pokud nemá hodnotu NULL). Všimněte si, že je to jediná funkce, která vám vrátí aktuální čas.
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
Tato funkce převádí strukturu tm na řetězec. Tento řetězec bude uložen do pole, na které ukazuje ukazatel s. Maximálně však max znaků. Co bude řetězec obsahovat, to je dáno formátovacím řetězcem format. Co může tento formátovací řetězec obsahovat najdete v tabulce níže.
char *strptime(const char *s, const char *format, struct tm *tm);
Toto je inverzní funkce k strftime(). Z textu s, ve kterém je zapsán datum ve formátu format načte čas a uloží jej do tm. Vrací ukazatel za místo, kde skončil s konverzí s na čas, nebo NULL v případě selhání.
Nejedná se o standardní funkci, proto je v příkladu níže její použití povoleno jen když je definováno makro _XOPEN_SOURCE.

V tabulce jsou formátovací znaky v řetězci format funkce strftime().

Formátovací řetězce pro funkci strftime()
%a zkrácený název dne v týdnu
%A úplný název dne v týdnu
%b zkrácený název měsíce
%B úplný název měsíce
%c datum a čas
%d den v měsíci 01-31
%H hodina 00-23
%I hodina ve 12-ti hodinovém formátu 01-12
%j den v roce 001-366
%m měsíc 01-12
%M minuty 00-59
%p am pro dopoledne, pm pro odpoledne
%S vteřiny 00-61
%u den v týdnu 1-7, 1 = pondělí
%U týden v roce 01-53 (neděle je prvním dnem v týdnu)
%V týden v roce 01-53 (pondělí je prvním dnem v týdnu)
%w den v týdnu 0-6 (0 = neděle)
%x datum v lokálním formátu
%X čas v lokálním formátu
%y poslední dvě číslice roku 00-99
%Y rok
%Z název časového pásma

Kromě těchto formátovacích znaků může formátovací řetězec obsahovat jakékoliv další písmena (včetně např. '%%' pro '%').

Příklad použití funkcí I (sportka)

V prvním příkladu ukáži, jak lze čas využít k inicializaci náhodných čísel (viz. funkce rand() a srand()). V příkladu simuluji losování sportky. Je to spíš ukázka práce s náhodnými čísly než s časem. Ukázka práce s časem je v dalším příkladě.

/*------------------------------------------------*/
/* c20/sportka.c                                  */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h> /* zde jsou definovany funkce rand() a srand() */
#include <stddef.h>
#include <time.h>

#define CENA 16     /* korun za vsazeni 1 sloupce */

/**
 * Funkce uhodnuto zjisti, kolik cisel z pole cisla[6] je
 * stejnych jako v poli prvnich 6 cisel v poli tah[7].
 * Posledni cislo v tomto poli je dodatkove!
 */


unsigned int uhodnuto(unsigned int *cisla, unsigned int *tah)
{
    size_t i, j;
    unsigned int vyhra = 0;

    for (j = 0; j < 6; j++)
        for (i = 0; i < 6; i++)
            if (cisla[j] == tah[i]) {
                vyhra++;        /* pocet uhadnutych cisel */
                break;          /* ukonci vnitrni cyklus */
            }
    return vyhra;
}

/**
 * Funkce vyhra_sportky urci na zaklade uhodnutych cisel
 * co jste vyhrali.
 * Pokud jste uhodli 5 cisel a 6te je stejne jako
 * dodatkove, pak mate "druhou"
 */

unsigned int vyhra_sportky(unsigned int uhodnute, unsigned int *cisla,
                           unsigned int *tah)
{
    size_t i;
    unsigned int vyhra = 0;
    switch (uhodnute) {
    case 6:
        vyhra = 1;              /* mate prvni */
        break;
    case 5:
        vyhra = 3;              /* mate treti */
        /* ale mozna mame druhou */
        for (i = 0; i < 6; i++)
            if (cisla[i] == tah[6]) {
                vyhra = 2;      /* mame druhou */
                break;
            }
        break;
    case 4:
        vyhra = 4;
        break;
    case 3:
        vyhra = 5;              /* mate patou */
        break;
    default:    /* to je tu trosku zbytecne,
                   protoze vyhra = 0 je jiz vyse, ale rozhodne to neni
                   na skodu, uz kvuli srozumitelnosti kodu */

        vyhra = 0; /* nic jste nevyhrali */
    }
    return vyhra;
}

/**
 * tato funkce urci, kolik penez jste vyhrali
 */

unsigned long penez(unsigned int vyhra)
{
    switch (vyhra) {
    case 5:
        return rand() % 50 + 30;
    case 4:
        return rand() % 500 + 250;
    case 3:
        return rand() % 5000 + 1000;
    case 2:
        return rand() % 500000 + 50000;
    case 1:
        return (unsigned long) ((rand() % 1000000) + 1) * (rand() % 10)
                + 1000000;
    default:
        return 0;
    }
}

/**
* kontroluje, zda cislo exituje v poli zadane delky
*/

int existuje(unsigned int cislo, unsigned int *pole, unsigned int delka)
{
    size_t i;
    for (i = 0; i < delka; i++)
        if (cislo == pole[i])
            return 1;
    return 0; /* cislo se nenaslo */
}

int main(void)
{
    size_t i;
    unsigned int  cisla[6], tah[7], vyhra, uhodnute, finance, vyhrano;
    unsigned int chci_vyhrat;
    unsigned long vyhry[] = { 0, 0, 0, 0, 0, 0 }; /* nic,prvni,druha ...
      vyhra "nic" bude zrejme velmi casto, proto je pole typu long int */

    unsigned long cyklus = 0;
    time_t cas1, cas2;

    (void) time(&cas1);         /* navratovou hodnotu ignoruji */
    /* vystupni hodnotu musim z formatu time_t
       zkonvertovat na typ unsigned int */

    srand((unsigned int) time(NULL));  

    /* inicializace */
    vyhrano = 0;

    printf("Zadejte poradi, ktere chcete vyhrat (0,1,2,3,4,5): ");
    scanf("%u", &chci_vyhrat);

    /* losovani vsazenych cisel do sportky */
    for (i = 0; i < 6; i++) {
        cisla[i] = (rand() % 49) + 1;
        if (existuje(cisla[i], cisla, i))
            i--;        /* cislo jiz bylo vylosovano,
                           bude se losovat znovu */

    }

    /* tisk */
    printf("Vsazena cisla:");
    for (i = 0; i < 6; i++) {
        printf("%3u", cisla[i]);
    }
    printf("|\n");

    printf("Cyklus  :%-23s| Vyhrano   Vyhry:    1    2    3    4    5  0\n",
           " Vylosovane Cisla");

    /* cyklus, dokud nevyhraji co chci */
    do {
        /* losovani sportky */
        for (i = 0; i < 7; i++) {
            tah[i] = (rand() % 49) + 1;
            if (existuje(tah[i], tah, i))
                i--;            /* cislo jiz bylo vylosovano,
                                   bude se losovat znovu */

        }

        /* tisk vysledku */
        printf("\r%8ld:", ++cyklus);

        for (i = 0; i < 6; i++)
            printf("%3u", tah[i]);
        printf(" /%3u", tah[6]);        /* dodatkove cislo */

        /* pocet uhodnutych cisel */
        uhodnute = uhodnuto(cisla, tah);      
        /* prvni, druha atp. */
        vyhra = vyhra_sportky(uhodnute, cisla, tah);
        finance = penez(vyhra);
        vyhry[vyhra] += 1;      /* vyhry[vyhra]++; */

        vyhrano += finance;
        printf("|%10ld[Kc]   ", (unsigned long) vyhrano - cyklus * CENA);

        for (i = 1; i < 6; i++) {
            printf(" %4ld", vyhry[i]);
        }
        printf(" %2ld", vyhry[0]);

        /* funkce fflush() zajisti, ze program nebude pokracovat dale,
         * dokud se vystup skutecne nezobrazi v stdout (tj. na obrazovce) */

        fflush(stdout);
        if (!chci_vyhrat) {
            break; /* nechci nic vyhrat, tj. chci_vyhrat == 0 */
        }
    } while ((chci_vyhrat < vyhra) || (vyhra == 0));

    cas2 = time(NULL); /* ziskame totez jako (void) time(&cas2); */
    printf("\nZiskal jste %i - %i*%ld = %ld Kc\n", vyhrano, CENA, cyklus,
           vyhrano - CENA * cyklus);
    printf("Cele to trvalo %lu vterin.\n",
           (unsigned long) difftime(cas2, cas1));

    return 0;
}

/*------------------------------------------------*/
Visual Studio

Makro _CRT_SECURE_NO_WARNINGS je tu kvůli funkci scanf(), viz scanf().

Možný výstup:

Zadejte poradi, ktere chcete vyhrat (0,1,2,3,4,5): 1
Vsazena cisla:  3 11 15 13 42 49|
Cyklus  : Vylosovane Cisla      | Vyhrano   Vyhry:     1     2     3     4     5     0
10370271: 15  3 49 13 42 11 / 30|-148969744[Kc]        1     1   175  9960 183054 10177080
Ziskal jste 16954592 - 16*10370271 = -148969744 Kc
Cele to trvalo 896 vterin.

Doufám, že vás tento příklad odnaučí sázet :).

Ještě si vás dovolím upozornit na jednu záludnost v programu. Týká se to velikosti datových typů. Vidíte, že například proměnná cyklus je typu long. Je to proto, protože při čekání na „první“ to může trvat velice dlouho. Hrozí tak, že kapacita této proměnné bude vyčerpána. Došlo by tak k „přetečení“ a to může vést k chybnému chování programu (například skončí dřív, než je „první“ vyhrána). To se týká také sčítání výhry …

Příklad použití funkcí II

Tento příklad už je poněkud klidnější a ukáže použití struktury tm.

/*------------------------------------------------*/
/* c20/tm1.c                                      */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <time.h>
#define MAX 80

int main(void)
{
    time_t cas;
    /* jestli pracujete se strukturou tm nebo ukazatelem
     * na tuto strukturu, to je vcelku jedno, ukazu oboji. */

    struct tm gtmcas;
    struct tm *ltmcas;
    char retezec[MAX];
    char *uch;

    cas = time(NULL);

    gtmcas = *(gmtime(&cas));
    ltmcas = localtime(&cas);

    /* vsimnete si, ze retezec vraceny funkcemi ctime a
     * asctime je jednak v anglictine a take na konci
     * ukoncen znakem '\n' (coz je v praxi tak trochu
     * neprijemne, ze). Texty LC a UTC  kvuli tomu
     * prejdou az na dalsi radek */

    printf("Je prave \"%s\" LC\n", ctime(&cas));
    printf("Je prave \"%s\" GMT\n", asctime(&gtmcas));
    printf("Je prave \"%s\" LC\n", asctime(ltmcas));

    strftime(retezec, MAX - 1, "Je prave %A %d.%m.%Y, %I:%M %p LC",
             ltmcas);
    printf("%s\n", retezec);
    printf("Tm(LC): %02i.%02i.%04i\n", ltmcas->tm_mday,
           ltmcas->tm_mon, ltmcas->tm_year + 1900);
    cas = (time_t) 0;
    printf("Pocatek vseho je %s", ctime(&cas));

#ifdef _XOPEN_SOURCE
    uch = strptime("22.06.2014 12:48 a konec",
             "%d.%m.%Y %H:%M", &gtmcas);
    if(uch != NULL) {
        strftime(retezec, MAX - 1,
                "strptime: %A %d.%m.%Y, %I:%M %p,",
                &gtmcas);
        printf("%s %s\n",retezec, uch);
    }
#endif

    return 0;
}

/*------------------------------------------------*/
Visual Studio

Makro _CRT_SECURE_NO_WARNINGS je tu kvůli funkci gmtime(), kterou považuje Visual Studio za nebezpečnou, viz scanf().

Možný výstup z programu:

Je prave "Fri Aug 29 15:31:04 2003
" LC
Je prave "Fri Aug 29 13:31:04 2003
" GMT
Je prave "Fri Aug 29 15:31:04 2003
" LC
Je prave Friday 29.08.2003, 03:31 PM LC
Tm(LC): 05.07.2003
Pocatek vseho je Thu Jan  1 01:00:00 1970
strptime: Sunday 22.06.2014, 12:48 PM,  a konec

Clock funkce

Všechny dosud zmíněné funkce jsou závislé na nastavení hodin v počítači. To může mít neblahé následky, zvláště při počítání časového intervalu, protože na hodiny se nedá spolehnout. Nastavený čas na hodinách počítače se může změnit při přechodu ze zimního na letní čas, automaticky při synchronizaci se serverem poskytujícím přesný čas, nebo ho může změnit sám uživatel počítače.

Teď si představte situaci, kdy dáte uživateli 5 minut na vyplnění nějakého testu. Spustíte test, uložíte si aktuální čas a pak kontrolujete, jestli je o 5 minut více. Když si uživatel po spuštění testu v počítači nastaví čas o hodinu zpět, má na vyplnení hodinu a 5 minut …

Nebo budete programovat hru a necháte tank jet rychlostí 10 pixelů za vteřinu. Během hry si počítač synchroniuzuje čas podle internetu a posune hodiny o 10 sec dopředu. A tank najednou poskočí o 100 pixelů …

Linux poskytuje funkce, které vracejí čas, který reálně uplynul od nějakého nespecifikovaného počátku.

Počátek může být okamžik, kdy jste spustili počítač, nebo kdy jste spustili program, který tyto funkce volá, nebo kdy jste zavolali první „clock“ funkci.

Asi nejdůležitější (a taky jediná, kterou vám tu představím) je funkce clock_gettime(). Ta vyplní strukturu struct timespec počtem sekund a nanosekund, které uplynuly od (onoho nespecifikovaného) počátku.

int clock_gettime(clockid_t clk_id, struct timespec *tp);

Struktura struct timespec vypadá takto:

struct timespec {
        time_t   tv_sec;        /* seconds */
        long     tv_nsec;       /* nanoseconds */
};

První argument funkce clock_gettime() ovlivňuje, co je počátek. Můžete použít jednu z konstant specifikovaných v time.h. Funkce může vracet i hodnotu v závislosti na nastavení hodin (CLOCK_REALTIME), což je přesně to co teď nechci, nebo hodnotu nezávislou na nastavení hodin, od onoho nespecifikovaného počátku (CLOCK_MONOLITIC).

Existují i další možnosti než jen CLOCK_REALTIME a CLOCK_MONOLITIC, viz manuálové stránky.

Funkce vrací 0 v případě úspěchu, jinak -1.

A teď příklad. Napsal jsem si pomocnou funkci difftimespec(), která vrací rozdíl dvou struct timespec struktur v sekundách (s přesností na nanosekundy).

/*------------------------------------------------*/
/* c20/clock.c                                    */
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#define SLEEP_SEC 30

float difftimespec(struct timespec *ts1, struct timespec *ts2)
{
   return difftime(ts1->tv_sec, ts2->tv_sec) + (ts1->tv_nsec - ts2->tv_nsec)*1e-9;
}

int main(void)
{
    time_t cas1,cas2;
    struct timespec ts1, ts2;

    cas1 = time(NULL);
    clock_gettime(CLOCK_MONOTONIC, &ts1);

    printf("Je prave %s", ctime(&cas1));
    printf("sleep(%i);\n", SLEEP_SEC);
    sleep(SLEEP_SEC);

    cas2 = time(NULL);
    clock_gettime(CLOCK_MONOTONIC, &ts2);


    printf("Je prave %s", ctime(&cas2));
    printf("Ubehlo  %f sekund (na hodinach).\n", difftime(cas2, cas1));
    printf("Ubehlo  %f sekund (realne).\n", difftimespec(&ts2, &ts1));

    return 0;
}

/*------------------------------------------------*/

Výsledek může vypadat takto:

Je prave Thu Dec 10 19:11:18 2015
sleep(30);
Je prave Thu Dec 10 19:11:48 2015
Ubehlo  30.000000 sekund (na hodinach).
Ubehlo  30.000311 sekund (realne).

Těch 311000 nanosekund navíc není kvůli tomu, že by mi na počítači šmajdali hodiny, ale protože vyskočení z těla funkce sleep(), zavolání time() a i zavolání clock_gettime() nějakou chvilku trvá. Funkce time() se volá dříve a navíc nepracuje s takovou přesností jako clock_gettime(), proto žádný čas navíc nepostřehne.

Program čeká 30 sekund, což je dostatečně dlouhá doba na to, abych změnil čas na hodinách v počitači. Pak může výsledek vypadat takto:

Je prave Thu Dec 10 19:42:09 2015
sleep(30);
Je prave Thu Dec 10 20:42:28 2015
Ubehlo  3619.000000 sekund (na hodinach).
Ubehlo  30.000202 sekund (realne).

Výsledek mluví sám za sebe.

Visual Studio

Jak už jsem psal, „clock“ funkce jsou linuxové (POSIX) rozšíření. Ve Visual Studiu je nenajdete, ale můžete použít jiné, windows, funkce k naprogramování něčeho podobného, viz třeba stack overflow.

Komentář Hlášení chyby
Vytvořeno: 29.8.2003
Naposledy upraveno: 10.12.2015
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..