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

Obsah / Utility / CALENDAR / Konverze absolutního času na datum a čas

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


Konverze absolutního času na datum a čas


; -----------------------------------------------------------------------------
;                     Convert absolute time to date-time
; -----------------------------------------------------------------------------
; INPUT:	EDX:EAX = absolute time (100-nanosec from 1/1/1 BCE 0:00)
;		EBX = date-time structure (see DATETIME)
; OUTPUT:	EBX structure filled up with date and time
; NOTES:	Only time and date entries (including day of week) are
;			returned. To get extended entries call DateTimeExt.
;		Takes 140 ns on 1.6 GHz CPU.
; -----------------------------------------------------------------------------

Funkce AbsToDateTime převede absolutní čas na datum a čas v rozloženém tvaru. Registr EBX obsahuje na vstupu funkce ukazatel na strukturu DATETIME. Registry EDX:EAX obsahují absolutní čas. Absolutní čas je násobkem 100 nanosekund od 0:00:00 hodin 1. ledna roku 1 n.l. Funkce nastaví pouze položky datum, čas a den v týdnu. Položky den v roce, číslo týdne a příznaky nejsou funkcí ovlivněny. Jsou-li potřebné i tyto doplňující informace, je možné dodatečně zavolat funkci DateTimeExt nebo namísto funkce AbsToDateTime zavolat přímo funkci AbsToDateTimeExt.

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

	b = 0;					// b=0 century for Julian
	c = date + 14610306;			// c=relative day to 3/1/-40000
	if (c >= (SPLITDATE+14610306))		// check date 10/15/1582
	{
		c = c + (14610004-14610306);	// c=relative day for Gregorian
		b = (c*4 + 3)/146097;		// b=century
						// 146097=days per 400 years
						//  = ((4*365+1)*25-1)*4+1
		c = c - (b*146097)/4;		// c=day in this century
	}

	d = (c*4 + 3)/1461;			// d=year in this century
						// 1461=days per 4 years
						//  = 4*365+1
	b = b*100 + d;				// b=year+40000
	c = c - (1461*d)/4;			// c=day in year (from March)
	d = (c*5 + 2)/153;			// d=month-3 (starting March)

	day = c - (153*d + 2)/5 + 1;		// day in this month
	if (d >= 10) e = 1; else e = 0;		// flag, month >= 10
	year = b - 40000 + e;			// year
	month = d + 3 + ((-12) & (-e));		// month

Na začátku se připraví proměnné pro Juliánský kalendář. Proměnná "b" obsahuje přepočet pro století, u Juliánsklého kalendáře se neuplatní a proto je vynulována. Proměnná "c" obsahuje počet dnů od 1. března roku 40000 př.n.l. Všechny výpočty probíhají tak, jako by byl přelom roku mezi únorem a březnem, tím se výpočty zjednoduší, protože přechodný den připadá vždy na konec roku.

Je-li datum SPLITDATE nebo vyšší, jedná se o Gregoriánský kalendář. Opraví se počet dnů v proměnné "c". Do proměnné "b" se připraví číslo století. Odečtením počtu dnů připadajících na celá staletí zůstane v proměnné "c" číslo dne v aktuálním století.

Do proměnné "d" se připraví číslo roku v aktuálním století. Součtem století*100 a roku ve století se připraví do proměnné "b" číslo aktuálního roku (s přičteným offsetem 40000). Odečtením dnů připadajících na celé roky od počtů dnů v proměnné "c" se vypočte číslo dne od pomocného počátku roku (tedy od 1. března). Den v roce se přepočte podle vztahu "(c*5 + 2)/153" na číslo měsíce v roce, opět s počátkem v březnu. Jedná se o celočíselné dělení, zbytek po dělení se ignoruje.

Nyní se již může zjistit číslo dne v měsíci - od čísla dne v roce v proměnné "c" se odečte počet dnů od začátku roku pro aktuální měsíc podle vztahu "(153*d + 2)/5".

Do pomocné proměnné "e" se připraví pomocný příznak indikující, zda se jedná o poslední 2 měsíce roku (vztaženo k pomocnému počátku roku v březnu, takže se tedy jedná o leden nebo únor) nebo o prvních 10 měsíců (tedy březen až prosinec). Příznak se použije ve výpočtu čísla roku - od čísla roku "b" se odečte počáteční offset 40000 (který byl potřebný na převod data do kladných čísel) a provede se doplňující oprava čísla roku příčtením korekce "e".

Číslo měsíce "d" se převede na kalendářní měsíc posunem o 3 a odečtením korekce 12 v případě posledních 2 měsíců.


; On whole function: ESI = date-time structure

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

AbsToDateTime:	push	eax		; push EAX
		push	ebx		; push EBX
		push	ecx		; push ECX
		push	edx		; push EDX
		push	esi		; push ESI
		push	edi		; push EDI
		mov	esi,ebx		; ESI <- date-time structure

; ------------- Convert time to positive value (-> EDX:EAX)

		sub	eax,MINTIMEL	; convert to positive value LOW
		sbb	edx,MINTIMEH	; convert to positive value HIGH

Na začátku funkce jsou uchovány použité registry, do registru ESI je připraven ukazatel na strukturu DATETIME a odečtením minimální hodnoty času MINTIMEL a MINTIMEH je čas převeden na kladné číslo vztažené k minimálnímu povolenému času.


; ------------- Split time to minutes (-> EDI:EBX) and 100-nanoseconds (-> EDX)

		xchg	eax,ebx		; EBX <- save time LOW
		xor	eax,eax		; EAX <- 0
		mov	ecx,60*10000000 ; ECX <- 100-nanoseconds per minute
		xchg	eax,edx		; EAX <- time HIGH, EDX <- 0
		div	ecx		; split to minutes HIGH
		xchg	eax,edi		; EDI <- minutes HIGH
		xchg	eax,ebx		; EAX <- time LOW
		div	ecx		; split to minutes LOW
		xchg	eax,ebx		; EBX <- minutes LOW

Absolutní čas je vydělením číslem 60*10000000 rozdělen na stovky nanosekund v minutě (v registru EDX) a minuty (uloženo do registrů EDI:EBX). Minuty mohou být 64-bitovým číslem, proto je dělení provedeno ve 2 krocích (vyšší a nižší dvojslovo).


; ------------- Split 100-nanoseconds to nanosecond and second

		mov	ecx,edx		; ECX <- 100-nanoseconds
		mov	eax,1801439851	; EAX <- 40 0000 0000 0000h/10000000
		mul	ecx		; EDX:EAX <- (100-nsec/10000000) << 54
		shr	edx,22		; EDX <- second
		mov	[esi+DATETIME_Sec],dl ; store second
		imul	edx,edx,10000000 ; EDX <- recalc second to 100-nsec
		sub	ecx,edx		; ECX <- 100-nanoseconds
		imul	eax,ecx,100	; EAX <- nanoseconds
		mov	[esi+DATETIME_NSec],eax ; store nanosecond

Stovky nanosekund v minutě v registru EDX budou vydělením číslem 10000000 rozloženy na sekundy a stovky nanosekund v sekundě. Namísto operace dělení je provedena rychlejší operace násobení převrácenou hodnotou, tj. číslem 40 0000 0000 0000h/10000000 (zaokrouhleno nahoru). Po vynásobení a opravě výsledku rotací o 22 bitů doprava bude v registru EDX počet sekund, který se uloží do struktury DATETIME. Počet sekund se vynásobí číslem 10000000, tím se převede zpět na stovky nanosekund, odečtením od výchozího počtu stovek nanosekund se získá zbytek stovek nanosekund v sekundě. Po vynásobení číslem 100 se uloží číslo nanosekundy do struktury DATETIME.


; ------------- Split minutes to minutes (-> EDX) and days (-> EAX)

		mov	edx,edi		; EDX <- minutes HIGH
		xchg	eax,ebx		; EAX <- minutes LOW
		mov	ecx,24*60	; ECX <- minutes per day
		div	ecx		; split to minutes and days

Minuty v registrech EDI:EBX jsou vydělením číslem 24*60 rozloženy na dny (registr EAX) a minuty v jednom dni (registr EDX).


; ------------- Split minutes to minute and hour

		imul	ecx,edx,279621	; ECX <- (minutes/60) * 1000000h
		shr	ecx,24		; ECX <- hour
		mov	[esi+DATETIME_Hour],cl ; store hour
		imul	ecx,ecx,60	; ECX <- recalc hour to minutes
		sub	edx,ecx		; EDX <- minute
		mov	[esi+DATETIME_Min],dl ; store minute

Minuty v jednom dni (v registru EAX) budou rozděleny na hodiny a minuty v hodině. Namísto dělení číslem 60 se použije rychlejší operace násobení převrácenou hodnotou (tj. číslem 1000000h/60, zaokrouhleno nahoru). Po vynásobení a opravě výsledku rotací o 24 bitů doprava bude počet hodin (v registru CL) uložen do struktury DATETIME. Vynásobením hodin číslem 60 a odečtením od původního počtu minut ve dni se získá počet minut v hodině, ten se též uloží do struktury DATETIME.


; ------------- Get day of week (number of days -> ECX)

		lea	ebx,[eax+4]	; EBX <- number of days + correction
		xchg	eax,ecx		; ECX <- save number of days
		mov	eax,613566757	; EAX <- 100000000h/7 round up
		mul	ebx		; EDX <- number of weeks
		imul	eax,edx,7	; EAX <- number of weeks * 7
		sub	ebx,eax		; EBX <- day of week
		inc	ebx		; EBX <- day of week starting 1
		mov	[esi+DATETIME_WDay],bl ; store day of week

Abychom zjistili číslo dne v týdnu, připravíme si do registru EBX číslo absolutního dne s přičtenou korekcí 4 dnů. Toto číslo budeme dělit číslem 7. Namísto dělení použijeme rychlejší operaci násobení převrácenou hodnotou čísla, tedy číslem 100000000h/7 (zaokrouhleno nahoru). V registru EDX obdržíme absolutní číslo týdne. Vynásobením zpět číslem 7 a odečtením od původního počtu dnů obdržíme číslo dne v týdnu, které uložíme do struktury DATETIME.


; ------------- Prepare Julian calendar (here is ECX = number of days)

		xor	ebx,ebx		; EBX <- b=0 century for Julian
		add	ecx,14610306+MINDAY ; ECX <- c=relative day 3/1/-40000

Připravíme proměnné pro Juliánský kalendář - proměnná "b" (v registru EBX) představující číslo století bude nulová, proměnná "c" (v registru ECX) s číslem absolutního dne se opraví posunem do kladných oblastí. Současně se odečte minimální den kalendáře, aby se opravil posun získaný na začátku funkce AbsToDateTime.


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

		cmp	ecx,SPLITDATE+14610306 ; check date 10/15/1582
		jl	AbsToDateTime2	; Julian calendar

; ------------- Prepare Gregorian calendar

		add	ecx,14610004-14610306 ; ECX <- c=relative day Gregorian
		lea	ebx,[ecx*4+3]	; EBX <- c*4+3
		mov	eax,963315389	; EAX <- 8000 0000 0000h / 146097
		mul	ebx		; EDX:EAX <- ((c*4+3)/146097) << 47
		shr	edx,15		; EDX <- b=(c*4+3)/146097 century
		mov	ebx,edx		; EBX <- b=(c*4+3)/146097 century
		imul	eax,edx,146097	; EAX <- b*146097
		shr	eax,2		; EAX <- (b*146097)/4
		sub	ecx,eax		; ECX <- c=c-(b*146097)/4 day in cent.

Nyní se provede příprava pro Gregoriánský kalendář (datum 15. října 1582 a vyšší). Opraví se počet dnů v proměnné "c" (v registru ECX) podle Gregoriánského kalendáře (10 dnů skok v datu a oprava rozdílně započítaných přechodných staletí). Do proměnné "b" (registr EBX) se připraví číslo století ("b=(c*4 + 3)/146097)") - namísto dělení číslem 146097 se použije rychlejší operace násobení převrácenou hodnotou (tj. 8000 0000 0000h/146097, zaokrouhleno nahoru). Po rotaci o 15 bitů doprava obdržíme číslo století. Po přepočtu století zpět na dny a odečtením od původního čísla dne ("c = c - (b*146097)/4") obdržíme číslo dne v aktuálním století (s počátkem roku 1. března).


; ------------- Prepare year in century (-> EDX = "d")

AbsToDateTime2:	lea	edx,[ecx*4+3]	; EDX <- c*4+3
		mov	eax,376287347	; EAX <- 80 0000 0000h / 1461
		mul	edx		; EDX:EAX <- ((c*4+3)/1461) << 39
		shr	edx,7		; EDX <- d=(c*4+3)/1461 year in century

Pokračuje část společná pro oba kalendáře. Do proměnné "d" (registr EDX) připravíme číslo dne ve století ("d = (c*4 + 3)/1461"). Namísto dělení použijeme rychlejší operaci násobení převrácenou hodnotou (tj. číslem 80 0000 0000h/1461, zaokrouhleno nahoru), výsledek v registru EDX opravíme rotací o 7 bitů doprava.


; ------------- Prepare year+40000 (-> EBX = "b")

		imul	ebx,ebx,100	; EBX <- b*100
		add	ebx,edx		; EBX <- b=b*100+d, year+40000

Přepočtem století na roky a přičtením roku ve století obdržíme číslo roku, zatím s přičteným počátkem 40000 ("b = b*100 + d").


; ------------- Prepare day in year (relative to March, -> ECX = "c")

		imul	edx,edx,1461	; EDX <- 1461*d
		shr	edx,2		; EDX <- (1461*d)/4
		sub	ecx,edx		; ECX <- c=c-(1461*d)/4 day in year

Do proměnné "c" (registr ECX) připravíme číslo dne v roce s počátkem 1. března s použitím dne ve století ("c = c - (1461*d)/4").


; ------------- Prepare month-3 (relative to March, -> EDX = "d")

		lea	edx,[ecx*4+ecx+2] ; EDX <- c*5+2
		mov	eax,3593175255	; EAX <- 80 0000 0000h / 153 
		mul	edx		; EDX:EAX <- ((c*5+2)/153) << 39
		shr	edx,7		; EDX <- d=(c*5+2)/153 month-3

Do proměnné "d" (registr EDX) připravíme číslo měsíce v roce s počátkem v březnu ("d = (c*5 + 2)/153"). Namísto dělením číslem 153 použijeme rychlejší operaci násobení převrácenou hodnotou (číslem 80 0000 0000h/153, zaokrouhleno nahoru). Výsledek obdržíme v registru EDX po rotaci o 7 bitů doprava.


; ------------- Calculate day in month (-> CL)

		mov	edi,edx		; EDI <- save "d"
		imul	edx,edx,153	; EDX <- 153*d
		inc	edx		; EDX <- 153*d+1
		inc	edx		; EDX <- 153*d+2
		mov	eax,858993460	; EAX <- 100000000h/5
		mul	edx		; EDX <- (153*d+2)/5
		sub	ecx,edx		; ECX <- c-(153*d+2)/5
		inc	ecx		; ECX <- day=c-(153*d+2)/5+1 day

Při výpočtu dne v měsíci přepočteme nejdříve číslo měsíce na počet dnů od pomocného začátku roku (kterým je 1. březen, "(153*d + 2)/5"). Namísto dělení číslem 5 použijeme rychlejší násobení převrácenou hodnotou (tj. číslem 100000000h/5, zaokrouhleno nahoru).


; ------------- Calculate year (add to ECX)

		cmp	edi,10		; check if month >= 10
		sbb	eax,eax		; EAX <- 0 if month >= 10, -1 if < 10
		inc	eax		; EAX <- e, if(d>=10) e=1 else e=0
		lea	ebx,[ebx-40000+eax] ; EBX <- year
		shl	ebx,16		; EBX <- year << 16
		or	ecx,ebx		; ECX <- add year

Při výpočtu roku si připravíme nejdříve pomocný příznak indikující, zda měsícem je leden nebo únor (číslo měsíce je 10 nebo 11, "if (d >= 10) e = 1; else e = 0;"). Příznak použijeme ve výpočtu čísla roku ("year = b - 40000 + e").


; ------------- Calculate month

		neg	eax		; EAX <- -e
		and	eax,byte -12	; EAX <- (-12)&(-e)
		lea	eax,[edi+3+eax]	; EAX <- month=d+3-((-12)&(-e))
		mov	ch,al		; CH <- month
		mov	[esi+DATETIME_Date],ecx ; store date

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

		pop	edi		; pop EDI
		pop	esi		; pop ESI
		pop	edx		; pop EDX
		pop	ecx		; pop ECX
		pop	ebx		; pop EBX
		pop	eax		; pop EAX
		ret

Nakonec vypočteme číslo měsíce ("month = d + 3 + ((-12) & (-e))") a výsledné datum uložíme do struktury DATETIME.


Obsah / Utility / CALENDAR / Konverze absolutního času na datum a čas