Bieżąca data i godzina w systemie Linux jest przechowywana w postaci tzw. znacznika czasu.
Jest to liczba oznaczająca liczbę sekund, które upłynęły od pierwszego dnia stycznia roku 1970, od
północy czasu UTC
(GMT). Znacznik czasu pobierany
jest funkcją sys_time (numer 13), a nowy czas można ustawić za pomocą
funkcji sys_stime (numer 25).
Jeśli nie chcemy korzystać z żadnych bibliotek, to ta forma jest dość niewygodna w użyciu.
Dlatego przedstawię tu sposoby przerabiania znacznika czasu na formę tradycyjną i odwrotnie.
Artykuł ten opracowałem na podstawie kodu źródłowego linuksowej biblioteki języka C (glibc), konkretnie - na podstawie pliku glibc/time/offtime.c.
resztą
Procedura DODATEK składa się z kroków:
Cały ten skomplikowany algorytm jest ukazany w tym oto programie (składnia FASM):
; Program wyliczający bieżącą datę i godziną na podstawie bieżącego ; znacznika czasu. Program NIC NIE WYŚWIETLA. ; ; 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 ; przesunięcie 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 strefę czasową ; jeśli reszta sekund < 0, dodajemy do niej liczbę sekund dnia, ; ale równocześnie zmniejszamy liczbę dni (EAX) spr_reszte: cmp edx, 0 jge reszta_ok add edx, SEK_NA_DZIEN sub eax, 1 jmp spr_reszte reszta_ok: ; jeśli reszta sekund > liczba sekund w dniu, odejmujemy od niej ; liczbę sekund dnia, ale równocześnie zwiększamy liczbę 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 godzinę mov [reszta], edx ; i nową resztę mov eax, edx mov ecx, 60 xor edx, edx idiv ecx ; nową resztę dzielimy przez 60 mov [min], al ; iloraz to liczba minut mov [sek], dl ; a reszta - liczba sekund ; znajdujemy dzień tygodnia mov eax, [l_dni] add eax, 4 ; 1970-1-1 to czwartek mov ebx, 7 xor edx, edx idiv ebx ; EAX = dzień tygodnia cmp dl, 0 jge dzient_ok add dl, 7 ; dodajemy 7, jeśli był mniejszy od zera dzient_ok: mov [dzient], dl ; początek pętli z punktu 9 spr_dni: mov eax, [y] call czy_przest ; ECX = 0, gdy Y jest przestępny. 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 dzień w roku przestępnym .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 ; jeśli 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 całość na raz od liczby dni mov eax, [yg] mov [y], eax ; do Y wstaw YG jmp spr_dni ; i na początek pętli koniec_spr_dni: mov eax, [y] ;sub eax, 1900 mov [rok], ax ; zapisz wyliczony rok call czy_przest ; ECX = 0, gdy przestępny mov eax, [l_dni] mov [dzienr], ax ; zapisz numer dnia w roku ; sprawdzimy, do którego miesiąca należy wyliczony numer dnia xor esi, esi ; zakładamy rok nieprzestępny mov ebx, 2 ; zaczynamy od pierwszego miesiąca test ecx, ecx jnz .nie_przest add esi, 13*2 ;jeśli przestępny,bierzemy drugą grupę liczb .nie_przest: ; szukamy miesiąca. EAX = numer dnia w roku cmp ax, [dni1+esi+ebx] ; porównujemy numer dnia z sumą dni aż ; do NASTĘPNEGO miesiąca jbe mies_juz ; jeśli już mniejszy, przerywamy add ebx, 2 ; sprawdzamy kolejny miesiąc jmp .nie_przest mies_juz: ; aby dostać numer dnia w miesiącu, odejmujemy od numeru dnia ;sumę liczb dni we wszystkich POPRZEDNICH miesiącach, stąd -2 sub ax, [dni1+esi+ebx-2] inc al ; i dodajemy jeden, żeby nie liczyć od zera mov [dzien], al ; zapisujemy dzień miesiąca shr ebx, 1 ; numer znalezionego miesiąca dzielimy przez 2, bo ; są 2 bajty na miesiąc 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 ; sprawdź resztę jge .edx_ok1 sub ecx, 1 ; jeśli 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 bieżącego wyniku cmp edx, 0 ; sprawdź resztę jge .edx_ok2 add ecx, 1 ; jeśli reszta < 0, od wyniku odejmij 1 .edx_ok2: pop eax xor edx, edx idiv ebx ; dziel EAX przez 400 add ecx, eax ; dodaj do bieżącego wyniku cmp edx, 0 ; sprawdź resztę jge .edx_ok3 sub ecx, 1 ; jeśli 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 przestępny, 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 różna od zera oznacza, że się nie ; dzieli, czyli nie może być przestępny ; będąc tu wiemy, że rok dzieli się przez 4 push eax xor edx, edx mov ebx, 100 idiv ebx ; dziel EAX przez 100 pop eax test edx, edx jnz .jest ; reszta różna od zera oznacza, że się nie ; dzieli przez 100, a dzielił się przez 4, ; czyli jest przestępny ; będąc tu wiemy, że rok dzieli się 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 równa zero oznacza, że się dzieli ; przez 400, czyli jest przestępny .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 dzieleń y dd 1970 ; początkowa wartość Y yg dd 0 ; zmienna YG przest dd 0 ; dodatek czas dd 0 ; znacznik czasu rok dw 0 ; bieżący rok mies db 0 ; bieżący miesiąc dzien db 0 ; bieżący dzień miesiąca dzient db 0 ; bieżący dzień tygodnia dzienr dw 0 ; bieżący dzień roku godz db 0 ; bieżąca godzina min db 0 ; bieżąca minuta sek db 0 ; bieżąca sekunda ; liczby dni poprzedzających każdy miesiąc w roku zwykłym i przestępnym 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
Ten algorytm jest o wiele prostszy. Mianowicie:
Znacznik czasu = SEKUNDY + MINUTY*60 + GODZINY*60*60 + DZIEŃ_ROKU*60*60*24 + LATA_OD_1970*60*60*24*365
+ LATA_PRZESTĘPNE_OD_1970*60*60*24
Wystarczy jedynie obliczyć, którym dniem w roku jest bieżący dzień (znając dzień miesiąca, korzystamy z tablicy w powyższym programie i do określonej liczby dodajemy bieżący numer dnia) oraz ile było lat przestępnych od roku 1970 do bieżącego (według znanych reguł, wystarczy w pętli dla każdego roku uruchomić procedurę czy_przest z poprzedniego programu).
Zauważcie, że tyle, ile było lat przestępnych, tyle dodajemy dni, nie całych lat.