Hlavní navigace

GLib: Machrování s kalendářem

3. 5. 2001
Doba čtení: 8 minut

Sdílet

Na to, že GLib zasahuje do mnoha oblastí programátorské praxe, jste si už jistě zvykli. Je to zkrátka holka pro všechno. Částí, kterou si dnes popíšeme, jsou nástroje pro kalendářní počty.
GDate

je struktura knihovny GLib reprezentující den od 1. 1. roku 1 po několik tisíc let v budoucnosti. (Přesný limit není stanoven. Samotná struktura GDate zvládne uložit i rok 65535, ale funkce g_date_set_par­se() umí přečíst datum zhruba jen do roku 8000.) GDate neukládá astronomická nebo historická data. Pro to nemá dispozice. Hodí se jedině tehdy, chcete-li pracovat s obyčejnými („každodenními“) letopočty. Mechanismus akceptuje jak formát den-měsíc-rok, tak Juliánskou reprezentaci (počet dnů od 1. ledna roku 1). (Technicky vzato, termín "Juliánský kalendář zde taky není na místě. Juliánský letopočet začal roku 4713 př. Kr. V dalším výkladu tento termín budeme ale chápat tak, jak jsme si jej definovali.)

Při práci s GDate  jsem přišel na jednu nesrovnalost. Mechanismus neošetřuje speciální událost v roce 1752, kdy proběhla Gregoriánská reforma kalendáře. Lidé si uvědomili, že se jim kalendář rozchází s astronomickými pozorováními a upravili výpočet přestupných let. V té době se však už „lidský“ kalendář zpožďoval o 11 dnů. V září roku 1752 proto učinili skok a kritických 11 dnů vypustili. Po středě 2. září 1752 následoval čtvrtek 14. září 1752. No a právě tento fakt GDate ignoruje.

Struktura GDate je bitové pole a na 64-bitových platformách může dojít k jejímu sbalení do jednoho velkého intu, což je z hlediska efektivity jistě velice příjemné. Pro přístup k položkám struktury by se měly používat výhradně funkce, které si v dnešním článku popíšeme.

Práce s časem (intermezzo)

Práce s časem v knihovně GLib se omezují jen na získání aktuálního času a se strukturou GDate nemá nic společného:

void g_get_current_time(GTimeVal *result);

struct GTimeVal
{
  glong tv_sec;
  glong tv_usec;
};

Struktura GTimeVal reprezentuje precizní čas se sekundami a mikrosekundami. Struktura je stejná jako timeval vracená unixovým voláním gettimeofday(). Jediný rozdíl je v tom, že g_get_current_time() funguje i na platformách Win32.

V rozhraní knihovny je ještě definován typ

typedef gint32 GTime;

…což je obyčejná náhrada typu time_t.

Vytvoření nového GDate

GDate* g_date_new(void);

…alokuje novou strukturu GDate a nastaví ji do „nedefinovaného“ (neplatného) stavu.

GDate* g_date_new_dmy(GDateDay day, GDateMonth month, GDateYear year);

…alokuje nový GDate a uloží do něj zadané datum. Typy argumentů jsou definovány takto:

typedef enum
{
  G_DATE_BAD_MONTH = 0,
  G_DATE_JANUARY   = 1,
  G_DATE_FEBRUARY  = 2,
  G_DATE_MARCH     = 3,
  G_DATE_APRIL     = 4,
  G_DATE_MAY       = 5,
  G_DATE_JUNE      = 6,
  G_DATE_JULY      = 7,
  G_DATE_AUGUST    = 8,
  G_DATE_SEPTEMBER = 9,
  G_DATE_OCTOBER   = 10,
  G_DATE_NOVEMBER  = 11,
  G_DATE_DECEMBER  = 12
} GDateMonth;

typedef guint8  GDateDay;
typedef guint16 GDateYear;

Podobně jako GDateMonth má konstantu G_DATE_BAD_MONTH označující chybný měsíc, i GDateDay  a GDateYear mají definována makra G_DATE_BAD_DAY a G_DATE_BAD_YEAR, kterými můžete označovat nesprávné hodnoty.

GDate* g_date_new_julian(guint32 julian_day);

…vytvoří nový GDate a uloží do něj datum podle atributu julian_day, což je počet dnů od prvního dne našeho letopočtu ( julian_day = 1 pro datum 1. 1. 1). Nedefinovaná Juliánská hodnota se označuje konstantním makrem  G_DATE_BAD_JULIAN.

Pokud GDate nealokujete na haldě, ale vytváříte je jako lokální proměnné na zásobníku nebo jako pole, měli byste je vždy ihned alespoň vynulovat, aby dostaly „nedefinovanou“ („neplatnou“), ale pro mechanismus zcela korektní hodnotu:

void g_date_clear(GDate *date, guint n_dates);

…funkce inicializuje jednu nebo více struktur GDate, na které ukazuje pointer date. Atribut n_dates je počet datových prvků v poli. Například:

GDate datum;
GDate pole_datumu[5];

g_date_clear(&datum, 1);
g_date_clear(&pole_datumu, 5);

Pro úplnost ještě uvedu další výčtové typy spřízněné s mechanismem  GDate:

typedef enum
{
  G_DATE_DAY   = 0,
  G_DATE_MONTH = 1,
  G_DATE_YEAR  = 2
} GDateDMY;

typedef enum
{
  G_DATE_BAD_WEEKDAY  = 0,
  G_DATE_MONDAY       = 1,
  G_DATE_TUESDAY      = 2,
  G_DATE_WEDNESDAY    = 3,
  G_DATE_THURSDAY     = 4,
  G_DATE_FRIDAY       = 5,
  G_DATE_SATURDAY     = 6,
  G_DATE_SUNDAY       = 7
} GDateWeekday;

První z nich ( GDateDMY) není knihovnou používán, ale vám může být užitečný pro poznámku k číslu, že jde o den, měsíc nebo rok. Ve druhém výčtovém typu jistě poznáváte dny v týdnu. Také v něm je definována konstanta pro neplatné označení dne v týdnu –  G_DATE_BAD_WEEKDAY.

Nastavení data

Pro uložení data do struktury GDate máte k dispozici celou sadu funkcí. Význam jejich názvů a parametrů je natolik zřejmý, že jistě nepotřebují žádný další komentář:

void g_date_set_day(GDate *date, GDateDay day);
void g_date_set_month(GDate *date, GDateMonth month);
void g_date_set_year(GDate *date, GDateYear year);
void g_date_set_dmy(GDate *date, GDateDay day, GDateMonth month, GDateYear y);
void g_date_set_julian(GDate *date, guint32 julian_date);

Funkce nastaví příslušné položky data. Předávané argumenty by samozřejmě měly být platné. Pokud výsledná trojice den-měsíc-rok uložená ve struktuře GDate je neplatná (např. 31. února 2001), stane se neplatným celé datum date.

Chcete-li do GDate uložit datum z hodnoty typu GTime ( time_t), použijte funkci

void g_date_set_time(GDate *date, GTime time);

Například dnešní datum získáte voláním:

g_date_set_time(date, time(NULL));

Chcete-li datum získat čtením z řetězce znaků, použijte funkci

void g_date_set_par­se(GDate *date, const gchar *str);

Funkce g_date_set_par­se() se při své práci ohlíží na locales a pokouší se uhodnout, jakým způsobem je datum zapsáno. Nutno ale podotknout, že její výsledky nejsou nikterak oslnivé. Často se stane, že naprosto obyčejný formát data nepochopí – zvláště pak datumy napsané tak, jak je v české kotlině zvykem. A to je škoda.

Kalendářová matematika

S datem uloženým v GDate  můžete provádět jednoduché matematické operace (podmínkou je, že upravované datum ( date) musí být platné):

void g_date_add_days(GDate *date, guint n_days);
void g_date_add_months(GDate *date, guint n_months);
void g_date_add_years(GDate *date, guint n_years);

…přičítání dnů, měsíců a let. Chcete raději odčítání? Prosím:

void g_date_subtract_days(GDate *date, guint n_days);
void g_date_subtract_months(GDate *date, guint n_months);
void g_date_subtract_years(GDate *date, guint n_years);

Příklad:

date = g_date_new_dmy(29, G_DATE_FEBRUARY, 2000);
g_date_add_years(date, 5); /* v date je nyni 28. 2. 2005 */

Hodnoty GDate lze i porovnávat:

gint g_date_compare(GDate *lhs, GDate *rhs);

Datumy lhs a rhs musí být platné (definované). Funkce g_date_compare() vrátí nulu, pokud si jsou datumy rovny, číslo menší než nula, pokud lhs je menší než rhs, a číslo větší než nula v opačném případě.

Získávání hodnot z GDate

Hodnoty jednou uložené do GDate je pochopitelně možné vytáhnout zpět – viz následující funkce:

GDateDay g_date_day(GDate *date);
GDateMonth g_date_month(GDate *date);
GDateYear g_date_year(GDate *date);

…vrátí složku dne, měsíce nebo roku data date.

guint32 g_date_julian(GDate *date);

…vrátí počet dnů od 1. 1. roku 1 po datum date. (Hodnota se tuším od skutečného počtu liší o 11 dnů – viz problém roku 1752.)

GDateWeekday g_date_weekday(GDate *date);

…vrátí den v týdnu (pondělí, úterý, …) náležející datu date.

guint g_date_day_of_year(GDate *date);

…výsledkem je pořadí dne určeného datem date v roce. Např.: 5. únor 2001 je 36. dnem v roce 2001. (29. únor v přestupných letech je samozřejmě zohledněn.)

Další funkce

guint8 g_date_days_in_month(GDateMonth month, GDateYear year);

…vrátí počet dnů v měsíci month v roce year. Např. leden 2001 – 31 dnů.

gboolean g_date_is_first_of_month(GDate *date);
gboolean g_date_is_last_of_month(GDate *date);

TRUE, jestliže je date prvním (posledním) dnem v měsíci.

gboolean g_date_is_leap_year(GDateYear year);

TRUE, jestliže je rok year přestupným rokem.

guint g_date_monday_week_of_year(GDate *date);
guint g_date_sunday_week_of_year(GDate *date);

…výsledkem funkcí je pořadí týdne v roce, do kterého datum date spadá, bere-li se za počátek týdne pondělí- monday (neděle- sunday). Jestliže se datum date nachází před prvním pondělím (nedělí) v roce, vrátí nulu.

guint8 g_date_monday_weeks_in_year(GDateYear year);
guint8 g_date_sunday_weeks_in_year(GDateYear year);

…tyto funkce vypočítají, kolik týdnů má rok year, chápeme-li jako počátek týdne pondělí (neděli). Rok má vždy 52 nebo 53 týdnů. Tyto funkce nám sdělí, kolik pondělí (nedělí) se v roce year naskytne.

Chcete-li datum zkonvertovat do řetězce, není nic jednoduššího. Prostě použijte funkci

gsize g_date_strftime(gchar *s, gsize slen,
                      const gchar *format,
              GDate *date);

Rutina g_date_strftime() uloží do textového bufferu s, jehož velikost je slen, řetězec s datem date podle formátu format. Při konverzi se berou v úvahu uživatelovy locales. Funkce vrací počet znaků zapsaných do bufferu s, nebo 0, pokud je buffer příliš malý. Formát format je stejný jako u funkce strftime(). Akceptují se ale jen řídicí sekvence pro datum, „časové“ formáty produkují nedefinované výsledky.

void g_date_to_struct_tm(GDate *date, struct tm *tm);

…uloží datum date do struktury tm. Časové položky vyplní nesmyslnými, ale korektními hodnotami.

Kontroly platnosti

Poslední sadou funkcí pro práci s datem v knihovně GLib jsou funkce, které kontrolují platnost datumových údajů. Názvy funkcí i jejich parametry jsou samovysvětlující – každý jistě správně odhadne, co která funkce dělá:

gboolean g_date_valid(GDate *date);
gboolean g_date_valid_day(GDateDay day);
gboolean g_date_valid_month(GDateMonth month);
gboolean g_date_valid_year(GDateYear year);
gboolean g_date_valid_dmy(GDateDay day, GDateMonth month, GDateYear year);
gboolean g_date_valid_julian(guint32 julian_date);
gboolean g_date_valid_weekday(GDateWeekday weekday);

Uvolnění GDate z paměti

Máte-li struktury GDate alokovány na haldě, sluší se je po skončení práce uvolnit:

CS24_early

void g_date_free(GDate *date);

No a to by tak bylo z mé strany o práci s datem v knihovně GLib vše. U dalšího pokračování se těší

Byl pro vás článek přínosný?

Autor článku

Michal Burda vystudoval informatiku a aplikovanou matematiku a nyní pracuje na Ostravské univerzitě jako odborný asistent. Zajímá se o data mining, Javu a Linux.