25
\$\begingroup\$

Challenge

Your task is to write a program which, once a second (including immediately when your program is started), prints the elapsed time from the time your program was started.

Rules

  • Time must be printed in hh:mm:ss format. (leading zeros for single-digit values)
  • The time stamps must be separated by CR, LF, or CRLF. (no leading whitespace)
  • A new time must appear every second. (stdout cannot be buffered for a second)
  • The behavior of the program if it is run past 23:59:59 is undefined.
  • You may use sleep(1) even if a specific second may be skipped whenever the overhead to print, calculate, loop, etc. accumulates to a second.

Example output:

00:00:00
00:00:01
00:00:02
00:00:04
00:00:05
⋮

Note that 00:00:03 is missing here due to processing overhead. The actual skipped values (if any) are of course dependent on implementation and/or system.

Reference implementation in C: (POSIX-compatible systems only)

#include <unistd.h> // sleep()
#include <tgmath.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#ifndef __STDC_IEC_559__
#error "unsupported double"
#endif
static_assert(sizeof(double) == 8, "double must have double precision");
#define MAX_PRECISE_DOUBLE ((double)(1ULL << 52))

int main(void) {
    time_t start = time(NULL);
    if (start == (time_t)-1) return EXIT_FAILURE;
    while (1) {
        time_t now = time(NULL);
        if (now == (time_t)-1) return EXIT_FAILURE;

        double diff = difftime(now, start);
        if (isnan(diff) || diff < 0) return EXIT_FAILURE;
        if (diff > MAX_PRECISE_DOUBLE) return EXIT_FAILURE;

        unsigned long long seconds = diff;
        unsigned long long h = seconds / 3600;
        seconds %= 3600;
        unsigned long long m = seconds / 60;
        seconds %= 60;
        unsigned long long s = seconds;

        (void)printf("\r%02llu:%02llu:%02llu", h, m, s);
        (void)fflush(stdout);

        (void)sleep(1);
    }
}

Winning criteria

This is , shortest code in bytes win!

\$\endgroup\$
1
  • \$\begingroup\$ Note for later challenges, clarification in the comments is a bad thing to do. reference \$\endgroup\$
    – user202729
    Commented Dec 27, 2017 at 3:56

37 Answers 37

1
2
1
\$\begingroup\$

x86-64 machine code (Linux system call): 78 bytes

RDTSC spin-loop timing, Linux sys_write system call.

x86-64 doesn't provide a convenient way to query the RDTSC "reference clock" frequency at run time. You can read an MSR (and do a calculation based on that), but that requires kernel mode, or root + opening /dev/cpu/%d/msr, so I decided to make the frequency a build-time constant. (Adjust FREQ_RDTSC as necessary: any 32-bit constant won't change the size of the machine code)

Note that x86 CPUs for several years have had fixed RDTSC frequency so it is usable as a timesource, not a core clock cycle performance counter unless you take steps to disable frequency changes. (There are actual perf counters for counting real CPU cycles.) Usually it ticks near the nominal sticker frequency, e.g. 4008 MHz for my 4.0 GHz i7-6700k regardless of turbo or powersaving. (SO answer covering many aspects of RDTSC.)

Anyway, this busy-wait timing doesn't depend on load average / interrupts (like a calibrated delay-loop would), and also isn't sensitive to CPU power saving. (Related: Stack Overflow answer with a spin-wait on RDTSC, with delay cycles calculated from nanoseconds, although it still needs the ref frequency set externally.)

This code will work for any x86 with a reference frequency below 2^32 Hz, i.e. up to ~4.29 GHz. Beyond that, the low 32 of the timestamp would wrap all the way in 1 second, so I'd have to look at the edx high 32 bits of the result, too.

Summary:

push 00:00:00\n on the stack. Then in a loop:

  • sys_write system call
  • ADC-loop over the digits (starting with the last) to increment the time by 1. Wrapping / carry-out handled with a cmp / cmov, with the CF result providing the carry-in for the next digit.
  • rdtsc and save the start time.
  • spin on rdtsc until the delta is >= ticks per second of the RDTSC frequency.

NASM listing:

 1  Address                            ; mov  %1, %2       ; use this macro to copy 64-bit registers in 2 bytes (no REX prefix)
 2           Machine code           %macro MOVE 2
 3           bytes                      push  %2
 4                                      pop   %1
 5                                  %endmacro
 6                                  
 7                                      ; frequency as a build-time constant because there's no easy way detect it without root + system calls, or kernel mode.
 8                                      FREQ_RDTSC equ 4000000000
 9                                  global _start
10                                  _start:
11 00000000 6A0A                        push     0xa                       ; newline
12 00000002 48BB30303A30303A3030        mov      rbx, "00:00:00"
13 0000000C 53                          push     rbx
14                                      ; rsp points to  `00:00:00\n`
20                                  
21                                      ; rbp = 0                (Linux process startup.  push imm8 / pop is as short as LEA for small constants)
22                                      ; low byte of rbx = '0'
23                                  .print:
24                                      ; edx potentially holds garbage (from rdtsc)
25                                  
26 0000000D 8D4501                      lea      eax, [rbp+1] ; __NR_write = 1
27 00000010 89C7                        mov      edi, eax     ; fd = 1 = stdout
28                                      MOVE     rsi, rsp
28 00000012 54                  <1>  push %2
28 00000013 5E                  <1>  pop %1
29 00000014 8D5008                      lea      edx, [rax-1 + 9]     ; len = 9 bytes.
30 00000017 0F05                        syscall               ; sys_write(1, buf, 9)
31                                  
32                                      ;; increment counter string:  least-significant digits are at high addresses (in printing order)
33 00000019 FD                          std                        ;  so loop backwards from the end, wrapping each digit manually
34 0000001A 488D7E07                    lea      rdi, [rsi+7]
35                                      MOVE     rsi, rdi
35 0000001E 57                  <1>  push %2
35 0000001F 5E                  <1>  pop %1
36                                  
37                                      ;; edx=9 from the system call
38 00000020 83C2FA                      add   edx, -9 + 3      ; edx=3 and set CF (so the low digit of seconds will be incremented by the carry-in)
39                                      ;stc
40                                  .string_increment_60:          ; do {
41 00000023 66B93902                    mov    cx, 0x0200 + '9'    ; saves 1 byte vs. ecx.
42                                      ; cl = '9' = wrap limit for manual carry of low digit.  ch = 2 = digit counter
43                                    .digitpair:
44 00000027 AC                          lodsb
45 00000028 1400                        adc      al, 0           ; carry-in = cmp from previous iteration; other instructions preserve CF
46 0000002A 38C1                        cmp      cl, al          ; manual carry-out + wrapping at '9' or '5'
47 0000002C 0F42C3                      cmovc    eax, ebx        ; bl = '0'.  1B shorter than JNC over a MOV al, '0'
48 0000002F AA                          stosb
49                                  
50 00000030 8D49FC                      lea     ecx, [rcx-4]    ; '9' -> '5' for the tens digit, so we wrap at 59
51 00000033 FECD                        dec     ch
52 00000035 75F0                        jnz    .digitpair
53                                      ; hours wrap from 59 to 00, so the max count is 59:59:59
54                                  
55 00000037 AC                          lodsb                        ; skip the ":" separator
56 00000038 AA                          stosb                        ; and increment rdi by storing the byte back again.  scasb would clobber CF
57                                  
58 00000039 FFCA                        dec     edx
59 0000003B 75E6                        jnz   .string_increment_60
60                                  
61                                      ; busy-wait for 1 second.  Note that time spent printing isn't counted, so error accumulates with a bias in one direction
62 0000003D 0F31                        rdtsc                         ; looking only at the 32-bit low halves works as long as RDTSC freq < 2^32 = ~4.29GHz
63 0000003F 89C1                        mov      ecx, eax             ; ecx = start
64                                  .spinwait:
65                                  ;    pause
66 00000041 0F31                        rdtsc                      ; edx:eax = reference cycles since boot
67 00000043 29C8                        sub      eax, ecx          ; delta = now - start.  This may wrap, but now we have the delta ready for a normal compare
68 00000045 3D00286BEE                  cmp      eax, FREQ_RDTSC   ; } while(delta < counts_per_second)
69                                   ;   cmp      eax, 40  ; fast count to test printing
70 0000004A 72F5                        jb     .spinwait
71                                  
72 0000004C EBBF                        jmp .print
  next address = 0x4E = size = 78 bytes.

Uncomment the pause instruction to save significant power: this heats one core up by ~15 degrees C without pause, but only by ~9 with pause. (On Skylake, where pause sleeps for ~100 cycles instead of ~5. I think it would save more if rdtsc wasn't also slow-ish so the CPU isn't doing much a lot of the time).


A 32-bit version would be a few bytes shorter, e.g. using a 32-bit version of this to push the initial 00:00:00\n string.

16                          ;    mov      ebx, "00:0"
17                          ;    push     rbx
18                          ;    bswap    ebx
19                          ;    mov      dword [rsp+4], ebx    ; in 32-bit mode, mov-imm / push / bswap / push would be 9 bytes vs. 11

And also using 1-byte dec edx. The int 0x80 system call ABI wouldn't use esi / edi, so the register setup for the syscall vs. lodsb / stosb might be simpler.

\$\endgroup\$
1
  • \$\begingroup\$ I could have used a nanosleep system call, but this was more interesting. With root on Linux, it is possible to read the right MSR and programmatically get the RDTSC frequency. \$\endgroup\$ Commented Jan 2, 2018 at 20:42
0
\$\begingroup\$

Ocaml, 125 117 bytes

let rec f i=Printf.printf"%02d:%02d:%02d\n"(i/3600)(i/60 mod 60)(i mod 60);flush stdout;Unix.sleep 1;f(i+1)let()=f 0

This is the naive way to do it, minus useless spaces. With a regular coding style, it is :

let rec f i = 
  Printf.printf "%02d:%02d:%02d\n" (i/3600) (i/60 mod 60) (i mod 60);
  flush stdout;
  Unix.sleep 1;
  f (i+1)
let () = f 0

[EDIT] I forgot to remove spaces before submission... (-8 bytes)

\$\endgroup\$
0
\$\begingroup\$

Windows Batch, 218 bytes

Only golfed a bit, can be improved.

@echo off
set/ah=m=s=0
:L
set r=00%h%
set r=%r:~-2%
set t=00%m%
set t=%t:~-2%
set y=00%s%
set y=%y:~-2%
echo %r%:%t%:%y%
set/as+=1
if %s%==60 set s=0&set/am+=1
if %m%==60 set m=0&set/ah+=1
timeout 1 /nobreak>nul
goto L
\$\endgroup\$
0
\$\begingroup\$

mIRC 7.49 53 Bytes

set %i 0 | timer 0 1 say $!duration(%i,3) $(|) inc %i
\$\endgroup\$
0
\$\begingroup\$

JavaScript (ES6), 68 67 bytes

f=m=>setTimeout(f,c=1e3,m+c|c,alert((8+new Date(m|0)).split` `[4]))

Assumes the alert is dismissed immediately (for example, by holding down the <enter> key); if that's not permitted then add 6 bytes for console.log.


Try it

alert overridden with console.log for your sanity!

alert=console.log
f=m=>setTimeout(f,c=1e3,m+c|c,alert((8+new Date(m|0)).split` `[4]))
f()

\$\endgroup\$
3
  • \$\begingroup\$ Umm, it starts at 01:00:00 instead of 00:00:00. \$\endgroup\$ Commented Jan 5, 2018 at 16:04
  • \$\begingroup\$ @KevinCruijssen; it might be locale dependent. \$\endgroup\$
    – Shaggy
    Commented Feb 14, 2018 at 15:57
  • \$\begingroup\$ Ah, you're probably right, since The Netherlands is in the +1 timezone. Maybe add some code at the start of the Try it code to set the timezone to +0? \$\endgroup\$ Commented Feb 14, 2018 at 16:13
0
\$\begingroup\$

Batch 183 187 Bytes

@Set/AH=m=0,s=-1
:L
@(Set/As+=1,"1/(60-s)"||Set/As=0,m+=1,"1/(60-m)"||Set/Am=0,h+=1) 2> nul
@Set .s=0%s%&Set .m=0%m%&Set .h=0%h%
@Echo(%.h:~-2%:%.m:~-2%:%.s:~-2%&>nul Timeout 1&Goto:L
\$\endgroup\$
0
\$\begingroup\$

Perl 5, 87 bytes

$s=time;{$n=time-$s;printf"%02d:%02d:%02d\n",int$n/3600,int$n/60%60,$n%60;sleep 1;redo}

Try it online!

Note: The header section forces TIO to display the output; it is not required to run this on a local system.

Ungolfed:

$start = time;
{
    $now = time - $start;
    printf(
        "%02d:%02d:%02d\n",
        int($now / 3600),
        int($now / 60 % 60),
        $now % 60
    );
    sleep 1;
    redo;
}
\$\endgroup\$
1
2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.