Wrote a 200+ line assembly program to do this. It prints the time stamp first and then prints the formatted date and time. All its functions follow System-V x64 calling convention.
global _start
section .rodata
strings: ; '\n', ' ', '/', ':'
db 0x1, 0xa, 0x1, 0x20
db 0x1, 0x2f, 0x1, 0x3a
weekdays: ; weekday strings
db 0x3, 0x54, 0x68, 0x75
db 0x3, 0x46, 0x72, 0x69
db 0x3, 0x53, 0x61, 0x74
db 0x3, 0x53, 0x75, 0x6e
db 0x3, 0x4d, 0x6f, 0x6e
db 0x3, 0x54, 0x75, 0x65
db 0x3, 0x57, 0x65, 0x64
months: ; length of months
db 0x1f, 0x1c, 0x1f, 0x1e
db 0x1f, 0x1e, 0x1f, 0x1f
db 0x1e, 0x1f, 0x1e, 0x1f
section .text
_start:
push rbx ; align stack
mov rax, 201 ; sys_time
xor rdi, rdi
syscall
mov rbx, rax
; you may uncomment the following line and put an arbitary timestamp to test it out
; mov rbx, 0
mov rdi, rbx
call print_num ; print unix timestamp
mov rdi, strings
call sys_print ; new line
mov rdi, rbx
call print_time ; print formatted date
pop rbx ; since we are exiting, we don't need this pop actually
mov rax, 60 ; sys_exit
xor rdi, rdi
syscall
leap_year: ; rsi + (year in rdi is leap)
mov rax, rdi
mov rcx, 4
xor rdx, rdx
div rcx
test rdx, rdx ; return 0 if year % 4
jnz func_leap_year_ret_0
mov rax, rdi
mov rcx, 100
xor rdx, rdx
div rcx
test rdx, rdx ; return 1 if year % 100
jnz func_leap_year_ret_1
mov rax, rdi
mov rcx, 400
xor rdx, rdx
div rcx
test rdx, rdx ; return 0 if year % 400
jnz func_leap_year_ret_0
func_leap_year_ret_1:
lea rax, [rsi + 1]
ret
func_leap_year_ret_0:
mov rax, rsi
ret
year_length: ; length of year in rdi
mov rsi, 365
jmp leap_year
month_length: ; length of month (year in rdi, month in rsi)
push r15
push r14
push r13
mov r14, rsi ; back up month in r14, will be used as index
cmp rsi, 1
setz r15b
movzx r13, r15b
xor rsi, rsi
call leap_year
and r13, rax
movzx rax, byte [r14 + months]
add rax, r13
pop r13
pop r14
pop r15
ret
print_time: ; print time_t in rdi
push r15
push r14
push r13
push r12
mov r14, 1970 ; 1970-01-01T00:00:00Z
xor r15, r15
mov rcx, 60
mov rax, rdi
xor rdx, rdx
div rcx
push rdx ; push #5
xor rdx, rdx
div rcx
push rdx ; push #6
mov rcx, 24
xor rdx, rdx
div rcx
push rdx ; push #7, the last one
mov r12, rax
mov r13, rax
func_print_time_loop_1_start:
mov rdi, r14
call year_length
cmp r13, rax
jb func_print_time_loop_2_start
sub r13, rax
inc r14
jmp func_print_time_loop_1_start
func_print_time_loop_2_start:
mov rdi, r14
mov rsi, r15
call month_length
cmp r13, rax
jb func_print_time_loop_end
sub r13, rax
inc r15
jmp func_print_time_loop_2_start
func_print_time_loop_end:
; print time
mov rdi, [rsp]
call print_num
mov rdi, strings + 6
call sys_print
mov rdi, [rsp + 8]
call print_num
mov rdi, strings + 6
call sys_print
mov rdi, [rsp + 16]
call print_num
; print " "
mov rdi, strings + 2
call sys_print
; print weekday
mov rax, r12
mov rcx, 7
xor rdx, rdx
div rcx
lea rdi, [rdx * 4 + weekdays]
call sys_print
; print " "
mov rdi, strings + 2
call sys_print
; print date
mov rdi, r15
inc rdi
call print_num
mov rdi, strings + 4
call sys_print
mov rdi, r13
inc rdi
call print_num
mov rdi, strings + 4
call sys_print
mov rdi, r14
call print_num
; print new line
mov rdi, strings
call sys_print
add rsp, 24
pop r12
pop r13
pop r14
pop r15
ret
print_num: ; print number in rdi
mov r8, rsp
sub rsp, 24 ; 21 bytes for local storage, with extra 3 bytes to keep stack aligned
xor r9, r9
mov rax, rdi
mov rcx, 10
func_print_num_loop_start:
dec r8
xor rdx, rdx
div rcx
add dl, 48
mov [r8], dl
inc r9b
test rax, rax
jnz func_print_num_loop_start
func_print_num_loop_end:
dec r8
mov [r8], r9b
mov rdi, r8
call sys_print
add rsp, 24 ; deallocate local storage, restore rsp
ret
sys_print: ; print a string pointed by rdi
movzx rdx, byte [rdi]
lea rsi, [rdi + 1]
mov rdi, 1 ; stdout
mov rax, 1 ; write
syscall
ret
print_num
function prints any number in register rdi
. If you want to know how I print a number you can look at that function.
print_time
is the where the date and time are calculated and printed.
Here is the output, along with output from a C program that prints formatted date & time using asctime(gmtime(time_t t))
$ ./time && ./ct
1608515228
1:47:8 Mon 12/21/2020
Unix time: 1608515228
C library returns: Mon Dec 21 01:47:08 2020
(The last two lines are from the C program)
You can also put any timestamp in line 34 to test it out.
My solution is very naive:
- Figure out the total days first, it can be found using time/60/60/24 (and you get your hour/min/sec in this step).
- Then figure out the year. I did this by subtracting number of days in a year, year by year. I designed a function to figure out the number of days in any year as my helper function.
- Find month of year and day of month. It's almost the same with step 2. I designed a function to figure out the number of days in any month of any year as my helper function.
Edit:
Pasted the whole program to this answer, as several people asked.
For printing integer part, I used implementation from @PeterCordes here:
https://stackoverflow.com/a/46301894