;name: daysinmonth.asm
;
;build: nasm -felf64 daysinmonth.asm -o daysinmonth.o
;
;description: Allthough easy solvable with a table of monthnumber as index and the days as values,
;             I like demonstrate the calculation of the number of days in a month the binary way
;             using one register only.  My inspiration comes from the book Hacker's Delight.
;             Using vector registers we can put in all months and calculate the days in all months
;             at once.  Therefor this algorithm is nice because it's branch free.
;
;remark: February has in this program always 28 days. Combined with a leapyear you can add one day.
;        I don't have a nice formula for this algorithm, it's merely a trial and error combined with
;        binary logic.  The ame was to create branch free routines without looking up values in a table.
;        Reading the comments in the code must help you further.
;
; The first phase figures out if a month has at least 28 or sure 29 days.  Only february has 28 days.
; We can't just rely on a sudden bit because, when we look at the month values in a cyclic manner, we see
; that december,january and july and august have 31 days. An xor with bit 3 however give the right value
; in bit 0 as indication that a month has one day less or more than it's predecesor.  Adding 28 gives us
; a cycle of 29-28-29-28-29-28-29-29-28-29-28-29.
; 
; month         nr     al     ah = al   ah >> 3   ah = ah xor al  ah = ah and 1   ah = ah or 00011100  days  
; ----------   ---  --------  --------  --------  --------------  --------------  -------------------  ----
; january       1   00000001  00000001  00000000     00000001       00000001          00011101          29
; february      2   00000010  00000010  00000000     00000010       00000000          00011100          28
; march         3   00000011  00000011  00000000     00000011       00000001          00011101          29
; april         4   00000100  00000100  00000000     00000100       00000000          00011100          28
; may           5   00000101  00000101  00000000     00000101       00000001          00011101          29
; june          6   00000110  00000110  00000000     00000110       00000000          00011100          28
; july          7   00000111  00000111  00000000     00000111       00000001          00011101          29
; august        8   00001000  00001000  00000001     00001001       00000001          00011101          29
; september     9   00001001  00001001  00000001     00001000       00000000          00011100          28
; october      10   00001010  00001010  00000001     00001011       00000001          00011101          29
; november     11   00001011  00001011  00000001     00001010       00000000          00011100          28
; december     12   00001100  00001100  00000001     00001101       00000001          00011101          29
;
; The second phase eliminates the month february which doesn't need two additional days.  Doing so we got for each
; month its number of days in rax in return.
;
; month         nr     al     al = al - 2   al or 0xF0     al - 1   al >> 3    value in ah   al and 2  ah = ah or al  days  
; ----------   ---  --------  ------------  -----------   --------  ---------  -----------   --------  -------------  ----
; january       1   00000001    11111111     11111111     11111110  00011111     00011101    00000010    00011111      31
; february      2   00000010    00000000     11110000     11101111  00011101     00011100    00000000    00011100      28
; march         3   00000011    00000001     11110001     11110000  00011110     00011101    00000010    00011111      31
; april         4   00000100    00000010     11110010     11110001  00011110     00011100    00000010    00011110      30
; may           5   00000101    00000011     11110011     11110010  00011110     00011101    00000010    00011111      31
; june          6   00000110    00000100     11110100     11110011  00011110     00011100    00000010    00011110      30
; july          7   00000111    00000101     11110101     11110100  00011110     00011101    00000010    00011111      31
; august        8   00001000    00000110     11110110     11110101  00011110     00011101    00000010    00011111      31
; september     9   00001001    00000111     11110111     11110110  00011110     00011100    00000010    00011110      30
; october      10   00001010    00001000     11111000     11110111  00011110     00011101    00000010    00011111      31
; november     11   00001011    00001001     11111001     11111000  00011111     00011100    00000010    00011110      30
; december     12   00001100    00001010     11111010     11111001  00011111     00011101    00000010    00011111      31

bits 64

section .text

global daysinmonth

daysinmonth:
;calculates the number of days in a month, february counts 28 days.
    ;phase one: figure out if we have more than 28 days
    mov     rax,rdi
    mov     ah,al               ;monthnumber in ah
    shr     ah,3                ;shift bit 3 to position 0 zeroing out all other bits 
    xor     ah,al               ;xor with month number
    and     ah,1                ;mask bit 0 from temp result
    ;we got now 0 or 1 in ah, indicating that a month has 31 or 30 days
    or      ah,28               ;adjust to number of days, ah has 29 or 28
    ;phase two: find out if we need to add two more days or not
    dec     al                  ;decrement month with two
    dec     al
    or      al,0xF0             ;erase lowest nibble
    dec     al                  ;decrement al
    shr     al,3                ;bit 4 of al in postion 0
    and     al,2                ;elimante all bits except the one on position 1
    or      ah,al               ;or this bit in number of days
    ;ah has now 28,30 or 31 for number of days
    shr     ax,8                ;shift result in al
    ret                         ;return number of days in AL
;name: leapyear.asm
;
;Build:    nasm "-felf64" leapyear.asm -l leapyear.lst -o leapyear.o
;
;description: the function calculates if a year in rdi (hexadecimal) is leap or not.
;             rax returns 0 if not leap otherwise 1


bits 64

global leapyear

section .text

leapyear:
    push	rbx                ; save used registers
    push	rcx
    push	rdx
    mov		rax, rdi
    xor		rcx, rcx           ; assume not leap, rcx = 0
    test	rax, 3             ; last two bits 0?
    jnz		.@1                ; if not year is not disible by 4 -> no leapyear
    inc		rcx                ; assume year is a leapyear, rcx = 1
    xor		rdx, rdx           ; prepare rdx for division
    mov		rbx, 100           ; year / 100
    div		rbx
    and		rdx, rdx           ; remainder = 0?
    jnz		.@1                ; no, no leapyear
    ; multiples of 100 aren't leap years except if last two bits
    ; are zero 0 (divisible by 4) then also divisible by 400
    test	rax, 3
    jz		.@1                ; yes, leap year
    dec		rcx                ; no, not leap year, rcx = 0
.@1:
    mov		rax, rcx           ; mov result in RAX
    pop		rdx
    pop		rcx
    pop		rbx
    ret
;name: semester.asm
;
;build: nasm -felf64 semester.asm -o semester.o
;
;description: calculating in which semester a month is.
;             semester: A period or term of six months
;
; month         nr  binary in AL  AL > 3
; ----------   ---  ------------  ------------  -------------
; january       1     00000001      00000010       00000000  
; february      2     00000010      00000011       00000000  
; march         3     00000011      00000100       00000000  
; april         4     00000100      00000101       00000000  
; may           5     00000101      00000110       00000000  
; june          6     00000110      00000111       00000000  
; july          7     00000111      00001000       00000001  
; august        8     00001000      00001001       00000001  
; september     9     00001001      00001010       00000001  
; october      10     00001010      00001011       00000001  
; november     11     00001011      00001100       00000001  
; december     12     00001100      00001101       00000001  
;
; incrementing the value in al gives us the value of the semester of that month.

bits 64
               
section .text

global semester

semester:
    mov     rax,rdi
    inc     al              ;s = month + 1
    shr     al,3            ;s = s div 8
    inc     al              ;s = s + 1
    ret                     ;return semester in rax
;name: quadrimester.asm
;
;build: nasm -felf64 quadrimester.asm -o quadrimester.o
;
;description: calculating in which quadrimester a month is.
;             quadrimester: A period of four months or about four months.
;
; month         nr  binary in al  al > 2
; ----------   ---  ------------  ------------  -------------
; january       1     00000001      00000000       00000000  
; february      2     00000010      00000001       00000000  
; march         3     00000011      00000010       00000000  
; april         4     00000100      00000011       00000000  
; may           5     00000101      00000100       00000001  
; june          6     00000110      00000101       00000001  
; july          7     00000111      00000110       00000001  
; august        8     00001000      00000111       00000001  
; september     9     00001001      00001000       00000010  
; october      10     00001010      00001001       00000010  
; november     11     00001011      00001010       00000010  
; december     12     00001100      00001011       00000010  
;
; incrementing the value in al by one and erasing all bits in ah gives the value of the semester of that month.

bits 64
                
section .text

global quadrimester

quadrimester:
; calculates the quadrimester number of a month in rdi
    mov     rax,rdi
    dec     al              ;q = month - 1
    shr     al,2            ;q = q idiv 4
    inc     al              ;q = q + 1
    ret                     ;return quadrimester in al
;name: shiftedmonth.asm
;
;build: nasm -felf64 shiftedmonth.asm -o shiftedmonth.o
;
;description: Calculates the shifted month from a given month in rdi, which can be used to calculate
;             Easter sundays (and most probably more than only Easter sundays).
;             The routine doesn't check for legal month numbers, it takes out the lower four bits to calculate
;             with.
;             Returns shifted month number in rax if rdi is a montnumber otherwise rubish.
;
;  ax <- month number      ax = ax - 3       ax and 111 0100 0000 1111           not al           and al,ah               inc al
;                                                                                                                  = shifted month number
; -------------------  -------------------  ---------------------------  -------------------  -------------------  ----------------------
; 0000 0000 0000 0001  1111 1111 1111 1110     1111 0100 0000 1110       0000 1011 0000 1110  0000 1011 0000 1010   0000 1011 0000 1011
; 0000 0000 0000 0010  1111 1111 1111 1111     1111 0100 0000 1111       0000 1011 0000 1111  0000 1011 0000 1011   0000 1011 0000 1100
; 0000 0000 0000 0011  0000 0000 0000 0000     0000 0000 0000 0000       1111 1111 0000 0000  1111 1111 0000 0000   1111 1111 0000 0001
; 0000 0000 0000 0100  0000 0000 0000 0001     0000 0000 0000 0001       1111 1111 0000 0001  1111 1111 0000 0001   1111 1111 0000 0010
; 0000 0000 0000 0101  0000 0000 0000 0010     0000 0000 0000 0010       1111 1111 0000 0010  1111 1111 0000 0010   1111 1111 0000 0011
; 0000 0000 0000 0110  0000 0000 0000 0011     0000 0000 0000 0011       1111 1111 0000 0011  1111 1111 0000 0011   1111 1111 0000 0100
; 0000 0000 0000 0111  0000 0000 0000 0100     0000 0000 0000 0100       1111 1111 0000 0100  1111 1111 0000 0100   1111 1111 0000 0101
; 0000 0000 0000 1000  0000 0000 0000 0101     0000 0000 0000 0101       1111 1111 0000 0101  1111 1111 0000 0101   1111 1111 0000 0110
; 0000 0000 0000 1001  0000 0000 0000 0110     0000 0000 0000 0110       1111 1111 0000 0110  1111 1111 0000 0110   1111 1111 0000 0111
; 0000 0000 0000 1010  0000 0000 0000 0111     0000 0000 0000 0111       1111 1111 0000 0111  1111 1111 0000 0111   1111 1111 0000 1000
; 0000 0000 0000 1011  0000 0000 0000 1000     0000 0000 0000 1000       1111 1111 0000 1000  1111 1111 0000 1000   1111 1111 0000 1001
; 0000 0000 0000 1100  0000 0000 0000 1001     0000 0000 0000 1001       1111 1111 0000 1001  1111 1111 0000 1001   1111 1111 0000 1010
;
; Looking at the values in al, we notice the shifted mont number. Clearing the other meaningless bits gives this number in rax.

bits 64

section .text

global shiftedmonth

shiftedmonth:
    mov     rax,rdi
    and     rax,1111b              ;take only lower 4 bits in concern
    dec     ax                     ;minus 3
    dec     ax
    dec     ax
    and     ax,1111010000001111b
    not     ah
    and     al,ah
    inc     al
    and     ax,1111b               ;only lower 4 bits are relevant for the result
    ret