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

Obsah / Utility / CALENDAR / Výpočet rozšířených položek kalendáře

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


Výpočet rozšířených položek kalendáře


; -----------------------------------------------------------------------------
;                       Get calendar extended entries
; -----------------------------------------------------------------------------
; INPUT:	EBX = date-time structure with valid date (see DATETIME)
; OUTPUT:	Extended entries and day of week of EBX structure are filled.
; NOTES:	Date entries (excluding day of week) of EBX structure
;			should be valid or else result will be unpredictable.
;		Takes 90 ns on 1.6 GHz CPU.
; -----------------------------------------------------------------------------

Funkce DateTimeExt vygeneruje rozšířené položky struktury DATETIME - tj. den v roce, číslo týdne, příznaky - včetně dne v týdnu. Vstupními daty funkce jsou položky data (den, měsíc, rok) ve struktuře DATETIME, na kterou ukazuje ukazatel v registru EBX. Datum by mělo být platné (funkce neřeší přetékání položek do vyšších řádů), jinak bude výsledek nedefinovaný.


; On whole function: ESI = date-time structure

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

DateTimeExt:	push	eax		; push EAX
		push	ebx		; push EBX
		push	ecx		; push ECX
		push	edx		; push EDX
		push	esi		; push ESI
		push	edi		; push EDI

; ------------- Clear flags

		mov	esi,ebx		; ESI <- date-time structure
		mov	byte [esi+DATETIME_Flags],0 ; clear flags

; ------------- Get current date (EAX <- day, EDX <- month, ECX <- year)

		mov	ecx,[esi+DATETIME_Date] ; ECX <- date
		mov	al,cl		; AL <- day
		mov	dl,ch		; DL <- month
		sar	ecx,16		; CX <- year

Po úschově registrů je to registru ESI připraven ukazatel na strukturu DATETIME (ukazatel zůstává platný po celou dobu funkce) a jsou vynulovány příznaky ve struktuře DATETIME. Ze struktury DATETIME je načteno datum a uloženo do registrů AL (den), DL (měsíc) a CX (rok).


; ------------- Set leap year flag

		call	CheckLeapYear	; check if year is a leap year
		adc	byte [esi+DATETIME_Flags],0 ; set leap year flag

S využitím funkce CheckLeapYear je připraven příznak přestupnosti roku. Funkce CheckLeapYear testuje rok v registru CX a pokud je přestupný, navrátí nastavený příznak CY. Příznakem ve struktuře DATETIME je bit 0, proto může být nastaven instrukcí ADC.


; ------------- Convert date to absolute day (-> EAX)

		call	DateToAbsDay	; recalc date to absolute day

Datum v registrech AL, DL a CX je překonvertováno funkcí DateToAbsDay na absolutní den od 1. ledna roku 1 n.l. (navráceno v registru EAX).


; ------------- Set Gregorian calendar flag

		cmp	eax,SPLITDATE	; check split day 10/15/1582
		xchg	eax,ebx		; EBX <- absolute day
		setge	al		; AL <- 1 if date is greater or equal
		shl	al,1		; AL <- B1 if Gregorian calendar
		or	[esi+DATETIME_Flags],al ; set Gregorian calendar flag

Porovnáním absolutního dne se SPLITDATE, což je datum 15. října 1582, kdy začal platit Gregoriánský kalendář, je rozlišen Juliánský a Gregoriánský kalendář a nastaven příslušný bit v příznacích.


; ------------- Day of week

		lea	edi,[ebx+4-MINDAY] ; EDI <- day + correction
		mov	eax,613566757	; EAX <- 100000000h/7 round up
		mul	edi		; EDX <- number of weeks
		imul	eax,edx,-7	; EAX <- number of weeks * -7
		add	eax,edi		; EAX <- day of week
		inc	eax		; EAX <- day of week starting 1
		mov	[esi+DATETIME_WDay],al ; store day of week
		xchg	eax,edi		; EDI <- day of week

Číslo dne v týdnu se zjistí podobně jako ve funkci AbsToDateTime. Do registru EDI se připraví číslo absolutního dne s přičtenou korekcí 4 dnů. Toto číslo se bude dělit číslem 7, ale namísto dělení se použije rychlejší operace násobení převrácenou hodnotou čísla, tedy číslem 100000000h/7 (zaokrouhleno nahoru). V registru EDX bude po násobení absolutní číslo týdne. Vynásobením zpět číslem 7 a odečtením od původního počtu dnů zůstane v registru EAX číslo dne v týdnu, který se uloží do struktury DATETIME.


; ------------- Get day of January 1st of this year (-> EAX)

		mov	al,1		; AL <- day 1
		mov	dl,1		; DL <- month 1
		call	DateToAbsDay	; get absolute day of start of year

; ------------- Day in year

		sub	ebx,eax		; EBX <- day in year
		mov	[esi+DATETIME_YDay],bx ; set day in year

Pomocí funkce DateToAbsDay se zjistí absolutní číslo prvního dne v roce, tj. datum se stejným rokem, ale den a měsíc mají číslo 1. Odečtením absolutního čísla současného dne od prvního dne v roce se obdrží číslo dne v roce a to se uloží do struktury DATETIME.


; ------------- Prepare day of week - 1 of January 4th (-> EBX)

		sub	edi,ebx		; EDI <- day of week - day in year
		add	edi,2+53*7	; EDI <- correction
		mov	eax,613566757	; EAX <- 100000000h/7 round up
		mul	edi		; EDX <- number of weeks
		imul	eax,edx,-7	; EAX <- number of weeks * -7
		add	eax,edi		; EAX <- day of week of January 4th
		xchg	eax,ebx		; EBX <- day of week of January 4th

Při výpočtu čísla týdne potřebujeme znát den v týdnu, který připadá na datum 4. ledna. Podle definice ISO-8601 je prvním týdnem roku týden obsahující 4. leden (týden obsahující první čtvrtek roku). Při výpočtu dne v týdnu připadajícího na 4. ledna se využije den v týdnu aktuálního dne a číslo aktuálního dne v roce. Po odečtení čísla aktuálního dne ode dne v týdnu aktuálního dne se získá den v týdnu prvního dne v roce (neopravený operací modulo). Přičtením 2 se obdrží neopravený den v týdnu pro 4. ledna. Přičtením 53*7 se údaj posune do oblasti kladných hodnot. Vynásobením převrácenou hodnotou 7, zpětným přepočtem na dny a odečtením od původní hodnoty se obdrží číslo dne v týdnu pro 4. ledna (registr EBX) s počátkem v 0 (tj. 0=pondělí, ... 6=neděle).


; ------------- Prepare number of weeks in this year

		mov	bh,52		; BH <- 52 weeks
		cmp	bl,7-1		; is it Sunday?
		je	DateTimeExt2	; Sunday - this year has 53 weeks
		cmp	bl,6-1		; is it Saturday?
		jne	DateTimeExt3	; this year has 52 weeks
		test	byte [esi+DATETIME_Flags],B0 ; is it leap year?
		jz	DateTimeExt3	; it is not leap year, it has 52 weeks
DateTimeExt2:	or	byte [esi+DATETIME_Flags],B2 ; set flag 53 weeks
		inc	bh		; BH <- 53 weeks

Dále je zapotřebí znát počet týdnů v roce, abychom mohli rozpoznat přetečení konce roku do prvního týdne následujícího roku. K tomu poslouží následující tabulky představující možné varianty začátků roku.

Nepřestupný rok:

Začátek roku Offset 1.
týdne od
začátku
roku
1.1. je
v prvním
týdnu
roku
Délka roku
(365 dnů)
Konec roku 31.12. je
v posledním
týdnu
roku
Počet týdnů
Po Út St Čt So Ne Po Út St Čt So Ne
            1. 1 ne 1 + 51*7 + 7 25. 26. 27. 28. 29. 30. 31. ano 52
          1. 2. 2 ne 2 + 51*7 + 6 26. 27. 28. 29. 30. 31. 1. ano 52
        1. 2. 3. 3 ne 3 + 51*7 + 5 27. 28. 29. 30. 31. 1. 2. ano 52
      1. 2. 3. 4. -3 ano 4 + 51*7 + 4 28. 29. 30. 31. 1. 2. 3. ano 53
    1. 2. 3. 4. 5. -2 ano 5 + 51*7 + 3 29. 30. 31. 1. 2. 3. 4. ne 52
  1. 2. 3. 4. 5. 6. -1 ano 6 + 51*7 + 2 30. 31. 1. 2. 3. 4. 5. ne 52
1. 2. 3. 4. 5. 6. 7. 0 ano 7 + 51*7 + 1 31. 1. 2. 3. 4. 5. 6. ne 52

Přestupný rok:

Začátek roku Offset 1.
týdne od
začátku
roku
1.1. je
v prvním
týdnu
roku
Délka roku
(366 dnů)
Konec roku 31.12. je
v posledním
týdnu
roku
Počet týdnů
Po Út St Čt So Ne Po Út St Čt So Ne
            1. 1 ne 1 + 52*7 + 1 31. 1. 2. 3. 4. 5. 6. ne 52
          1. 2. 2 ne 2 + 51*7 + 7 25. 26. 27. 28. 29. 30. 31. ano 52
        1. 2. 3. 3 ne 3 + 51*7 + 6 26. 27. 28. 29. 30. 31. 1. ano 52
      1. 2. 3. 4. -3 ano 4 + 51*7 + 5 27. 28. 29. 30. 31. 1. 2. ano 53
    1. 2. 3. 4. 5. -2 ano 5 + 51*7 + 4 28. 29. 30. 31. 1. 2. 3. ano 53
  1. 2. 3. 4. 5. 6. -1 ano 6 + 51*7 + 3 29. 30. 31. 1. 2. 3. 4. ne 52
1. 2. 3. 4. 5. 6. 7. 0 ano 7 + 51*7 + 2 30. 31. 1. 2. 3. 4. 5. ne 52

Rok 1582:

Začátek roku Offset 1.
týdne od
začátku
roku
1.1. je
v prvním
týdnu
roku
Délka roku
(355 dnů)
Konec roku 31.12. je
v posledním
týdnu
roku
Počet týdnů
Po Út St Čt So Ne Po Út St Čt So Ne
1. 2. 3. 4. 5. 6. 7. 0 ano 7 + 49*7 + 5 27. 28. 29. 30. 31. 1. 2. ano 51

Jak je vidět z tabulek, ve většině případů má rok 52 týdnů (přednastaveno do registru BH). Je-li 4.1. neděle, má rok vždy 53 týdnů. Ostatní dny, kromě soboty, mají vždy 52 týdnů. V případě soboty má rok 53 týdnů tehdy, jedná-li se o přestupný rok. Pro rok 1582 patří 32.12. do posledního týdne stejného roku, není proto potřeba připravovat skutečný počet týdnů.


; ------------- Day offset (from begin of year) of first week (-> EAX)

DateTimeExt3:	mov	al,3		; AL <- day correction
		sub	al,bl		; AL <- day offset of first week
		movsx	eax,al		; EAX <- day offset of first week

Do registru EAX se připraví offset prvního týdne roku od začátku roku s využitím dne v týdnu 4. ledna (jak je vidět z tabulek).


; ------------- Get week (first week is week with January 4th, ISO-8601)

		movzx	edx,word [esi+DATETIME_YDay] ; EDX <- day in year
		sub	edx,eax		; EDX <- distance from first week
		add	edx,byte 7	; EDX <- add correction
		mov	eax,613566757	; EAX <- 100000000h/7 round up
		mul	edx		; EDX <- week

Do registru EDX se připraví číslo dne v roce. Odečtením offsetu prvního týdne v roku se získá offset tohoto dne od začátku prvního týdne v roku. Přičtením čísla 7 se offset posune do kladných hodnot a zajistí se, že první týden v roce bude mít číslo 1. Vydělením číslem 7 se vypočte číslo týdne. Namísto operace dělení se použije rychlejší operace násobení převrácenou hodnotou čísla, tedy 100000000h/7 (zaokrouhleno nahoru). Číslo týdne bude v registru EDX.


; ------------- Check last week of previous year (only for January 1,2,3)

		or	dl,dl		; is it last week of previous year?
		jnz	DateTimeExt5	; it is not last week
		or	byte [esi+DATETIME_Flags],B3 ; set flag of last week

Má-li týden číslo nula, jedná se o poslední týden předešlého roku. To se týká 1. až 3. ledna v případech, kdy 4. leden připadá na pondělí, úterý nebo středu (ve výše uvedených tabulkách se jedná o první 3 řádky). Nastaví se příznak posledního týdne předešlého roku a dále bude nutné zjistit číslo posledního týdne předešlého roku.


; ------------- Last week of year 1582 (Saturday Januay 1 or Sunday January 2)

		mov	cx,[esi+DATETIME_Year] ; CX <- year
		mov	dl,51		; week 51 for 1582 year
		cmp	cx,1583		; is it 1583 year?
		je	DateTimeExt6	; previous year has 51 weeks

Do registru CX se připraví číslo aktuálního roku a rozliší se speciální případ roku 1583, kdy předešlý rok 1582 končí týdnem 51.


; ------------- Previous year is always 52 or 53 weeks

		inc	edx		; DL <- week 52
		cmp	bl,1-1		; is January 4th in Monday?
		je	DateTimeExt4	; previous year has always 53 weeks
		cmp	bl,3-1		; is January 4th in Wednesday?
		je	DateTimeExt6	; previous year has always 52 weeks

Připadne-li 4. leden na pondělí, bude mít předešlý rok vždy 53 týdnů. Připadne-li na středu, bude mít vždy 52 týdnů. Tuto závislost lze vypozorovat z předešlých tabulek podle posunu dnů na konci roku.


; ------------- Tuesday, check if previous year is a leap year

		dec	cx		; CX <- previous year
		call	CheckLeapYear	; check if previous year is a leap year
		jnc	DateTimeExt6	; previous year is not leap, 52 weeks
DateTimeExt4:	inc	edx		; DL <- week 53
		jmp	short DateTimeExt6 ; it is valid week

Připadne-li 4. leden na úterý, bude počet týdnů záviset na tom, zda předešlý rok je přestupným rokem nebo ne. Test přestupnosti roku se provede pomocí funkce CheckLeapYear. Není-li předešlý rok přestupný, bude předešlý rok končit 52. týdnem. Je-li rok přestupný, bude to týden 53. To lze opět vypozorovat porovnáním tabulek pro přestupný a nepřestupný rok.


; ------------- First week of following year (1582 year is always OK)

DateTimeExt5:	cmp	dl,bh		; is it valid week?
		jbe	DateTimeExt6	; it is valid week
		mov	dl,1		; first week of next year
		or	byte [esi+DATETIME_Flags],B4 ; first week of next year

; ------------- Store week

DateTimeExt6:	mov	[esi+DATETIME_Week],dl ; store week

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

DateTimeExt9:	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

Poslední test zkontroluje, zda je číslo týdne platné a zda se nejedná o první týden následujícího roku. Přitom se použije počet týdnů zjištěných dříve v registru BH. Pokud se jedná o první týden následujícího roku, nastaví se příslušný příznak a týden bude mít číslo 1. Na závěr se zjištěné číslo týdne uloží do struktury DATETIME a obnoví se registry ze zásobníku.


; -----------------------------------------------------------------------------
;             Convert absolute time to date-time with extended entries
; -----------------------------------------------------------------------------
; 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 + extended entries
; NOTES:	Takes 230 ns on 1.6 GHz CPU.
; -----------------------------------------------------------------------------

AbsToDateTimeExt:
		call	AbsToDateTime	; convert absolute time to date-time
		call	DateTimeExt	; get calendar extended entries
		ret

Funkce AbsToDateTimeExt je obdobou funkce AbsToDateTime. Liší se od ní tím, že po provedení převodu absolutního čas na datum a čas v rozloženém tvaru se provede ještě výpočet rozšířených položek struktury DATETIME.


Obsah / Utility / CALENDAR / Výpočet rozšířených položek kalendáře