   #Start Contents English version

            Pobieranie i ustawianie daty oraz godziny pod Linuksem

   Biezaca data i godzina w systemie Linux jest przechowywana w postaci
   tzw. znacznika czasu. Jest to liczba oznaczajaca liczbe sekund, ktore
   uplynely od pierwszego dnia stycznia roku 1970, od polnocy czasu UTC
   (GMT). Znacznik czasu pobierany jest funkcja sys_time (numer 13), a
   nowy czas mozna ustawic za pomoca funkcji sys_stime (numer 25).
   Jesli nie chcemy korzystac z zadnych bibliotek, to ta forma jest dosc
   niewygodna w uzyciu. Dlatego przedstawie tu sposoby przerabiania
   znacznika czasu na forme tradycyjna i odwrotnie.

   Artykul ten opracowalem na podstawie kodu zrodlowego linuksowej
   biblioteki jezyka C (glibc), konkretnie - na podstawie pliku
   glibc/time/offtime.c.
     _________________________________________________________________

Zmiana znacznika czasu na forme tradycyjna

   (przeskocz do konwersji w druga strone)
    1. dzielimy znacznik przez liczbe sekund przypadajaca na dzien
       (60*60*24), zachowujemy iloraz jako liczbe dni oraz reszte z tego
       dzielenia
    2. do reszty dodajemy przesuniecie naszego czasu od GMT, w sekundach
       (60*60 w czasie zimowym, 2*60*60 w czasie letnim)
    3. jesli reszta jest mniejsza od zera, to dodajemy do niej liczbe
       sekund przypadajacych na dzien, az stanie sie wieksza od zera, za
       kazdym razem zmniejszajac liczbe dni z pierwszego kroku
    4. jesli reszta jest wieksza od liczby sekund dnia, to odejmujemy do
       niej liczbe sekund przypadajacych na dzien, az stanie sie mniejsza
       od tej liczby, za kazdym razem zwiekszajac liczbe dni z pierwszego
       kroku
    5. dzielimy reszte przez liczbe sekund przypadajaca na godzine.
       Iloraz zachowujemy jako obliczona godzine, reszte zapisujemy do
       zmiennej, ktora dalej nazywamy "reszta"
    6. reszte z poprzedniego kroku dzielimy przez liczbe sekund w
       minucie. Iloraz zachowujemy jako liczbe minut biezacego czasu,
       reszte - jako liczbe sekund
    7. do liczby dni dodajemy 4 (jako ze pierwszy stycznia 1970 byl
       czwartkiem), a wynik dzielimy przez 7. Reszte (jesli jest ujemna,
       dodajemy 7) z tego dzielenia zachowujemy jako dzien tygodnia (0
       oznacza niedziele)
    8. do zmiennej Y wstawiamy 1970
    9. w petli wykonuj dzialania:
         1. sprawdz, czy liczba dni jest mniejsza od zera lub wieksza od
            liczby dni w roku Y. Jesli nie zachodzi ani to, ani to, wyjdz
            z petli.
            W tym kroku nalezy sprawdzic, czy Y jest przestepny. Kazdy
            rok, ktory dzieli sie przez 4, lecz nie dzieli sie przez 100
            jest przestepny. Dodatkowo, kazdy rok, ktory dzieli sie przez
            400, jest przestepny.
         2. do nowej zmiennej YG wstaw sume Y oraz ilorazu z dzielenia
            liczby dni przez 365. Jesli reszta z dzielenia liczby dni
            przez 365 byla ujemna, od YG odejmij jeden.
         3. od liczby dni odejmij roznice miedzy YG a Y pomnozona przez
            365
         4. od liczby dni odejmij wynik procedury DODATEK (omowiona
            pozniej), wykonanej na liczbie YG-1
         5. do liczby dni dodaj wynik procedury DODATEK (omowiona
            pozniej), wykonanej na liczbie Y-1
         6. do Y wstaw YG
   10. do numeru dnia w roku wstaw biezaca wartosc liczby dni
   11. sprawdz, w ktorym miesiacu znajduje sie dzien o tym numerze i
       zapisz ten miesiac. Od liczby dni odejmij sumaryczna liczbe dni we
       wszystkich poprzednich miesiacach.
   12. do dnia miesiaca wstaw biezaca liczbe dni powiekszona o 1

   Procedura DODATEK sklada sie z krokow:
    1. podziel podany rok przez 4 i zachowaj wynik. Jesli reszta wyszla
       mniejsza od zera, od wyniku odejmij 1
    2. podziel podany rok przez 100 i zachowaj wynik. Jesli reszta wyszla
       mniejsza od zera, od wyniku odejmij 1
    3. podziel podany rok przez 400 i zachowaj wynik. Jesli reszta wyszla
       mniejsza od zera, od wyniku odejmij 1
    4. od pierwszego wyniku odejmij drugi, po czym dodaj trzeci, a calosc
       zwroc jako wynik procedury

   Caly ten skomplikowany algorytm jest ukazany w tym oto programie
   (skladnia FASM):
   (przeskocz program)
; Program wyliczajacy biezaca date i godzina na podstawie biezacego
;       znacznika czasu. Program NIC NIE WYSWIETLA.
;
; Autor: Bogdan D., bogdandr (at) op.pl
;
; kompilacja:
;   fasm dataczas.fasm

format ELF executable
segment executable
entry main

SEK_NA_GODZ     = (60 * 60)             ; liczba sekund w godzinie
SEK_NA_DZIEN    = (SEK_NA_GODZ * 24)    ; liczba sekund w dobie
LETNI           = 1                     ; 0, gdy zimowy, 1 gdy letni
PRZES_GMT       = 1*SEK_NA_GODZ + LETNI*SEK_NA_GODZ  ; przesuniecie od GMT

main:
        mov     eax, 13
        xor     ebx, ebx
        int     80h     ; pobierz aktualny czas w sekundach
        mov     [czas], eax

        mov     ebx, SEK_NA_DZIEN
        xor     edx, edx
        idiv    ebx     ; liczba sekund / liczba sekund w dniu = liczba dni

        add     edx, PRZES_GMT  ; dodaj strefe czasowa

        ; jesli reszta sekund < 0, dodajemy do niej liczbe sekund dnia,
        ; ale rownoczesnie zmniejszamy liczbe dni (EAX)
spr_reszte:
        cmp     edx, 0
        jge     reszta_ok

        add     edx, SEK_NA_DZIEN
        sub     eax, 1

        jmp     spr_reszte

reszta_ok:

        ; jesli reszta sekund > liczba sekund w dniu, odejmujemy od niej
        ; liczbe sekund dnia, ale rownoczesnie zwiekszamy liczbe dni (EAX)
spr_reszte2:
        cmp     edx, SEK_NA_DZIEN
        jl      reszta_ok2

        sub     edx, SEK_NA_DZIEN
        add     eax, 1

        jmp     spr_reszte2

reszta_ok2:

        mov     [l_dni], eax
        mov     [reszta], edx

        mov     eax, edx        ; EAX = reszta
        mov     ebx, SEK_NA_GODZ
        xor     edx, edx
        idiv    ebx     ; EAX = numer godziny, reszta - minuty+sekundy

        mov     [godz], al      ; zachowujemy godzine
        mov     [reszta], edx   ; i nowa reszte

        mov     eax, edx
        mov     ecx, 60
        xor     edx, edx
        idiv    ecx             ; nowa reszte dzielimy przez 60

        mov     [min], al       ; iloraz to liczba minut
        mov     [sek], dl       ; a reszta - liczba sekund

        ; znajdujemy dzien tygodnia
        mov     eax, [l_dni]
        add     eax, 4  ; 1970-1-1 to czwartek
        mov     ebx, 7
        xor     edx, edx
        idiv    ebx     ; EAX = dzien tygodnia

        cmp     dl, 0
        jge     dzient_ok
        add     dl, 7   ; dodajemy 7, jesli byl mniejszy od zera
dzient_ok:
        mov     [dzient], dl


        ; poczatek petli z punktu 9
spr_dni:
        mov     eax, [y]
        call    czy_przest      ; ECX = 0, gdy Y jest przestepny.

        cmp     dword [l_dni], 0
        jl      zmien_dni       ; sprawdzamy, czy liczba dni < 0

        mov     esi, 365
        test    ecx, ecx
        jnz     .przest_ok
        add     esi, 1          ; dodajemy 1 dzien w roku przestepnym
.przest_ok:

        cmp     [l_dni], esi
        jl      koniec_spr_dni  ; sprawdzamy, czy liczba dni >= 365/366

zmien_dni:

        mov     esi, 365
        mov     eax, [l_dni]
        xor     edx, edx
        idiv    esi             ; EAX = liczba dni/365
        mov     ecx, eax        ; zachowujemy do ECX
        cmp     edx, 0
        jge     .edx_ok1
        sub     ecx, 1          ; jesli reszta < 0, to odejmujemy 1
.edx_ok1:
        add     ecx, [y]        ; ECX = liczba dni/365 + Y +1 lub +0
        mov     [yg], ecx       ; zachowaj do YG

        sub     ecx, [y]
        imul    ecx, ecx, 365   ; ECX = (YG-Y)*365

        push    ecx
        mov     eax, [yg]
        sub     eax, 1
        call    dodatek         ; wylicz DODATEK na YG-1 i zapisz w [przest]
        pop     ecx
        add     ecx, [przest]   ; ECX = (YG-Y)*365+DODATEK(YG-1)

        push    ecx
        mov     eax, [y]
        sub     eax, 1
        call    dodatek         ; wylicz DODATEK na Y-1 i zapisz w [przest]
        pop     ecx
        sub     ecx, [przest]   ; ECX=(YG-Y)*365+DODATEK(YG-1)-DODATEK(Y-1)

        sub     [l_dni], ecx    ; odejmij calosc na raz od liczby dni

        mov     eax, [yg]
        mov     [y], eax        ; do Y wstaw YG

        jmp     spr_dni         ; i na poczatek petli

koniec_spr_dni:
        mov     eax, [y]
        ;sub    eax, 1900
        mov     [rok], ax       ; zapisz wyliczony rok
        call    czy_przest      ; ECX = 0, gdy przestepny

        mov     eax, [l_dni]
        mov     [dzienr], ax    ; zapisz numer dnia w roku

        ; sprawdzimy, do ktorego miesiaca nalezy wyliczony numer dnia
        xor     esi, esi        ; zakladamy rok nieprzestepny
        mov     ebx, 2          ; zaczynamy od pierwszego miesiaca
        test    ecx, ecx
        jnz     .nie_przest
        add     esi, 13*2       ;jesli przestepny,bierzemy druga grupe liczb
.nie_przest:
        ; szukamy miesiaca. EAX = numer dnia w roku
        cmp     ax, [dni1+esi+ebx]  ; porownujemy numer dnia z suma dni az
                                ; do NASTEPNEGO miesiaca
        jbe     mies_juz        ; jesli juz mniejszy, przerywamy
        add     ebx, 2          ; sprawdzamy kolejny miesiac
        jmp     .nie_przest

mies_juz:
                ; aby dostac numer dnia w miesiacu, odejmujemy od numeru dnia
                ;sume liczb dni we wszystkich POPRZEDNICH miesiacach, stad -2
        sub     ax, [dni1+esi+ebx-2]
        inc     al      ; i dodajemy jeden, zeby nie liczyc od zera
        mov     [dzien], al     ; zapisujemy dzien miesiaca

        shr     ebx, 1  ; numer znalezionego miesiaca dzielimy przez 2, bo
                        ; sa 2 bajty na miesiac
        mov     [mies], bl      ; i zachowujemy

        mov     eax, 1
        xor     ebx, ebx
        int     80h     ; koniec programu


dodatek:
; oblicza DODATEK dla roku podanego w EAX
        push    eax
        push    ebx
        push    ecx
        push    edx
        push    esi
        push    edi

        mov     esi, 4
        mov     edi, 100
        mov     ebx, 400
        and     eax, 0ffffh

        push    eax
        xor     edx, edx
        idiv    esi             ; dziel EAX przez 4
        mov     ecx, eax        ; zachowaj wynik
        cmp     edx, 0          ; sprawdz reszte
        jge     .edx_ok1
        sub     ecx, 1          ; jesli reszta < 0, od wyniku odejmij 1
.edx_ok1:

        pop     eax
        push    eax
        xor     edx, edx
        idiv    edi             ; dziel EAX przez 100
        sub     ecx, eax        ; odejmij od biezacego wyniku
        cmp     edx, 0          ; sprawdz reszte
        jge     .edx_ok2
        add     ecx, 1          ; jesli reszta < 0, od wyniku odejmij 1
.edx_ok2:

        pop     eax
        xor     edx, edx
        idiv    ebx             ; dziel EAX przez 400
        add     ecx, eax        ; dodaj do biezacego wyniku
        cmp     edx, 0          ; sprawdz reszte
        jge     .edx_ok3
        sub     ecx, 1          ; jesli reszta < 0, od wyniku odejmij 1
.edx_ok3:

        mov     [przest], ecx   ; zachowaj wynik

        pop     edi
        pop     esi
        pop     edx
        pop     ecx
        pop     ebx
        pop     eax
        ret

; zwraca 0 w ECX, gdy rok podany w EAX jest przestepny, 1 - gdy nie jest
czy_przest:
        push    eax
        push    ebx
        push    edx

        xor     ecx, ecx

        push    eax
        xor     edx, edx
        mov     ebx, 4
        idiv    ebx             ; dziel EAX przez 4
        pop     eax
        test    edx, edx
        jnz     .nie_jest       ; reszta rozna od zera oznacza, ze sie nie
                                ; dzieli, czyli nie moze byc przestepny

        ; bedac tu wiemy, ze rok dzieli sie przez 4
        push    eax
        xor     edx, edx
        mov     ebx, 100
        idiv    ebx             ; dziel EAX przez 100
        pop     eax
        test    edx, edx
        jnz     .jest           ; reszta rozna od zera oznacza, ze sie nie
                                ; dzieli przez 100, a dzielil sie przez 4,
                                ; czyli jest przestepny


        ; bedac tu wiemy, ze rok dzieli sie przez 4 i przez 100
        push    eax
        xor     edx, edx
        mov     ebx, 400
        idiv    ebx             ; dziel EAX przez 400
        pop     eax
        test    edx, edx
        jz      .jest           ; reszta rowna zero oznacza, ze sie dzieli
                                ; przez 400, czyli jest przestepny

.nie_jest:
        mov     ecx, 1

.jest:
        pop     edx
        pop     ebx
        pop     eax
        ret


segment readable writeable

l_dni   dd      0       ; wyliczona liczba dni
reszta  dd      0       ; reszta z dzielen
y       dd      1970    ; poczatkowa wartosc Y
yg      dd      0       ; zmienna YG
przest  dd      0       ; dodatek
czas    dd      0       ; znacznik czasu

rok     dw      0       ; biezacy rok
mies    db      0       ; biezacy miesiac
dzien   db      0       ; biezacy dzien miesiaca
dzient  db      0       ; biezacy dzien tygodnia
dzienr  dw      0       ; biezacy dzien roku

godz    db      0       ; biezaca godzina
min     db      0       ; biezaca minuta
sek     db      0       ; biezaca sekunda

; liczby dni poprzedzajacych kazdy miesiac w roku zwyklym i przestepnym
dni1    dw      0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
        dw      0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
     _________________________________________________________________

Zmiana formy tradycyjnej na znacznik czasu

   Ten algorytm jest o wiele prostszy. Mianowicie:
   Znacznik czasu = SEKUNDY + MINUTY*60 + GODZINY*60*60 +
   DZIEN_ROKU*60*60*24 + LATA_OD_1970*60*60*24*365 +
   LATA_PRZESTEPNE_OD_1970*60*60*24

   Wystarczy jedynie obliczyc, ktorym dniem w roku jest biezacy dzien
   (znajac dzien miesiaca, korzystamy z tablicy w powyzszym programie i
   do okreslonej liczby dodajemy biezacy numer dnia) oraz ile bylo lat
   przestepnych od roku 1970 do biezacego (wedlug znanych regul,
   wystarczy w petli dla kazdego roku uruchomic procedure czy_przest z
   poprzedniego programu).

   Zauwazcie, ze tyle, ile bylo lat przestepnych, tyle dodajemy dni, nie
   calych lat.

   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
