
   Getting and setting the date and time under Linux

   The  current  date  and  time  under Linux is stored in the so-called
   timestamp.  A  timestamp  is  the number of seconds that have elapsed
   since midnight on January 1, 1970 in UTC (GMT) time. The timestamp is
   obtained  using  the  sys_time system function (number 13), and a new
   time can be set using the sys_stime system function (number 25).
   If  we  don't  want  to  use  any  libraries,  then  this  format  is
   inconvenient for use. This is why I'm going to show here a method for
   converting  a  timestamp to traditional time format and the other way
   round.
   This  article  was  written  based  on  the  source code of the GNU C
   library (glibc), strictly speaking - the glibc/time/offtime.c file.
     ________________________________________________________________

   Convert a timestamp into traditional form

   (skip to the other way conversion)
    1. divide   the  timestamp  by  the  number  of  seconds  in  a  day
       (60*60*24).  Save  the  quotient  as  the  number of days and the
       remainder from this division
    2. increase  the remainder by the time offset of your time zone from
       GMT, in seconds (in Central Europe, this is 60*60 in winter time,
       2*60*60 in summer time)
    3. if  the remainder is less than zero, increase it by the number of
       seconds  per  day  until  the  remainder  is  positive, each time
       decreasing the number of days from step 1
    4. if  the remainder is greater than the number of seconds in a day,
       decrease  it by the number of seconds per day until the remainder
       is less than the number of seconds in a day, each time increasing
       the number of days from step one
    5. divide  the  remainder by the number of seconds in one hour. Save
       the  quotient  as  the  calculated  hour  of  the  day,  save the
       remainder in a variable called the "remainder" from now on
    6. divide  the  remainder  from  the  previous step by the number of
       seconds  in  a minute. Save the quotient as the number of minutes
       in  the  current  hour  and  save  the remainder as the number of
       seconds in the current minute
    7. increase  the  number  of  days  by  4  (as  January 1 1970 was a
       Thursday),  and  divide  the result by 7. The remainder (add 7 if
       negative)  from  this  division  should  be  saved as the current
       weekday (0 means Sunday)
    8. put 1970 in the Y variable
    9. do the following in a loop:
         1. check  if  number  of  days  is  negative or grater than the
            number of days in year Y. If neither is true, exit he loop.
            This  step  requires  the check if the year Y is a leap year
            (has 366 days). Each year with number divisible by 4 is leap
            except  for  those divisible by 100. Additionally, each year
            divisible by 400 is leap.
         2. fill  a new variable, YG, with the sum of Y and the quotient
            from dividing the number of days by 365. If the remainder is
            negative, decrease YG by 1.
         3. decrease the number of days by the difference between YG and
            Y multiplied by 365
         4. decrease  the  number of days by the result of the procedure
            EXTRA (shown later) called with a parameter of YG-1
         5. increase  the  number of days by the result of the procedure
            EXTRA (shown later) called with a parameter of Y-1
         6. put YG into Y YG
   10. put the current number of days in the day-of-year variable
   11. check, within which month is the current day and save that month.
       Decrease  the  number  of days by the total number of days in the
       preceding months.
   12. put the current number of days increased by 1 in the day-of-month
       variable

   The EXTRA procedure has the following steps:
    1. divide the given year (the parameter) by 4 and save the quotient.
       If the remainder was negative, decrease the quotient by 1
    2. divide  the  given  year  (the  parameter)  by  100  and save the
       quotient. If the remainder was negative, decrease the quotient by
       1
    3. divide  the  given  year  (the  parameter)  by  400  and save the
       quotient. If the remainder was negative, decrease the quotient by
       1
    4. substract  the  second  result from the first, then add the third
       result  and  return  the  obtained  number  as  a  result  of the
       procedure

   This  big,  complicated  algorithm  is shown in the following program
   (FASM syntax):
   (skip the program)
; Program calculates the current time based on the current timestamp.
; The program DOES NOT DISPLAY ANYTHING.
;
; Author: Bogdan D., bogdandr (at) op.pl
;
; assembly:
;   fasm dataczas.fasm

format ELF executable
segment executable
entry main

SEK_NA_GODZ     = (60 * 60)             ; number of seconds in an hour
SEK_NA_DZIEN    = (SEK_NA_GODZ * 24)    ; number of seconds in a day
LETNI           = 1                     ; 0, if winter time, 1 if summer time
PRZES_GMT       = 1*SEK_NA_GODZ + LETNI*SEK_NA_GODZ  ; offset from GMT

main:
        mov     eax, 13
        xor     ebx, ebx
        int     80h     ; get the current time in seconds
        mov     [czas], eax

        mov     ebx, SEK_NA_DZIEN
        xor     edx, edx
        idiv    ebx     ; number of seconds / number of seconds in a day =
                        ; number of days

        add     edx, PRZES_GMT  ; add time zone

        ; if remaining number of seconds < 0, increase it by the number of
        ; seconds in a day, each time decreasing the number of days (EAX)
spr_reszte:
        cmp     edx, 0
        jge     reszta_ok

        add     edx, SEK_NA_DZIEN
        sub     eax, 1

        jmp     spr_reszte

reszta_ok:

        ; if remaining number of seconds > number of seconds in a day,
        ; decrease it by the number of seconds in a day, each time increasing
        ; the number of days (EAX)
spr_reszte2:
        cmp     edx, SEK_NA_DZIEN
        jl      reszta_ok2

        sub     edx, SEK_NA_DZIEN
        add     eax, 1

        jmp     spr_reszte2

reszta_ok2:

        mov     [l_dni], eax
        mov     [reszta], edx

        mov     eax, edx        ; EAX = remainder
        mov     ebx, SEK_NA_GODZ
        xor     edx, edx
        idiv    ebx     ; EAX = hour, remainder - minutes+seconds

        mov     [godz], al      ; save the hour
        mov     [reszta], edx   ; and the new remainder

        mov     eax, edx
        mov     ecx, 60
        xor     edx, edx
        idiv    ecx             ; divide the new remainder by 60

        mov     [min], al       ; quotients is the number of minutes
        mov     [sek], dl       ; remainder is the number of seconds

        ; find the weekday
        mov     eax, [l_dni]
        add     eax, 4  ; 1970-1-1 was a Thursday
        mov     ebx, 7
        xor     edx, edx
        idiv    ebx     ; EAX = day of week

        cmp     dl, 0
        jge     dzient_ok
        add     dl, 7   ; add 7, if negative
dzient_ok:
        mov     [dzient], dl


        ; beginning of the loop in point 9
spr_dni:
        mov     eax, [y]
        call    czy_przest      ; ECX = 0, if Y is leap.

        cmp     dword [l_dni], 0
        jl      zmien_dni       ; check if number of days < 0

        mov     esi, 365
        test    ecx, ecx
        jnz     .przest_ok
        add     esi, 1          ; add 1 day in a lep year
.przest_ok:

        cmp     [l_dni], esi
        jl      koniec_spr_dni  ; check if number of days >= 365/366

zmien_dni:

        mov     esi, 365
        mov     eax, [l_dni]
        xor     edx, edx
        idiv    esi             ; EAX = number of days/365
        mov     ecx, eax        ; save to ECX
        cmp     edx, 0
        jge     .edx_ok1
        sub     ecx, 1          ; if remainder < 0, substract 1
.edx_ok1:
        add     ecx, [y]        ; ECX = number of days/365 + Y +1 or +0
        mov     [yg], ecx       ; save into YG

        sub     ecx, [y]
        imul    ecx, ecx, 365   ; ECX = (YG-Y)*365

        push    ecx
        mov     eax, [yg]
        sub     eax, 1
        call    dodatek         ; calculate the EXTRA on YG-1 and save the
                                ; result in [przest]
        pop     ecx
        add     ecx, [przest]   ; ECX = (YG-Y)*365 + EXTRA(YG-1)

        push    ecx
        mov     eax, [y]
        sub     eax, 1
        call    dodatek         ; calculate the EXTRA on Y-1 and save the
                                ; result in [przest]
        pop     ecx
        sub     ecx, [przest]   ; ECX=(YG-Y)*365 + EXTRA(YG-1) - EXTRA(Y-1)

        sub     [l_dni], ecx    ; substract the whole from the number of days

        mov     eax, [yg]
        mov     [y], eax        ; store YG into Y

        jmp     spr_dni         ; go to the beginning of the loop

koniec_spr_dni:
        mov     eax, [y]
        ;sub    eax, 1900
        mov     [rok], ax       ; save the calculated year
        call    czy_przest      ; ECX = 0, if leap

        mov     eax, [l_dni]
        mov     [dzienr], ax    ; save the number of te day in the year

        ; check which month the day belongs to
        xor     esi, esi        ; assume non-leap year
        mov     ebx, 2          ; start with the first month
        test    ecx, ecx
        jnz     .nie_przest
        add     esi, 13*2       ; if leap, take the second group of numbers
.nie_przest:
        ; look for the month. EAX = number of te day in the year
        cmp     ax, [dni1+esi+ebx]  ; compare the day number with the sum of
        ; days until the NEXT month
        jbe     mies_juz        ; stop if laready less
        add     ebx, 2          ; check the next month
        jmp     .nie_przest

mies_juz:
                ; to get the day number in a month, substract the sum of all
                ;days in all PREVIOUS months (hence the -2) from the day numbe
r
        sub     ax, [dni1+esi+ebx-2]
        inc     al      ; and add 1, so we don't count from zero
        mov     [dzien], al     ; save the day of month

        shr     ebx, 1  ; divide the month number by 2, because there are 2
                        ; bytes per month
        mov     [mies], bl      ; and save the result

        mov     eax, 1
        xor     ebx, ebx
        int     80h     ; exit the program

; the EXTRA procedure. Year (the parameter) is given in EAX
dodatek:
        push    eax
        push    ebx
        push    ecx
        push    edx
        push    esi
        push    edi

        mov     esi, 4
        mov     edi, 100
        mov     ebx, 400
        and     eax, 0ffffh

        push    eax
        xor     edx, edx
        idiv    esi             ; divide EAX by 4
        mov     ecx, eax        ; save the result
        cmp     edx, 0          ; check the remainder
        jge     .edx_ok1
        sub     ecx, 1          ; if remainder < 0, decrease the result by 1
.edx_ok1:

        pop     eax
        push    eax
        xor     edx, edx
        idiv    edi             ; divide EAX by 100
        sub     ecx, eax        ; substract from the current result
        cmp     edx, 0          ; check the remainder
        jge     .edx_ok2
        add     ecx, 1          ; if remainder < 0, decrease the result by 1
.edx_ok2:

        pop     eax
        xor     edx, edx
        idiv    ebx             ; divide EAX by 400
        add     ecx, eax        ; add to the current result
        cmp     edx, 0          ; check the remainder
        jge     .edx_ok3
        sub     ecx, 1          ; if remainder < 0, decrease the result by 1
.edx_ok3:

        mov     [przest], ecx   ; save the result

        pop     edi
        pop     esi
        pop     edx
        pop     ecx
        pop     ebx
        pop     eax
        ret

; returns 0 in ECX if the year given in EAX is leap, 1 - if it is not
czy_przest:
        push    eax
        push    ebx
        push    edx

        xor     ecx, ecx

        push    eax
        xor     edx, edx
        mov     ebx, 4
        idiv    ebx             ; divide EAX by 4
        pop     eax
        test    edx, edx
        jnz     .nie_jest       ; non-zero remainder means the year isn't even
ly
                                ; divisible, so it can't be leap

        ; here we know the year is evenly disible by 4
        push    eax
        xor     edx, edx
        mov     ebx, 100
        idiv    ebx             ; divide EAX by 100
        pop     eax
        test    edx, edx
        jnz     .jest           ; non-zero remainder means the year isn't even
ly
                                ; divisible by 100, but is divisible by 4, so
                                ; it is a leap year

        ; here we know the year is evenly disible by 4 and 100
        push    eax
        xor     edx, edx
        mov     ebx, 400
        idiv    ebx             ; divide EAX by 400
        pop     eax
        test    edx, edx
        jz      .jest           ; zero remainder means the year is evenly
                                ; divisible by 400, so it is leap
.nie_jest:
        mov     ecx, 1

.jest:
        pop     edx
        pop     ebx
        pop     eax
        ret


segment readable writeable

l_dni   dd      0       ; calculated number of days
reszta  dd      0       ; remainder from the divisions
y       dd      1970    ; starting Y value
yg      dd      0       ; YG variable
przest  dd      0       ; extra
czas    dd      0       ; timestamp

rok     dw      0       ; current year
mies    db      0       ; current month
dzien   db      0       ; current day of month
dzient  db      0       ; current day of week
dzienr  dw      0       ; current day of year

godz    db      0       ; current hour
min     db      0       ; current minute
sek     db      0       ; current second

; number of days preceding each month in a non-leap and leap years
dni1    dw      0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
        dw      0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
     ________________________________________________________________

   Converting traditional form to a timestamp

   This algorithm is much simpler. Namely:
   Timestamp = SECONDS + MINUTE*60 + HOUR*60*60 + DAY_OF_YEAR*60*60*24 +
   YEARS_SINCE_1970*60*60*24*365 + LEAP_YEARS_SINCE_1970*60*60*24
   All you have to do is calculate, which day of the year is the current
   day  (knowing  the  day of month, use the arrays in the above program
   and  add  the day number to the right number) and how many leap years
   were  there since 1970 until the current year (according to the known
   rules;  all you have to do is call the above czy_przest procedure for
   each year).
   Just  notice, that the number of leap years shows the number of days,
   not years, to add.

   On-line contents (Alt+2)
   Helpers for people with disabilities (Alt+0)
