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. subtract 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 leap 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, subtract 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	; subtract 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 the 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 the 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 already less
	add	ebx, 2		; check the next month
	jmp	.nie_przest

mies_juz:
		; to get the day number in a month, subtract the sum of all
		;days in all PREVIOUS months (hence the -2) from the day number
	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	; subtract 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 evenly
				; divisible, so it can't be leap

	; here we know the year is evenly divisible 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 evenly
				; divisible by 100, but is divisible by 4, so
				; it is a leap year

	; here we know the year is evenly divisible 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 (access key 2)
Helpers for people with disabilities (access key 0)