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:
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:
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í
- Převede strukturu tm na řetězec ("Wed Jun 30 21:49:08 1993\n"). Návratovou hodnotou je ukazatel na tento řetězec.
- Vrací to samé, jako
asctime(localtime(timep));
- Vrací rozdíl mezi časem time0 a time1 ve vteřinách.
- Převede čas uložený v timep (pomocí vteřin) na strukturu tm a to v čase UTC (GMT). Vrací ukazatel na tuto strukturu.
- Jako funkce gmtime(), ale vrací lokální čas (nikoliv UTC).
- Převádí strukturu na kterou odkazuje timeptr do počtu vteřin, jež jsou návratovou hodnotou.
- 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.
- 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.
-
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()
.
%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;
}
/*------------------------------------------------*/
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(>mcas));
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", >mcas);
if(uch != NULL) {
strftime(retezec, MAX - 1,
"strptime: %A %d.%m.%Y, %I:%M %p,",
>mcas);
printf("%s %s\n",retezec, uch);
}
#endif
return 0;
}
/*------------------------------------------------*/
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.
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.
Struktura struct timespec
vypadá takto:
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.
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.