; Библиотека календарных функций написана на ассемблере ФАСМ. ; ; Возможности: ; * обслуживает даты с периода длиной сверх 11 миллионов лет: ; - с 1 января 5843880 г. до н.э. по 3 августа 5915100 г. ; для юлианского календаря, ; - с 30 декабря 5844001 г. до н.э. по 17 января 5915222 г. ; для григорианского календаря, ; * свободная конверсия дат между календарями юлианском и григорианском ; в упомянутом периоде, ; * определение дня недели для данной даты, ; * вычисление номера дня в году для данных номеров месяца и дня ; в месяце, ; * определение високосности года в данном календаре, ; * вычисление "абсолютного" номера дня соответствующего данной ; дате, благодаря чему можно легко рассчитывать количество дней ; между двумя датами. ; ; (C) Mikołaj Hajduk, 16.06.2008. ; format PE GUI 4.0 DLL entry DllEntryPoint include 'win32a.inc' ; Определение используемых констант. ; C1 = 365 ; Количество дней в обычном году. C4 = 4*C1 + 1 ; Количество дней в четырёхлетнем цикле (основном цикле юлианского календаря). C100 = 25*C4 - 1 ; Количество дней в "обычном" столетии григорианского календаря ; (т.е. в столетии, которое заканчивается невисокосным годом). C400 = 4*C100 + 1 ; Количество дней в полном 400-летнем цикле григорианского календаря. k = 30 J = 194796 ; Константы, которые определяют количество полных лет календарей юлианского G = 194800 ; и григорианского содержимых в отрезке времени равном "Великому Циклу" T. section '.data' data readable writeable ; Таблица, которая содержит длину месяцев обычного и високосного годов. ; MonthLen db 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 db 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ; Таблица, которая содержит значения функции 'DaySum' для всех пар (номер месяца, флаг високосности года). ; DaySum dw 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 dw 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 section '.code' code readable executable proc DllEntryPoint, hinstDLL, fdwReason, lpvReserved mov eax, TRUE ret endp ; DWORD DayOfWeek(DWORD Y, DWORD M, DWORD D, DWORD Gregorian) ; ; Функция рассчитывает для данной даты номер дня недели. Принято следующее определение: 0 - воскресенье, ; 1 - понедельник, 2 - вторник, 3 - среда, 4 - четверг, 5 - пятница, 6 - суббота. ; ; Параметры: ; Y - год, ; M - месяц, ; D - день, ; Gregorian - определение календаря (0 - юлианский, 1 - григорианский). ; ; Возвращаемые значения: ; * 0, 1, ..., 6, если дата верна, ; * -1, если данные неверны. ; proc DayOfWeek, Y, M, D, Gregorian pushfd push ebx edx stdcall DateToAbsDayNum, [Y], [M], [D], [Gregorian] ; eax := N test eax, eax jz .Error mov ebx, 7 ; xor edx, edx ; add eax, 5 ; edx := (eax + 5) mod 7 = (N + 5) mod 7 adc edx, edx ; div ebx ; xchg eax, edx ; eax := edx jmp .End .Error: mov eax, -1 .End: pop edx ebx popfd ret endp ; DWORD IsLeapYear(DWORD Y, DWORD Gregorian) ; ; Функция определяет високосность года в данном календаре. ; ; Параметры: ; Y - год, ; Gregorian - определение календаря (0 - юлианский, 1 - григорианский). ; ; Возвращаемые значения: ; * 1, если год Y високосный, 0 - в противном случае. ; * -1, если данные неверны. ; proc IsLeapYear, Y, Gregorian pushfd push ebx edx .CheckParameters: test [Gregorian], -2 ; 0 <= Gregorian <= 1 jnz .Error ; .IsYNegative: mov eax, [Y] ; eax := Y test eax, eax jz .Error jns .CheckCalendar ; eax < 0 (Y < 0) ; inc eax ; eax := eax + 1 neg eax ; eax := -eax = -(Y + 1) = -Y - 1 = ; = |Y| - [Y < 0] = Y' .CheckCalendar: cmp [Gregorian], 0 je .mod4 .Gregorian: xor edx, edx ; eax := E(eax / 100) = E(Y' / 100) mov ebx, 100 ; edx := eax mod 100 = Y' mod 100 div ebx ; test edx, edx jz .mod4 mov eax, edx ; eax := edx = Y' mod 100 ; ; {(Y' mod 100) mod 4 = Y' mod 4} .mod4: shr eax, 1 ; eax := E(eax / 2); CF := eax mod 2 jc .Result ; shr eax, 1 ; eax := E(eax / 2); CF := eax mod 2 jmp .Result ; .Error: mov eax, -1 jmp .End .Result: setnc al ; eax := not CF movzx eax, al ; .End: pop edx ebx popfd ret endp ; DWORD MDToDayNum(DWORD M, DWORD D, DWORD LeapYearFlag) ; ; Функция рассчитывает порядковый номер дня в году (в зависимости от високосности года). ; ; Параметры: ; M - месяц, ; D - день, ; LeapYearFlag - флаг високосности года (0 - год нормальный, 1 - високосный). ; ; Возвращаемые значения: ; * 1, 2, ..., 365 для нормального года, 1, 2, ..., 366 для високосного года, ; * -1, если данные неверны. ; proc MDToDayNum, M, D, LeapYearFlag pushfd push ebx edx .LeapYearFlag: test [LeapYearFlag], -2 ; 0 <= LeapYearFlag <= 1 jnz .Error ; .Month: cmp [M], 1 ; jb .Error ; 1 <= M <= 12 cmp [M], 12 ; ja .Error ; .Day: cmp [D], 1 ; D >= 1 jb .Error ; mov ebx, [LeapYearFlag] ; ebx := LeapYearFlag lea ebx, [ebx + 2*ebx] ; ebx := 3*ebx = 3*LeapYearFlag shl ebx, 2 ; ebx := 4*ebx = 12*LeapYearFlag mov edx, [M] ; eax := MonthLen[M - 1 + 12*LeapYearFlag] movzx eax, [MonthLen - 1 + ebx + edx] ; cmp [D], eax ; D <= MonthLen[M - 1 + 12*LeapYearFlag] ja .Error ; .CalculateDayNum: shl ebx, 1 ; ebx := 2*ebx = 24*LeapYearFlag movzx eax, [DaySum - 2 + ebx + 2*edx] ; eax := DaySum(M, LeapYearFlag) add eax, [D] ; eax := eax + D = DaySum(M, LeapYearFlag) + D jmp .End .Error: mov eax, -1 .End: pop edx ebx popfd ret endp ; DWORD DayNumToMD(DWORD n, DWORD LeapYearFlag, DWORD* M, DWORD* D) ; ; Функция конвертирует данный номер дня в году в соответствующие номера месяца и дня в месяце. ; Результат сильно зависит от значения флаги високосности года. ; ; Параметры: ; n - номер дня в году, ; LeapYearFlag - флаг високосности года (0 - год нормальный, 1 - високосный), ; M - указатель на переменную в которую будет записан вычисленный номер месяца, ; D - указатель на переменную в которую будет записан вычисленный номер дня. ; ; Возвращаемые значения: ; * 0, если данные верны (n, LeapYearFlag), ; * -1 в противном случае. ; proc DayNumToMD, n, LeapYearFlag, M, D pushfd push ebx ecx edx .CheckParameters: test [LeapYearFlag], -2 ; 0 <= LeapYearFlag <= 1 jnz .Error cmp [n], 1 ; n >= 1 jb .Error ; mov eax, 365 ; add eax, [LeapYearFlag] ; eax := 365 + LeapYearFlag cmp [n], eax ; n <= eax ja .Error ; .CalculateMD: mov ebx, [LeapYearFlag] ; ebx := LeapYearFlag lea ebx, [ebx + 2*ebx] ; ebx := 3*ebx = 3*LeapYearFlag shl ebx, 3 ; ebx := 8*ebx = 24*LeapYearFlag mov ecx, 12 ; ; .Loop: ; ecx := max{i; 1 <= i <= 12, DaySum(i, LeapYearFlag) < n} = m movzx edx, [DaySum - 2 + ebx + 2*ecx] ; cmp [n], edx ; edx := DaySum(m, LeapYearFlag) ja .LoopEnd ; loop .Loop ; .LoopEnd: mov eax, [M] ; M := ecx = m mov [eax], ecx ; mov ecx, [n] ; ecx := n sub ecx, edx ; ecx := ecx - edx = n - DaySum(m, LeapYearFlag) mov eax, [D] ; D := ecx mov [eax], ecx ; xor eax, eax jmp .End .Error: mov eax, -1 .End: pop edx ecx ebx popfd ret endp ; DWORD DateToAbsDayNum(DWORD Y, DWORD M, DWORD D, DWORD Gregorian) ; ; Функция рассчитывает для данной даты соответствующий абсолютный номер дня. ; ; Параметры: ; Y - год, ; M - месяц, ; D - день, ; Gregorian - определение календаря (0 - юлианский, 1 - григорианский). ; ; Возвращаемые значения: ; * 1, 2, ..., 2^32-1, если дата данного календаря верна, ; * 0, если данные неверны. ; proc DateToAbsDayNum, Y, M, D, Gregorian pushfd push ebx ecx edx test [Gregorian], -2 ; 0 <= Gregorian <= 1 jnz .Error ; stdcall IsLeapYear, [Y], [Gregorian] ; cmp eax, -1 ; eax := IsLeapYear(Y, Gregorian) je .Error ; ; Y <> 0 mov ebx, eax ; ebx := eax stdcall MDToDayNum, [M], [D], ebx ; cmp eax, -1 ; eax := MDToDayNum(M, D, ebx) = n je .Error ; mov ecx, [Y] ; cmp ecx, 0 ; ecx := Y jg .CalculateDayNum ; inc ecx ; Y < 0 ; ecx := ecx + 1 = Y + 1 = Y + [Y < 0] .CalculateDayNum: add ecx, k*J ; cmp [Gregorian], 0 ; ecx := ecx + kJ + k(G-J)[Gregorian = 1] = je .Yprim0 ; = Y + [Y < 0] + kJ + k(G-J)[Gregorian = 1] = Y' add ecx, k*(G-J) ; .Yprim0: cmp ecx, 0 ; jne .YprimPositive ; Y' = 0 sub eax, 364 ; eax := eax - 364 = n - 364 jmp .End ; .YprimPositive: ; Y' > 0 ; dec ecx ; ecx := ecx - 1 = Y' - 1 mov ebx, eax ; ebx := eax = n mov eax, 365 ; eax := 365 mul ecx ; eax := 365 * ecx = 365(Y' - 1) shr ecx, 2 ; ecx := E(ecx / 4) = E((Y' - 1) / 4) add eax, ecx ; eax := eax + ecx = 365(Y' - 1) + E((Y' - 1) / 4) add eax, ebx ; eax := eax + ebx = eax + n = ; = 365(Y' - 1) + E((Y' - 1) / 4) + n cmp [Gregorian], 0 jz .End .Gregorian: push eax ; X := eax xor edx, edx ; mov eax, ecx ; eax := ecx = E((Y' - 1) / 4) mov ebx, 25 ; div ebx ; eax := E(eax / 25) = E(E((Y' - 1) / 4) / 25) = ; = E((Y' - 1) / 100) mov ecx, eax ; ecx := eax = E((Y' - 1) / 100) pop eax ; eax := X = 365(Y' - 1) + E((Y' - 1) / 4) + n sub eax, ecx ; eax := eax - ecx = 365(Y' - 1) + E((Y' - 1) / 4) + n - ; - E((Y' - 1) / 100) shr ecx, 2 ; ecx : = E(ecx / 4) = E(E((Y' - 1) / 100) / 4) = ; = E((Y' - 1) / 400) add eax, ecx ; eax := eax + ecx = 365(Y' - 1) + E((Y' - 1) / 4) + n - ; - E((Y' - 1) / 100) + E((Y' - 1) / 400) add eax, 2 ; eax := eax + 2 = 365(Y' - 1) + E((Y' - 1) / 4) + n - ; - E((Y' - 1) / 100) + E((Y' - 1) / 400) + 2 = ; = N jmp .End .Error: xor eax, eax .End: pop edx ecx ebx popfd ret endp ; DWORD AbsDayNumToDate(DWORD N, DWORD Gregorian, DWORD* Y, DWORD* M, DWORD* D) ; ; Функция конвертирует абсолютный номер дня N = 1, 2, ..., 2^32-1 в соответствующую дату (в данном календаре). ; ; Параметры: ; N - абсолютный номер дня, ; Gregorian - определение календаря (0 - юлианский, 1 - григорианский), ; Y - указатель на переменную в которую будет записан вычисленный номер года, ; M - указатель на переменную в которую будет записан вычисленный номер месяца, ; D - указатель на переменную в которую будет записан вычисленный номер дня. ; ; Возвращаемые значения: ; * 0, если данные верны (N, Gregorian), ; * -1 в противном случае. ; proc AbsDayNumToDate, N, Gregorian, Y, M, D pushfd push ebx ecx edx cmp [N], 0 ; N <> 0 je .Error ; test [Gregorian], -2 ; 0 <= Gregorian <= 1 jnz .Error ; xor ecx, ecx ; ecx := 0 mov eax, [N] ; eax := N - 1 dec eax ; cmp [Gregorian], 0 je .Julian .Gregorian: cmp eax, 1 ja .NextDays ; 0 <= eax <= 1 (1 <= N <= 2) mov ebx, [M] ; M := 12 mov dword [ebx], 12 ; add eax, 30 ; eax := eax + 30 = N - 1 + 30 = N + 29 mov ebx, [D] ; D := eax = N + 29 mov [ebx], eax ; mov ecx, -k*G - 1 ; ecx := -kG - 1 jmp .ReturnY .NextDays: ; eax > 1 (N > 2) sub eax, 2 ; eax := eax - 2 = N - 1 - 2 = N - 3 xor edx, edx ; mov ebx, C400 ; eax := E(eax / C400) = E((N - 3) / C400) div ebx ; edx := eax mod C400 = (N - 3) mod C400 lea eax, [eax + 4*eax] ; eax := 5*eax = 5*E((N - 3) / C400) lea eax, [eax + 4*eax] ; eax := 5*eax = 5*(5*E((N - 3) / C400)) = ; = 25*E((N - 3) / C400) shl eax, 4 ; eax := 16*eax = 16*(25*E((N - 3) / C400)) = ; = 400*E((N - 3) / C400) xchg ecx, eax ; ecx := eax = 400*E((N - 3) / C400) ; xchg eax, edx ; eax := edx = (N - 3) mod C400 ; .Centuries: ; cmp eax, C100 ; jb .Julian ; ; add ecx, 100 ; sub eax, C100 ; ; cmp eax, C100 ; (eax, ecx) := P(eax, ecx) = jb .Julian ; = P((N - 3) mod C400, 400*E((N - 3) / C400)) = ; = (N100, Y100) add ecx, 100 ; sub eax, C100 ; ; cmp eax, C100 ; jb .Julian ; ; add ecx, 100 ; sub eax, C100 ; .Julian: ; / ; | (N - 1, 0) ; Gregorian = 0 ; (N100, Y100) = (eax, ecx) = < ; | P((N - 3) mod C400, 400*E((N - 3) / C400)) ; Gregorian = 1 ; \ xor edx, edx ; mov ebx, C4 ; eax := E(eax / C4) = E(N100 / C4) div ebx ; edx := eax mod C4 = N100 mod C4 shl eax, 2 ; eax := 4*eax = 4*E(N100 / C4) add ecx, eax ; ecx := ecx + eax = Y100 + 4*E(N100 / C4) .Years: ; inc ecx ; cmp edx, C1 ; jb .MD ; ; sub edx, C1 ; ; inc ecx ; (edx, ecx) := Q(edx, ecx) = cmp edx, C1 ; = Q(N100 mod C4, Y100 + 4*E(N100 / C4)) = jb .MD ; = (N', Y*) ; sub edx, C1 ; ; inc ecx ; cmp edx, C1 ; jb .MD ; ; sub edx, C1 ; ; inc ecx ; .MD: inc edx ; edx := edx + 1 = N' + 1 stdcall IsLeapYear, ecx, [Gregorian] ; eax := IsLeapYear(ecx, Gregorian) = ; = IsLeapYear(Y*, Gregorian) stdcall DayNumToMD, edx, eax, [M], [D] ; eax := DayNumToMD(edx, eax, M, D) = ; = DayNumToMD(N' + 1, IsLeapYear(Y*, Gregorian), M, D) cmp [Gregorian], 0 je .JulianYears .GregorianYears: ; sub ecx, k*(G - J) ; ; ecx := ecx - kJ - k(G - J)[Gregorian = 1] = .JulianYears: ; = Y* - kJ - k(G - J)[Gregorian = 1] = sub ecx, k*J ; = Y' cmp ecx, 0 jg .ReturnY ; ecx <= 0 (Y' <= 0) dec ecx ; ecx := ecx - 1 = Y' - 1 = Y' - [Y' <= 0] .ReturnY: mov eax, [Y] ; Y := ecx mov [eax], ecx ; xor eax, eax jmp .End .Error: mov eax, -1 .End: pop edx ecx ebx popfd ret endp ; DWORD GregorianToJulian(DWORD Yg, DWORD Mg, DWORD Dg, DWORD* Yj, DWORD* Mj, DWORD* Dj) ; ; Функция конвертирует григорианскую дату в соответствующую дату юлианского календаря. ; ; Параметры: ; Yg - год григорианской даты, ; Mg - месяц григорианской даты, ; Dg - день григорианской даты, ; Yj - указатель на переменную в которую будет записан вычисленный номер года юлианской даты, ; Mj - указатель на переменную в которую будет записан вычисленный номер месяца юлианской даты, ; Dj - указатель на переменную в которую будет записан вычисленный номер дня юлианской даты. ; ; Возвращаемые значения: ; * 0, если григорианская дата верна, ; * -1 в противном случае. ; proc GregorianToJulian, Yg, Mg, Dg, Yj, Mj, Dj .GregorianToNum: stdcall DateToAbsDayNum, [Yg], [Mg], [Dg], 1 test eax, eax jz .Error .NumToJulian: stdcall AbsDayNumToDate, eax, 0, [Yj], [Mj], [Dj] jmp .End .Error: mov eax, -1 .End: ret endp ; DWORD JulianToGregorian(DWORD Yj, DWORD Mj, DWORD Dj, DWORD* Yg, DWORD* Mg, DWORD* Dg) ; ; Функция конвертирует юлианскую дату в соответствующую дату григорианского календаря. ; ; Параметры: ; Yj - год юлианской даты, ; Mj - месяц юлианской даты, ; Dj - день юлианской даты, ; Yg - указатель на переменную в которую будет записан вычисленный номер года григорианской даты, ; Mg - указатель на переменную в которую будет записан вычисленный номер месяца григорианской даты, ; Dg - указатель на переменную в которую будет записан вычисленный номер дня григорианской даты. ; ; Возвращаемые значения: ; * 0, если юлианская дата верна, ; * -1 в противном случае. ; proc JulianToGregorian, Yj, Mj, Dj, Yg, Mg, Dg .JulianToNum: stdcall DateToAbsDayNum, [Yj], [Mj], [Dj], 0 test eax, eax jz .Error .NumToGregorian: stdcall AbsDayNumToDate, eax, 1, [Yg], [Mg], [Dg] jmp .End .Error: mov eax, -1 .End: ret endp section '.edata' export data readable export 'Calendar.dll',\ AbsDayNumToDate, 'AbsDayNumToDate',\ DateToAbsDayNum, 'DateToAbsDayNum',\ DayNumToMD, 'DayNumToMD',\ DayOfWeek, 'DayOfWeek',\ GregorianToJulian, 'GregorianToJulian',\ IsLeapYear, 'IsLeapYear',\ JulianToGregorian, 'JulianToGregorian',\ MDToDayNum, 'MDToDayNum' section '.reloc' fixups data discardable