6
\$\begingroup\$

Introduction

Time is a really simple concept. Seconds pass, one after the other, all the same. But humans love making it hard. This Stackoverflow question has gathered many answers and even though they are surprisingly long and complex, most of them will still fail for certain inputs. Can you format a number of seconds into a timestamp for human consumption not only precisely, but correctly?

Challenge

Build a human-readable timestamp from a floating point number of seconds. The timestamp must be formatted as hh:mm:ss.zzz. Single digit hours, minutes and seconds must be formatted with a leading zero. If the number of complete hours is zero, the hours and leading colon must be omitted, making the output format mm:ss.zzz. Please note that the number of hours may have more than two digits. For inputs that cannot be expressed as a finite number, the result must be --:--.--- (see below for examples of this that probably apply to your language). This is code-golf, so the shortest code in bytes wins.

Example Input and Output

0          -> 00:00.000
-0         -> 00:00.000
1          -> 00:01.000
-1         -> -00:01.000
0.001      -> 00:00.001
3.141528   -> 00:03.142
3600       -> 01:00:00.000
3599.9995  -> 01:00:00.000
363600     -> 101:00:00.000
17999.9999 -> 05:00:00.000
Infinity   -> --:--.---
-Infinity  -> --:--.---
NaN        -> --:--.---
\$\endgroup\$
12
  • 1
    \$\begingroup\$ Does the number of milliseconds have to be rounded or can it be truncated? \$\endgroup\$
    – Neil
    Jun 3 at 9:10
  • 4
    \$\begingroup\$ What if my language does not support infinities or NaNs? \$\endgroup\$
    – Adám
    Jun 3 at 10:01
  • 1
    \$\begingroup\$ Then you don't have to handle them. The examples are a guess for things you'll probably have to handle in most languages, but as long as you handle all values that your language's floating point type can have, I'll consider it solved. \$\endgroup\$
    – Nuvanda
    Jun 3 at 10:15
  • 6
    \$\begingroup\$ The rules give a large advantage to languages that happen not to support infinity/NaN/etc. \$\endgroup\$
    – Jonah
    Jun 3 at 13:09
  • 5
    \$\begingroup\$ Unless there's a good reason to focus on a specific language, we usually want challenges to be as much language-agnostic as possible and allow flexible I/O. So the fair way to settle this would be to simply assume that the input is a finite float. (You may even consider allowing an integer representing a number of microseconds as an alternate input format for languages that don't have floats at all.) \$\endgroup\$
    – Arnauld
    Jun 3 at 16:18

7 Answers 7

4
\$\begingroup\$

JavaScript (ES6), 112 bytes

f=n=>1/n?n<0?'-'+f(-n):(n=n*1e3+.5,(k=n/36e5|0)?k>9?k:'0'+k:'')+new Date(n).toJSON().slice(13+!k,-1):'--:--.---'

Try it online!

How?

We use the toJSON() method to extract the minutes, seconds and milliseconds directly in the expected format. Such a date representation is always expressed as UTC, so we don't have to bother about the system time zone.

1970-01-01T00:00:00.000Z
              \_______/

Hours are formatted manually. Unless there's no complete hour and the field is not inserted at all, the leading colon in the above string is also included.

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

JavaScript™, 161 bytes

t=n=>(f=parseInt,O='0',p=n<0?(n=-n,'-'):'',h=f(n/3600),m=f(n/60%60),s=(n%60).toFixed(3),n-n==0?p+(h?(h>9?h:O+h)+':':'')+(m>9?m:O+m)+':'+(s<10?O+s:s):'--:--.---')

I could save 13 bytes if I used |0 to truncate h & m, but I think that would cause it to fail on very large numbers (e.g. Number.MAX_SAFE_INTEGER/3600|0 === -1966140585.

Also, this is somewhat silly, but I think this could even work in an old ES3-only engine if the arrow function were swapped out for a normal one? (the other JS answer uses .toJSON which was added in ES5 I believe).

(n => (
    // constants
    f = parseInt,
    O = '0',
    // variables
    p = n<0?(n=-n,'-'):'',
    h = f(n/3600),
    m = f(n/60%60),
    s = (n%60).toFixed(3),
    // return value
    n-n==0
        ? p+(h?(h>9?h:O+h)+':':'')+(m>9?m:O+m)+':'+(s<10?O+s:s)
        : '--:--.---'
) )
\$\endgroup\$
2
  • \$\begingroup\$ Welcome to Code Golf! Nice first answer. \$\endgroup\$
    – Steffan
    Jun 4 at 3:49
  • 1
    \$\begingroup\$ You can actually exclude t= from the byte count: see meta post. \$\endgroup\$
    – Steffan
    Jun 4 at 3:52
2
\$\begingroup\$

MATLAB, 114 bytes

function d(i),if~isfinite(i),'--:--.---',end,f='hh:mm:ss.SSS';if abs(i)<3600,f=f(4:12);end,i=seconds(i);i.Format=f

Using MATLAB's built in 'duration' class (and its derivative, seconds) I can convert the float input i to a duration and change the output format to the proper format for the challenge. If the input is nan or inf though, the duration will be displayed as nan or inf, so I hard-coded the exception string.

TIO doesn't have MATLAB, but octave, and octave doesn't have the builtin 'seconds' class, so I can't show a TIO link.

\$\endgroup\$
2
\$\begingroup\$

05AB1E, 44 bytes

3.òÄƵ/v60ym‰`}₄*ò)T‰J`₄+¦)…::..ι¬_·.$JIï'-Ãì

05AB1E doesn't have NaN nor Inf. It can handle number-strings with leading 0s though, so I've added a test case to my test suite for it.

Formatting takes about 2/3rd of the code, although can most likely be golfed a bit more..

Try it online or verify all test cases.

Explanation:

Step 1: Get the individual hours, minutes, seconds, and milliseconds:

3.ò       # Round the (implicit) input by 3 decimals
   Ä      # Take the absolute value of it
Ƶ/        # Push compressed integer 210
  v       # Loop over its digits `y`:
   60ym   #  Push 60 to the power `y`
          #  (3600,60,1 in the three iterations respectively)
       ‰  #  Divmod the top integer by this
        ` #  Pop and push the quotient and remainder separately to the stack
   }      # After the loop
    ₄*    # Multiply the top (decimal) value by 1000
      ò   # (Banker's) round it to the nearest integer
       )  # Wrap all values on the stack into a list

See this 05AB1E tip of mine (section How to compress large integers?) to understand why Ƶ/ is 210.

Step 2: Format and output them:

T‰        # Divmod all values in the list by 10
  J       # Then join the pairs together
          # (≥10 will remain unchanged; <10 will have a single leading 0)
   `      # Pop and push all values to the stack again
    ₄+    # Add 1000 to the top value
      ¦   # Remove the leading 1
          # (to format numbers #/##/### to "00#"/"0##"/"###" respectively)
       )  # Wrap the stack back into a list
…::.      # Push string "::."
    .ι    # Interleave the list with this as list [":",":","."]
¬         # Push the first item (without popping the list itself)
 _        # Check if it's 0; or technically "00" (1 if "00"; 0 otherwise)
  ·       # Double it
   .$     # Remove that many leading items from the list
     J    # Join everything in the stack together
I         # Push the input again
 ï        # Cast it to an integer (edge case "-0" becomes 0)
  '-Ã    '# Only keep the potential "-" (or an empty string otherwise)
     ì    # Prepend that to the string
          # (after which the result is output implicitly)
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Gives erroneous output for e.g. 17999.9999. \$\endgroup\$
    – Neil
    Jun 3 at 16:39
  • \$\begingroup\$ @Neil Thanks for noticing, fixed at the cost of 3 bytes. \$\endgroup\$ Jun 3 at 19:20
2
\$\begingroup\$

Charcoal, 64 bytes

≔∕⌊⁺·⁵×φNφθF‹θ⁰-≦↔θ⊞υ﹪%06.3f﹪θ⁶⁰F²«≧÷⁶⁰θ¿¬‹θι⊞υ﹪%02d⎇ιθ﹪θ⁶⁰»⪫⮌υ:

Try it online! Link is to verbose version of code. Explanation:

≔∕⌊⁺·⁵×φNφθ

Input the number of seconds and round it to the nearest millisecond (rounding 0.5 to infinity). (Note that Charcoal only supports inputs of the form -?\d+(.\d+)?.)

F‹θ⁰-

If the number is negative then output a -.

≦↔θ

Take the absolute value of the input.

⊞υ﹪%06.3f﹪θ⁶⁰

Take the seconds part of the value and format it to 6 zero-filled characters of which 3 are decimals.

F²«

Repeat for the minutes and the hours.

≧÷⁶⁰θ

Floor divide the value by 60.

¿¬‹θι

If the value is not a zero number of hours, then...

⊞υ﹪%02d⎇ιθ﹪θ⁶⁰

... format the minutes or hours as appropriate to 2 zero-filled characters.

»⪫⮌υ:

Correct the order of the parts of the time and join them with :s.

I did try writing a Retina answer but implementing rounding was too arduous. Without rounding it took 135 bytes: Try it online! Link includes test cases.

\$\endgroup\$
1
\$\begingroup\$

Python, 123 bytes

lambda x:'--:--.---'*(x*0!=x*0)or'-'*(x<0)+f'{(q:=abs(round(x,3)))/3600:02.0f}:'*(q>=3600)+f'{q//60%60:02.0f}:{q%60:06.3f}'

Attempt This Online!

I don't believe Python has the concept of Infinity or NaN, so this function only accepts a floating point number. TIL about float('inf') and float('nan').

(x*0!=x*0)          # If the input is infinite or NaN...
'--:--.---'*        # ...return the result filled with '-'
or                  # Otherwise:
'-'*(x<0)           # Prefix with '-' if the input is negative
q:=abs(round(x,3))  # Initialize q = abs(x) rounded to 3 decimals
*(q>=3600)          # If q >= 3600...
+f'{q/3600:02.0f}:' # ...append q/3600, padded with 0s. Append ':'

# Finally, append minutes (q//60%60) padded with 0s, a literal ':',
# and seconds (q%60) padded with 0s and rounded to 3 decimal places
+f'{q//60%60:02.0f}:{q%60:06.3f}'
\$\endgroup\$
1
\$\begingroup\$

C (gcc), 200 183 175 bytes

  • -8 thanks to Juan Ignacio Díaz

I round to the nearest millisecond before performing calculations to avoid incorrect displays around 3599.9995 seconds. To remove the hours part if not required and fix up spacing for negative values, I use positional formatting in printf().

f(a,b,c,d)double a,b;{c=*(long*)&a<0;b=fabs(rint(a*1000)/1000);d=b<3600;printf(a*0?"--:--.---":"%5$0*4$.f:%0*.f:%06.3f"+d*10,c*d+2,fmod((d?a:b)/60,60),fmod(b,60),c+2,a/3600);}

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ Good catch, I'll add that as a test case. \$\endgroup\$
    – Nuvanda
    Jun 4 at 12:43
  • \$\begingroup\$ 175 bytes \$\endgroup\$ Jun 13 at 21:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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