Tvůrce webu je i pro tebe! Postav třeba web. Bez grafika. Bez kodéra. Hned.
wz

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