Obsah / Utility / CALENDAR / Podpůrné funkce
Zdrojový kód:
INCLUDE\UTIL\CALENDAR.INC, UTIL\CALENDAR.ASM
Podpůrné
funkce
; ------------- Macro - convert absolute time to Windows FILETIME
; %1 = time LOW, %2 time HIGH (correction: 584390 days = 504912960000000000)
%macro ABSTOFILETIME 2
sub %1,774A8000h ; correction LOW
sbb %2,701CFA9h ; correction HIGH
%endmacro
|
Makro ABSTOFILETIME
zkonvertuje absolutní čas na časový údaj Windows FILETIME
(který udává počet stovek nanosekund od 1.1.1601 v 0:00:00).
Prvním parametrem makra je registr nebo ukazatel na nižší
dvojslovo konvertovaného času, druhým parametrem je vyšší
dvojslovo.
; ------------- Macro - convert Windows FILETIME to absolute time
; %1 = time LOW, %2 time HIGH (correction: 584390 days = 504912960000000000)
%macro FILETIMETOABS 2
add %1,774A8000h ; correction LOW
adc %2,701CFA9h ; correction HIGH
%endmacro
|
Makro FILETIMETOABS
provádí opačný převod, z údaje FILETIME na absolutní čas.
; -----------------------------------------------------------------------------
; Convert absolute time to Julian date
; -----------------------------------------------------------------------------
; INPUT: EDX:EAX = absolute time (100-nanosec from 1/1/1 BCE 0:00)
; OUTPUT: ST0 = Julian date (days from 1/1/4713 BCE noon 12:00)
; -----------------------------------------------------------------------------
; ------------- Push registers (save absolute time into stack)
AbsToJulian: push edx ; push EDX
push eax ; push EAX
; ------------- Convert absolute time to Julian date
fild qword [esp] ; load absolute time into ST0
fmul qword [AbsToDayCoef] ; recalc absolute time to days
fadd qword [JulianBase] ; add base of Julian date
; ------------- Pop registers
pop eax ; pop EAX
pop edx ; pop EDX
ret
JulianBase: dq 1721423.5 ; base Julian day (1/1/1 CE, 0:00:00)
AbsToDayCoef: dq 1.1574074074074074074e-12 ; ; abs. time to days coeff.
|
Funkce AbsToJulian
zkonvertuje absolutní čas na Juliánské datum. Na vstupu
funkce obsahují registry EDX:EAX absolutní čas (ve stovkách
nanosekund). Na výstupu obsahuje registr koprocesoru ST0
Juliánské datum ve formě počtu dnů, desetinná část
vyjadřuje poměrnou část dne.
Během konverze je
absolutní čas uložen do zásobníku a načten z něj do
koprocesoru jako celé 64-bitové číslo. Vynásobením
koeficientem 1/(24*60*60*10000000) je absolutní čas převeden
na dny. Nakonec je přičten základ Juliánského data, tj.
počet dnů počátku letopočtu vyjádřený Juliánským datem.
; -----------------------------------------------------------------------------
; Convert Julian date to absolute time
; -----------------------------------------------------------------------------
; INPUT: ST0 = Julian date (days from 1/1/4713 BCE noon 12:00)
; OUTPUT: EDX:EAX = absolute time (100-nanosec from 1/1/1 BCE 0:00)
; -----------------------------------------------------------------------------
; ------------- Push registers (prepare absolute time in stack)
JulianToAbs: push edx ; push EDX
push eax ; push EAX
; ------------- Convert Julian date to absolute time
fsub qword [JulianBase] ; subtract base of Julian date
fmul qword [DayToAbsCoef] ; recalc days to absolute time
fistp qword [esp] ; save absolute time from ST0
; ------------- Pop registers
pop eax ; pop EAX (absolute time LOW)
pop edx ; pop EDX (absolute time HIGH)
ret
DayToAbsCoef: dq 864000000000.0 ; days to absolute time coefficient
|
Funkce JulianToAbs
zkonvertuje Juliánské datum na absolutní čas. Na vstupu
funkce obsahuje registr koprocesoru ST0 Juliánské datum ve
formě počtu dnů, desetinná část vyjadřuje poměrnou část
dne. Na výstupu registry EDX:EAX obsahují absolutní čas (ve
stovkách nanosekund).
Během konverze je
nejdříve odečten základ Juliánského data, tj. počet dnů
počátku letopočtu vyjádřený Juliánským datem.
Vynásobením koeficientem 24*60*60*10000000 jsou dny převedeny
na absolutní čas. Nakonec je obsah registru ST0 uložen do
zásobníku a ze zásobníku načten výsledek do registrů
EDX:EAX.
; -----------------------------------------------------------------------------
; Check if year is a leap year
; -----------------------------------------------------------------------------
; INPUT: CX = year
; OUTPUT: CY = leap year
; -----------------------------------------------------------------------------
; ------------- Check if it can be a leap year (year is divisible by 4)
CheckLeapYear: test cl,3 ; is it divisible by 4?
jz CheckLeapYear4 ; it can be a leap year
ret ; here is NC = it is not leap year
; ------------- Set "leap year" flag if it is Julian calendar
CheckLeapYear2: stc ; set "leap year" flag
ret
; ------------- Check if it is Gregorian calendar (1582 and more)
CheckLeapYear4: cmp cx,SPLITYEAR ; is it Gregorian calendar?
jle CheckLeapYear2 ; it is Julian calendar
; ------------- Push registers
push eax ; push EAX
push edx ; push EDX
; ------------- Get century (-> EDX)
movzx edx,cx ; EDX <- year
mov eax,42949673 ; EAX <- 100000000h/100 (round up)
mul edx ; EDX <- century
; ------------- Get year in century
imul eax,edx,-100 ; EAX <- convert century to years
add ax,cx ; AX <- year in century
; ------------- Check leap century (century is divisible by 4)
jnz CheckLeapYear6 ; it is not century
test dl,3 ; is century divisible by 4?
jnz CheckLeapYear8 ; century is not divisible by 4
; ------------- Pop registers
CheckLeapYear6: stc ; set "leap year" flag
CheckLeapYear8: pop edx ; pop EDX
pop eax ; pop EAX
ret
|
Funkce CheckLeapYear
provede test, zda je rok v registru CX přechodným rokem. Pokud
ano, navrátí nastavený příznak CF (tj. stav CY), jinak bude
příznak CF vynulován (tj. stav NC).
Na začátku funkce
je proveden test, zda rok může být přechodný - tj. musí
být dělitelný číslem 4. Pokud není dělitelný číslem 4,
nemůže být přechodný a funkce se ukončí s vynulovaným
příznakem CF.
Jedná-li se o
Juliánský kalendář (tj. před rokem 1582), není potřeba
další obsluha a funkce se ukončí s nastaveným příznakem CF
jako indikace, že se jedná o přechodný rok.
Pro test
přechodnosti v Gregoriánském kalendáři je potřeba
nejdříve rozložit rok na století a na pořadové číslo roku
ve století. Namísto dělení číslem 100 se použije
rychlejší operace násobení převrácenou hodnotou (tj.
číslem 1 0000 0000h/100). Po násobení obdržíme v registru
EDX století. Abychom získali pořadové číslo roku ve
století, vynásobíme století opět hodnotou 100 a odečtením
od čísla roku obdržíme pořadové číslo roku ve století.
Pokud bylo pořadové
číslo roku ve století nenulové, nejedná se o století a
funkce se ukončí s nastaveným příznakem přechodného roku
CF. Jinak bude příznak přechodného roku nastaven pouze v
případě, že století bylo dělitelné číslem 4 (tj.
nejnižší 2 bity čísla století jsou nulové).
; -----------------------------------------------------------------------------
; Get last day in month
; -----------------------------------------------------------------------------
; INPUT: DL = month (1 to 12)
; CX = year (MINYEAR to MAXYEAR)
; OUTPUT: AL = last day in month (28, 29, 30, 31 or 0 on error)
; CY = invalid month or year (AL = 0)
; -----------------------------------------------------------------------------
; ------------- Push registers
GetLastDayMonth:push ebx ; push EBX
; ------------- Check month
mov al,dl ; AL <- month
dec al ; AL <- month relative (0 to 11)
cmp al,11 ; check maximal month
ja GetLastDayMon8 ; invalid month
; ------------- Check year
cmp cx,MINYEAR ; minimal year
jl GetLastDayMon8 ; invalid year
cmp cx,MAXYEAR ; maximal year
jg GetLastDayMon8 ; invalid year
; ------------- Get days in month (-> AL)
xor ebx,ebx ; EBX <- 0
call CheckLeapYear ; check leap year (CY = year)
setnc bl ; BL <- 0 leap year, 1 non-leap year
dec ebx ; EBX <- -1 leap year, 0 non-leap year
and ebx,DayMonthTabL-DayMonthTabN ; EBX <- table offset
add ebx,DayMonthTabN ; EBX <- leap/non-leap table pointer
xlatb ; AL <- days in month
; ------------- OK: Pop registers
clc ; clear error flag
pop ebx ; pop EBX
ret
; ------------- ERROR: Pop registers
GetLastDayMon8: mov al,0 ; AL <- 0, invalid month or year
stc ; set error flag
pop ebx ; pop EBX
ret
|
Funkce
GetLastDayMonth navrátí číslo posledního dne v měsíci. Na
vstupu funkce obsahuje registr DL číslo měsíce (v rozsahu 1
až 12) a registr CX rok (v rozsahu MINYEAR až MAXYEAR). Funkce
navrátí v registru AL poslední den v měsíci (v rozsahu 28
až 31). Je-li nastaven příznak CF, byl zadán neplatný
měsíc nebo rok a funkce navrátí v registru AL nulu.
Hodnota v registru AL
odpovídá počtu dnů v měsíci. Výjimkou je říjen 1582,
který končí 31. říjnem, ale má pouze 21 dnů, protože po
4. říjnu 1582 následuje 15. říjen 1582.
Na začátku funkce
jsou zkontrolovány rozsahy měsíce a roku. Jsou-li údaje mimo
povolený rozsah, funkce se předčasně ukončí s vynulovaným
registrem AL a nastaveným příznakem CF.
Provede se test
přechodnosti zadaného roku pomocí funkce CheckLeapYear.
Nejedná-li se o přechodný rok, navrací funkce CheckLeapYear
vynulovaný příznak CF. Podle stavu příznaku CF je do
registru EBX připravena tabulka obsahující poslední den
měsíce buď pro přechodný nebo pro nepřechodný rok. Hodnota
měsíce se převede pomocí této tabulky na poslední den v
měsíci a funkce se ukončí s vynulovaným příznakem CF.
; -----------------------------------------------------------------------------
; Get days in year
; -----------------------------------------------------------------------------
; INPUT: CX = year (MINYEAR to MAXYEAR)
; OUTPUT: AX = days in year (355, 365, 366 or 0 on error)
; CY = invalid year (AX = 0)
; -----------------------------------------------------------------------------
; ------------- Check year
GetDaysInYear: cmp cx,MINYEAR ; minimal year
jl GetDaysInYear8 ; invalid year
cmp cx,MAXYEAR ; maximal year
jg GetDaysInYear8 ; invalid year
; ------------- Get days in year (-> EAX, it clears CF)
mov ax,355 ; AX <- 355, days in year 1582
cmp cx,SPLITYEAR ; is it split year 1582?
je GetDaysInYear6 ; it is split year 1582
call CheckLeapYear ; check leap year
adc al,10 ; correction (it clears CF)
GetDaysInYear6: ret ; here is NC
; ------------- ERROR
GetDaysInYear8: xor ax,ax ; AX <- 0, invalid year
stc ; set error flag
ret
|
Funkce GetDaysInYear
navrátí v registru AX počet dnů v roce zadaném na vstupu
funkce v registru CX. Rok může mít rozsah hodnot MINYEAR až
MAXYEAR. Počet dnů v roce bude mít hodnoty 355 (rok 1582), 365
(nepřechodný rok) nebo 366 (přechodný rok). V případě
zadání neplatného roku bude při návratu z funkce nastaven
příznak CF a obsah registru AX bude nulový.
Nejdříve je
provedena kontrola platného údaje roku. Není-li rok platný,
funkce se ukončí s vynulovaným registrem AX a nastaveným
příznakem CF.
Je-li rok platný,
rozliší se speciální případ pro rok 1582 (355 dnů), jinak
bude navrácen podle přechodnosti roku (test funkcí
CheckLeapYear) hodnota 365 nebo 366 s vynulovaným příznakem
chyby CF.
; ------------- Error codes of "Check system time"
ERR_DT_NSEC EQU ERR_SYNTAX+10 ; invalid "nanosecond" entry
ERR_DT_SEC EQU ERR_SYNTAX+11 ; invalid "second" entry
ERR_DT_MIN EQU ERR_SYNTAX+12 ; invalid "minute" entry
ERR_DT_HOUR EQU ERR_SYNTAX+13 ; invalid "hour" entry
ERR_DT_DAY EQU ERR_SYNTAX+14 ; invalid "day in month" entry
ERR_DT_MONTH EQU ERR_SYNTAX+15 ; invalid "month" entry
ERR_DT_YEAR EQU ERR_SYNTAX+16 ; invalid "year" entry
; -----------------------------------------------------------------------------
; Check date-time structure
; -----------------------------------------------------------------------------
; INPUT: EBX = date-time structure (see DATETIME)
; OUTPUT: EAX = error code (ERR_OK or ERR_DT_* error codes)
; CY = invalid date or time entry
; -----------------------------------------------------------------------------
; ------------- Push registers
DateTimeCheck: push ebx ; push EBX
push ecx ; push ECX
push edx ; push EDX
; ------------- Check "nanosecond" entry
mov edx,ERR_DT_NSEC ; EDX <- error code "invalid nsec"
cmp dword [ebx+DATETIME_NSec],999999999 ; check value
ja DateTimeCheck8 ; invalid nanosecond
; ------------- Check "second" entry
mov eax,[ebx+DATETIME_Time] ; EAX <- time
inc edx ; EDX <- error code "invalid second"
cmp al,59 ; check maximal value
ja DateTimeCheck8 ; invalid second
; ------------- Check "minute" entry
inc edx ; EDX <- error code "invalid minute"
cmp ah,59 ; check maximal value
ja DateTimeCheck8 ; invalid minute
; ------------- Check "hour" entry
shr eax,16 ; AL <- hour
inc edx ; EDX <- error code "invalid hour"
cmp al,23 ; check maximal value
ja DateTimeCheck8 ; invalid hour
; ------------- Prepare year (-> CX), month (-> AH) and day in month (-> AL)
mov eax,[ebx+DATETIME_Date] ; EAX <- date
inc edx ; EDX <- error code "invalid day"
cmp al,0 ; check minimal day in month
je DateTimeCheck8 ; invalid day in month
shld ecx,eax,16 ; CX <- year
cmp cx,SPLITYEAR ; year 1582?
je DateTimeCheck4 ; check year 1582
; ------------- Check day in month
DateTimeCheck2: xchg al,ah ; AL <- month, AH <- day in month
xchg eax,edx ; DL <- month, DH <- day, EAX <- error
xchg eax,ebx ; EBX <- error
call GetLastDayMonth ; get last day in month -> AL
xchg eax,ebx ; EAX <- error, BL <- last day in month
xchg eax,edx ; AL <- month, AH <- day, EDX <- error
jc DateTimeCheck6 ; invalid month or year
cmp ah,bl ; check day in month
ja DateTimeCheck8 ; invalid day in month
; ------------- OK: Pop registers
DateTimeCheck3: xor eax,eax ; EAX <- clear error
pop edx ; pop EDX
pop ecx ; pop ECX
pop ebx ; pop EBX
ret
; ------------- Check invalid date range in year 1582
DateTimeCheck4: cmp ah,10 ; October 1582?
jne DateTimeCheck2 ; not October 1582
cmp al,5 ; start of invalid date interval
jb DateTimeCheck3 ; date is OK
cmp al,14 ; end of invalid date interval
ja DateTimeCheck2 ; it is not invalid date interval
jmp short DateTimeCheck8 ; error
; ------------- Invalid month or year
DateTimeCheck6: inc edx ; EDX <- error code "invalid month"
dec eax ; AL <- month - 1
cmp al,12 ; check maximal month (CY=invalid year)
adc edx,byte 0 ; EDX <- error code "invalid year"
; ------------- ERROR: Pop registers
DateTimeCheck8: xchg eax,edx ; EAX <- error code
stc ; set error flag
pop edx ; pop EDX
pop ecx ; pop ECX
pop ebx ; pop EBX
ret
|
Pomocí funkce
DateTimeCheck je možné otestovat platnost položek ve
struktuře DATETIME. Ukazatel na strukturu DATETIME je funkci
předán v registru EBX. Jsou-li položky struktury platné, je
navrácen vynulovaný příznak CF a registr EAX obsahuje kód
ERR_OK. Je-li rozsah položek neplatný, navrátí funkce
nastavený příznak CF a registr EAX bude obsahovat některý z
chybových kódů ERR_DT_*.
Na začátku funkce
jsou nejdříve zkontrolovány položky času - nanosekunda (0
až 999 999 999), sekunda (0 až 59), minuta (0 až 59) a hodina
(0 až 23).
Do registru EAX jsou
načteny položky data a do registru CX je připraven rok. Pro
rok 1582 (tj. rok přechodu Juliánského kalendáře na
kalendář Gregoriánský) je provedena speciální kontrola -
interval data 5.10.1582 až 14.10.1582 je zakázaný jako
neplatný.
Voláním funkce
GetLastDayMonth se zjistí číslo posledního dne v měsíci a
zkontroluje se platnost rozsahu dne v měsíci. Navrátí-li
funkce chybový kód CY, rozliší se kontrolou čísla měsíce,
zda chybu vyvolalo neplatné zadání roku nebo neplatný
měsíc.
Obsah / Utility / CALENDAR / Podpůrné funkce