Shell: Bash.

Goal: obtain time t in milliseconds since some fixed point in time, suitable for timestamping stuff with printf.

Condition: the solution must pass the all-in-one-line-of-text test.

Additional: the solution should be atomic (yeah, right!..), lightweight, keep quantization & rounding issues to a minimum, blah..

t=$[$(date +%s%N)/1000000] <--- my solution, the fixed point being Jan 1, 1970 in this case. BUT fundamentally bad due to the two date calls.

printf "t=%d\n" $[$(date +%s%N)/1000000] <--- here it is, using printf.

t=$(date +%s)$[10#$(date +%N)/1000000] <--- terrible example. Even seems to need de-pad of, then re-pad with, leading zeros.

printf "t=%d%03d\n" $(date +%s) $[10#$(date +%N)/1000000] <--- here it is, using printf.

Any better (sensible) suggestions?

EDIT (appending):

t=$(date +%s%N) and then printf "%s\n" ${t::13} <--- I guess, but not one line.

  • 10
    How is t=$[$(date +%s%N)/1000000] "two date calls" ?
    – thrig
    Sep 24, 2017 at 17:08
  • I suspected that the %s was one, and the %N was another. Is this incorrect? Sep 24, 2017 at 18:25
  • No, date is run, once, makes a system get-time call, then fills in the template of %s%N with the information from the system call, and emits that data.
    – thrig
    Sep 24, 2017 at 18:40
  • @thrig: Ah, thanks! Which would make your first comment redundant :( Reductionism should be practiced universally. Sep 24, 2017 at 19:27
  • 3
    It's fine to ask for a bash-specific solution, but it's counter-productive to ask answerers not give solutions for other shells. This Q&A would be useful to more people without it as not all people are restricted to using bash or could have the same requirement in other shells. Sep 25, 2017 at 10:00

4 Answers 4


Since bash5+ there is a simple solution:

$ printf "%.3f\n" $EPOCHREALTIME

If the dot is not desired:

printf "%s\n" "$((${EPOCHREALTIME/.}/1000))"

Provide time in milliseconds since epoch all-in-one-line-of-text, atomic (one call to an internal bash variable), lightweight, and no quantization or rounding issues.

  • On my system running bash 5.0.17, $EPOCHREALTIME provides the time in microseconds so you'd need "$(( ${EPOCHREALTIME/.} / 1000000 ))", or better yet: "${EPOCHREALTIME%.*}" which can deal with either milliseconds or microseconds. Nov 22, 2023 at 19:42

As noted by @Isaac, with date implementations that support %N like GNU's or ast-open's, you can use %s%3N to limit the precision, but except in ksh93 where date can be made to be the builtin version of ast-open's date, the date command is not builtin. It will take a few hundred if not thousand microseconds to start and a few more to print the date and return.

bash did copy a subset of ksh93 printf '%(...)T' format, but not the %N part.

Here it looks like you'd need to use more advanced shells like ksh93 or zsh.

Those shells can make their $SECONDS variable which records the time since the shell started (and that you can also reset to any value) floating point:

$ typeset -F SECONDS=0; date +%s%3N; echo $SECONDS

It took up to 1787 microseconds to run GNU date here.

You can use $((SECONDS*1000)) to get a number of milliseconds as both shells support floating point arithmetic (beware ksh93 honours the locale's decimal mark).

For the epoch time as a float, zsh has $EPOCHREALTIME:

$ zmodload zsh/datetime

And ksh93 can use "$(printf '%(%s.%N)T' now)" (note that ksh93's command substitution doesn't fork processes nor use pipes for builtins so is not as expensive as in other Bourne-like shells).

You could also define the $EPOCHREALTIME variable there with:

$ EPOCHREALTIME.get() { .sh.value=$(printf "%(%s.%6N)T");

For automatic timestamping, you can also use set -o xtrace and a $PS4 that prints the current time. In zsh:

$ zsh -c 'PS4="+%D{%s.%.}> "; set -x; sleep 1; date +%s.%N'
+1506332128.753> sleep 1
+1506332129.754> date +%s.%N

In ksh93:

$ ksh -c 'PS4="+\$(printf "%(%s.%3N)T")> "; set -x; sleep 1; date +%s.%N'
+1506332247.844> sleep 1
+1506332248.851> date +%s.%N

Depending on your use case, you may be able to rely on moreutils's ts for your time-stamping:

$ (date +%s.%6N; date +%s.%6N) | ts %.s
1506319395.000080 1506319394.970619
1506319395.000141 1506319394.971972

(ts gives the time it read the line from date's output through the pipe).

Or for time between lines of output:

$ (date +%s.%6N; date +%s.%6N) | ts -i %.s
0.000011 1506319496.806554
0.000071 1506319496.807907

If you want to get the time it took to run a given command (pipeline), you can also use the time keyword, adjusting the format with $TIMEFORMAT in bash:

$ TIMEFORMAT=%E; time date
Mon 25 Sep 09:51:41 BST 2017

Those time format directives initially come from csh (though bash, contrary to zsh or GNU time only supports a tiny subset). In (t)csh, you can time every command by setting the $time special variable:

$ csh -xc 'set time = (0 %E); sleep 1; sleep 2'
set time = ( 0 %E )
sleep 1
sleep 2

(the first number (0 here) tells that commands that take more than that many seconds should be timed, the second specifies the format).

  1. First the date tool is not part of the bash, it is an external tool.
  2. Bash itself can't do it without external commands.
  3. Doing this with external commands can't pass your single-line criterion.

Thus, you can't do it.

However, if you allow 2 lines, or you write a function for that, you can do it, as it was written in the comments.

Extension: you can do it with more recent bashes (>4.2), even as a single-line command, although it won't be the most simplest bash line you've ever wrote. Check the other answer.

  • 1
    Recent (4.2+ I think) versions of bash have strftime formats built in to printf: printf '%(%F)T\n' $(date +%s)
    – Jeff Schaller
    Sep 24, 2017 at 23:15
  • 1
    Well ... ... there is a kind of one liner way if you accept that a command expansion may be a part of a "one liner". Read my answer.
    – user232326
    Sep 25, 2017 at 0:49


$ date +'%s%3N'


An strict bash solution (no external executables) is possible since bash 4.2:

$ printf '%(%s)T\n' "-1"

$ printf '%(%Y%m%d-%H:%M:%S)T\n' "-1"

But that doesn't allow milliseconds, nor nanoseconds (yet).


To get miliseconds or nanoseconds you need to use GNU date as this:

$ printf '%s\n' "$(date +'%Y%m%d-%H:%M:%S.%N')"


$ printf '%s\n' "$(date +'%s.%N')"

The limit to 3 digits in the fractional part of the seconds could be produced with a %.3f format for printf:

$ printf '%.3f\n' "$(date +'%s.%N')"

Or better, use the ability to reduce the number of digits that the date nanoseconds format allow:

$ printf '%s\n' "$(date +'%s.%3N')"

And then, the dot could be removed:

$ printf '%s\n' "$(date +'%s%3N')"

Of course, in this case, the simpler solution (without printf, instead of what was exactly asked) seems more apropiate:

$ date +'%s%3N'
  • wouldn't it be simpler to just call date +.... instead of printf '%s\n' $(date...)?
    – Jeff Schaller
    Sep 29, 2017 at 2:36
  • 1
    Yes, indeed, but the question ask for the use of printf (for whichever reasons the OP had): ... Accurate timestamping in Bash with printf .... @JeffSchaller
    – user232326
    Sep 30, 2017 at 18:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .