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

Obsah / Utility / CALENDAR / Konverze data na absolutní den

Zdrojový kód: INCLUDE\UTIL\CALENDAR.INC, UTIL\CALENDAR.ASM


Konverze data na absolutní den


; -----------------------------------------------------------------------------
;                       Convert date to absolute day
; -----------------------------------------------------------------------------
; INPUT:	AL = day (1..31, may be -128 to +127)
;		CX = year (MINYEAR to MAXYEAR)
;		DL = month (1..12, may be -128 to +127)
; OUTPUT:	EAX = absolute day (offset to 1/1/1 CE, MINDAY to MAXDAY)
; NOTES:	Input entries are signed and their overlaps are accounted.
; 		Takes 25 ns on 1.6 GHz CPU.
; -----------------------------------------------------------------------------

Funkce DateToAbsDay vypočítá absolutní den pro datum zadané na vstupu funkce. Vstupními parametry funkce jsou: v registru AL číslo dne v měsíci (rozsah 1 až 31, ale může být -128 až +127, při překročení hranic se opraví číslo měsíce), v registru DL číslo měsíce (rozsah 1 až 12, ale může být -128 až +127, při překročení hranic se opraví číslo roku) a v registru CX číslo roku (v rozsahu MINYEAR až MAXYEAR po případné opravě přetečení měsíce). Funkce navrátí v registru EAX absolutní den, což je den vztažený k počátku letopočtu (tj. datum 1.1.1 n.l.). Absolutní den může mít rozsah hodnot MINDAY až MAXDAY.

Konverze se provádí podle následujícího symbolického zápisu:

	m = month - 1;
	if (m >= 2) a = 0; else a = -1;
	y = year + a + 40000;
	m = m + (12 & a) - 2;
	date = day + (153*m + 2)/5 + y*365;
	y = y/4;
	date = date + y - 14610307;
	if (date >= SPLITDATE)
	{
		y = y / 25;
		date = date - y + y/4 + 302;
	}

Na začátku obsahuje proměnná "m" číslo měsíce s počátkem v nule (tj. hodnoty 0 až 11). Podle čísla měsíce se připraví v proměnné "a" příznak indikující, zda měsícem je leden až únor nebo březen až prosinec. Rok se přičtením konstanty 40000 převede na kladné číslo s počátkem dělitelným 400 a s korekcí pro měsíce lede a únor. Všechny výpočty probíhají totiž tak, jako by byl přelom roku mezi únorem a březnem. Proto se i měsíc proměnné "m" upraví na offset od měsíce března (tj. březen=0, duben=1, ... prosinec=9, leden=10, únor=11).

Celočíselnou operací (která neuchovává zbytek po dělení) "(153*m + 2)/5" se vypočte offset měsíce (počet dnů) od pomocného počátku roku (kterým je 1. březen). Přičtením dne v měsíci a přičtením násobku 365*rok se zjistí absolutní den, tedy počet dnů od března roku -40000, zatím bez korekce přechodných let.

Přičtením 1/4 čísla roku se provede korekce přechodných let pro Juliánský kalendář. Odečtením 14610307 dnů se číslo dne převede zpět na offset od počátku letopočtu.

Pro den SPLITDATE a vyšší je potřeba provést další korekce pro Gregoriánský kalendář. Odečtením 1/100 čísla roku se odečtou dny pro nepřechodná staletí, příčtením 1/400 čísla roku se přičtou dny pro přechodná 400-letí, tedy století dělitelná číslem 4. Přičtením hodnoty 302 se opraví chyba rozdílu data 10 dnů a nesprávně odečtených nepřechodných staletí do roku 1582.


; ------------- Push registers

DateToAbsDay:	push	ebx		; push EBX
		push	ecx		; push ECX
		push	edx		; push EDX

; ------------- Extend input entries (EAX <- month, EBX <- day, ECX <- year)

		movsx	ebx,al		; EBX <- day
		movsx	eax,dl		; EAX <- month
		movsx	ecx,cx		; ECX <- year

Po úschově registrů jsou do registrů připraveny vstupní parametry funkce (EAX=měsíc, EBX=den v měsíci, ECX=rok).


; ------------- Normalize month (EAX) and year (ECX)

		dec	eax		; EAX <- change month to zero-based
DateToAbsDay2:	cmp	eax,byte 12	; is it valid value?
		jae	DateToAbsDay6	; invalid value


...............................


; ------------- Normalize month (-> EAX) and year (-> ECX)

DateToAbsDay6:	or	eax,eax		; is month negative?
                jns	DateToAbsDay8	; month is not negative
		add	eax,byte 12	; correct month
		dec	ecx		; correct year
		jmp	DateToAbsDay2	; next year	

DateToAbsDay8:	sub	eax,byte 12	; correct month
		inc	ecx		; correct year
		jmp	DateToAbsDay2	; next year

Ke správnému provádění výpočtů je nutné číslo měsíce nejdříve normalizovat, tj. uvést do rozsahu 0 až 11 (po snížení o 1). Ve většině případů nebude normalizace nutná, proto se provádí větví ležící mimo hlavní funkci, aby při běžném rozsahu hodnot nedocházelo ke zpomalování hlavní funkce. Pokud je hodnota měsíce menší než nula, přičítá se k měsíci číslo 12 a číslo roku se snižuje o 1. Je-li měsíc naopak větší než 11, odečítá se od měsíce číslo 12 a číslo roku se zvyšuje o 1.


; ------------- Prepare month flag (if month is 2 or more, -> EDX)

		cmp	al,2		; check (month-1) 0 or 1
		sbb	edx,edx		; EDX <- -1=(month-1)<2, 0=(month-1)>=2

Do registru EDX se připraví příznak, že měsícem je leden nebo únor ("if (m >= 2) a = 0; else a = -1;"). Porovnáním čísla měsíce (v registru AL) sníženého o 1 s hodnotou 2 se nastaví příznak CF v případě, že měsícem je leden nebo únor (tj. měsíc-1 je 0 nebo 1). Další instrukce nastaví registr EDX na hodnotu -1 v případě měsíce leden nebo únor, pro ostatní měsíce bude obsah registru EDX nulový.


; ------------- Year correction (-> ECX)

		lea	ecx,[ecx+edx+40000] ; ECX <- convert year to positive

Rok v registru ECX se upraví na kladné číslo přičtením offsetu 40000 a příznaku měsíce ledna a února ("y = year + a + 40000;").


; ------------- Month correction (-> EDX)
; Values: 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

		and	edx,byte 12	; EDX <- 12=month 1 or 2, 0=month > 2
		add	edx,eax		; EDX <- add month - 1
		dec	edx		; EDX <- month correction
		dec	edx		; EDX <- month correction

Měsíc v registru EDX se upraví na offset od měsíce března ("m = m + (12 & a) - 2;"). Maskováním příznaku měsíce ledna a února číslem 12 obdržíme číslo 12 pro měsíce leden a únor, pro ostatní měsíce nám zůstane nula.


; ------------- Recalc month to days (-> EBX)
; Values: 306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275)

		imul	eax,edx,153	; EAX <- month coefficient * 153
		inc	eax		; EAX + 1
		inc	eax		; EAX + 2
		imul	edx,eax,13108	; EDX <- days * 10000h
		shr	edx,16		; EDX <- days

Nyní přepočteme číslo měsíce na počet dnů od 1. března ("(153*m + 2)/5"). Měsíc v registru EDX vynásobíme konstantou 153 a přičteme 2. Namísto dělení číslem 5 použijeme rychlejší operaci násobení převrácenou hodnotou čísla, tj. číslem 10000h/5 (zaokrouhleno nahoru). Rotací výsledku o 16 bitů doprava obdržíme požadovaný počet dnů od 1. března.


; ------------- Add day in month (-> EDX)

		add	edx,ebx		; EDX <- add day in month

; ------------- Recalc years to days (without leap correction, -> EAX)

		imul	eax,ecx,365	; EAX <- days of years
		add	eax,edx		; EAX <- add days in month and day

Přičtením čísla dne v měsíci a přičtením 365-násobku čísla roku obdržíme absolutní číslo dne bez korekce přechodných let ("date = day + (153*m + 2)/5 + y*365;").


; ------------- Add leap year correction for Julian calendar

		shr	ecx,2		; ECX <- year / 4
		add	eax,ecx		; EAX <- add days in leap years
		sub	eax,14610307	; EAX <- start offset of calendar

Přičtením 1/4 čísla roku (dny pro přechodné roky) a odečtením počátečního offsetu kalendáře obdržíme absolutní den vztažený k počátku letopočtu pro Juliánský kalendář ("y = y/4; date = date + y - 14610307;").


; ------------- Check if it is Julian or Gregorian calendar

		cmp	eax,SPLITDATE	; check date 10/15/1582
		jl	DateToAbsDay4	; it is Julian calendar

; ------------- Do correction for Gregorian calendar

		imul	edx,ecx,167773	; EDX <- (year/100) * 400000h
		shr	edx,22		; EDX <- year/100
		sub	eax,edx		; EAX <- subtract century correction
		shr	edx,2		; EDX <- century / 4
		add	eax,edx		; EAX <- add 400-years correction
		add	eax,302		; EAX <- start offset of calendar

; ------------- Pop registers

DateToAbsDay4:	pop	edx		; pop EDX
		pop	ecx		; pop ECX
		pop	ebx		; pop EBX
		ret

Testem hodnoty data rozlišíme, zda se jedná o Gregoriánský kalendář ("if (date >= SPLITDATE)"). Pokud ano, připravíme si 1/100 čísla roku, přičemž víme, že rok již máme vydělený číslem 4 ("y = y / 25;"). Namísto dělení číslem 25 použijeme rychlejší násobení převrácenou hodnotou, tj. číslem 400000h/25 (zaokrouhleno nahoru). Výsledek obdržíme v registru EDX a rotací o 22 bitů doprava získáme 1/100 čísla roku, tedy číslo století (opět s přelomem let po měsíci únoru). Odečtením čísla století, přičtením 1/4 čísla století a příčtením opravného koeficientu 302 obdržíme výsledný absolutní den Gregoriánského kalendáře.


Obsah / Utility / CALENDAR / Konverze data na absolutní den