2

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.

  • 9
    How is t=$[$(date +%s%N)/1000000] "two date calls" ? – thrig Sep 24 '17 at 17:08
  • I suspected that the %s was one, and the %N was another. Is this incorrect? – Anthony Webber Sep 24 '17 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 '17 at 18:40
  • @thrig: Ah, thanks! Which would make your first comment redundant :( Reductionism should be practiced universally. – Anthony Webber Sep 24 '17 at 19:27
  • 1
    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. – Stéphane Chazelas Sep 25 '17 at 10:00
4

As noted by @Arrow, 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
1506318780647
0.0017870000

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
$ echo $EPOCHREALTIME
1506318947.2758708000

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");
$ echo "$EPOCHREALTIME"
1506333341.962697

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
1506332129.755322928

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
1506332248.853111699

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
0.002

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
0:01.00
sleep 2
0:02.00

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

  • @Arrow, see my comment and edit on the question. – Wildcard Sep 26 '17 at 2:49
2
  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
    criterion, not criteria :) – Anthony Webber Sep 24 '17 at 19:29
  • @AnthonyWebber Oh, grenglish rulez :-) I fixed it. – peterh Sep 24 '17 at 19:30
  • @peterh a Latin rule, or maybe Greek. – RonJohn Sep 24 '17 at 22:11
  • 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 '17 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. – Arrow Sep 25 '17 at 0:49
2

tl;dr

$ date +'%s%3N'
1506298414529

bash

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

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

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

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

date

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

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

Or

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

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')"
1506298414.529

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

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

And then, the dot could be removed:

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

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

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

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.