8

We can print the current time with the builtin printf function, without needing to invoke an external command like date, like this:

printf '%(%Y-%m-%d:%H:%M:%S)T %s\n' -1
# sample output: 2019-03-30:17:39:36,846

How can we make printf to print milliseconds or nanoseconds as well? Using %3N or %N in the format string doesn't work:

printf '%(%Y-%m-%d:%H:%M:%S,%3N)T %s\n' -1 # outputs 2019-03-30:17:38:16,%3N
printf '%(%Y-%m-%d:%H:%M:%S,%N)T %s\n' -1  # outputs 2019-03-30:17:38:16,%N

However, the date command works fine:

date +%Y-%m-%d:%H:%M:%S,%3N # gives 2019-03-30:17:39:36,846
date +%Y-%m-%d:%H:%M:%S,%N  # gives 2019-03-30:17:39:36,160643077

This is on a Red Hat Linux version 7.3.

codeforester
  • 39,467
  • 16
  • 112
  • 140
  • Just use `date` if you need that – Shawn Mar 31 '19 at 03:38
  • 1
    Yes, `date` works. However, I prefer to use the builtin `printf` instead of using an external command like `date`. – codeforester Mar 31 '19 at 03:40
  • 2
    You can't use the bash `printf` if you want higher resolution than seconds because it (and the underlying call to `strftime(3)` used for `%(foo)` doesn't support such a thing. – Shawn Mar 31 '19 at 03:42
  • 2
    @codeforester This is not ***nano***, this is ***micro seconds***! To play with ***nano seconds*** you have to use *`/proc/timerlist`*. See [How to play with nanoseconds in bash](https://stackoverflow.com/a/19067658/1765658) ! (Second part, where I tell about ***Hires sleep***. – F. Hauri - Give Up GitHub Oct 25 '19 at 11:21

2 Answers2

6

v5 and $EPOCHREALTIME

  • EPOCHREALTIME floating point value with micro-second granularity

  • EPOCHSECONDS the number of seconds since the Unix Epoch

Correct way

Simply:

IFS=. read ESEC NSEC <<<$EPOCHREALTIME
printf '%(%F:%T)T.%06.0f\n' $ESEC $NSEC

Or if you really don't need to store values:

printf '%(%F:%T)T.%06.0f\n' ${EPOCHREALTIME/./ }

1. About $EPOCHREALTIME

Please care:

    EPOCHREALTIME
         Each time this parameter is referenced, it expands to the number
         of seconds since the Unix Epoch  (see  time(3))  as  a  floating
         point  value  with  micro-second  granularity.

So, if I ask for same variable two time in same line:

echo $EPOCHREALTIME...  $EPOCHREALTIME 
1572000683.886830... 1572000683.886840

or more clearly:

printf "%s\n" ${EPOCHREALTIME#*.} ${EPOCHREALTIME#*.}
761893
761925

echo $((  -10#${EPOCHREALTIME#*.} + 10#${EPOCHREALTIME#*.} ))
37

Same on my raspberry-pi:

printf "%s\n" ${EPOCHREALTIME#*.} ${EPOCHREALTIME#*.}
801459
801694

echo $((  -10#${EPOCHREALTIME#*.} + 10#${EPOCHREALTIME#*.} ))
246

So inquiring this two time for building interger part and fractional part is separated process could lead to issues: (On same line first access to $ EPOCHREALTIME could give: NNN1.999995, then next: NNN2.000002. Than result will become: NNN1.000002 with 1000000 micro-second error)

2. WARNING! About mixing $EPOCHSECONDS and $EPOCHREALTIME

Using both together not only lead to first mentioned bug!

$EPOCHSECONDS use call to time() which is not updated constantly, while $EPOCHREALTIME use call to gettimeofday()! So results could differ a lot:

I found This answer to time() and gettimeofday() return different seconds with good explanation.

If I try on my host:

epochVariableDiff () {
    local errcnt=0 lasterrcnt v1 v2 v3 us vals line
    while ((errcnt==0)) || ((errcnt>lasterrcnt)); do
        lasterrcnt=$errcnt
        printf -v vals '%(%s)T %s %s' -1 $EPOCHSECONDS $EPOCHREALTIME
        IFS=$' .' read v1 v2 v3 us <<<"$vals"
        [ "$v1" = "$v2" ] && [ "$v2" = "$v3" ] || ((errcnt++))
        [ $errcnt -eq 1 ] && echo "$line"
        printf -v line '%3d %s - %s - %s . %s' $errcnt $v1 $v2 $v3 $us
        printf "%s\r" "$line"
        ((errcnt)) && echo "$line"
        read -t ${1:-.0002}
    done
}

(
Nota: I use read -t instead of sleep, because sleep is not builtin
Nota2: You could play with argument of function to change value of read timeout (sleep)
)

This could render something lile:

$ epochVariableDiff .0002
  0 1586851573 - 1586851573 - 1586851573 . 999894
  1 1586851573 - 1586851573 - 1586851574 . 000277
  2 1586851573 - 1586851573 - 1586851574 . 000686
  3 1586851573 - 1586851573 - 1586851574 . 001087
  4 1586851573 - 1586851573 - 1586851574 . 001502
  5 1586851573 - 1586851573 - 1586851574 . 001910
  6 1586851573 - 1586851573 - 1586851574 . 002309
  7 1586851573 - 1586851573 - 1586851574 . 002701
  8 1586851573 - 1586851573 - 1586851574 . 003108
  9 1586851573 - 1586851573 - 1586851574 . 003495
 10 1586851573 - 1586851573 - 1586851574 . 003899
 11 1586851573 - 1586851573 - 1586851574 . 004400
 12 1586851573 - 1586851573 - 1586851574 . 004898
 13 1586851573 - 1586851573 - 1586851574 . 005324
 14 1586851573 - 1586851573 - 1586851574 . 005720
 15 1586851573 - 1586851573 - 1586851574 . 006113
 16 1586851573 - 1586851573 - 1586851574 . 006526
 17 1586851573 - 1586851573 - 1586851574 . 006932
 18 1586851573 - 1586851573 - 1586851574 . 007324
 19 1586851573 - 1586851573 - 1586851574 . 007733
 19 1586851574 - 1586851574 - 1586851574 . 008144

Where integer part of $EPOCHREALTIME could increase more than 8000 microseconds before $EPOCHSECONDS (on my host).

Nota: This seem to be linked to some bug, result could differ a lot between different hosts or on same host after reboot, and other things... Strangely I could reproduce them on a lot of different hosts (Intel Core, Intel Xeon, Amd64..) but not on raspberry pi!? (Same Debian bash v5.0.3(1)-release), different kernel version.

Correct: This is not a bug! Mixing time() and gettimeofday() is a bug!

So avoid using both together !!!

3. About printf "..%06.0f"

Nota: I use %06.0f instead of %d to ensure $NSEC to be interpreted as a decimal (float), (prevent octal interpretation if variable begin by 0).

Compare:

printf "nn.%06.0f\n" 012345
nn.012345

printf "nn.%06.0f\n" 098765
nn.098765

and

printf "nn.%d\n" 012345
nn.5349

printf "nn.%d\n" 098765
-bash: printf: 098765: invalid octal number
nn.0

Sample run: Showing time at start of each seconds...

Little test:

wait until next second, then print current time with micro-seconds

while ! read -sn 1 -t .$(( 1000000 - 10#${EPOCHREALTIME#*.} )) _; do
    IFS=. read ESEC NSEC <<< $EPOCHREALTIME
    printf '%(%F:%T)T.%06.0f\n' $ESEC $NSEC
done

You could end this test by pressing Any key.

2023-01-06:17:28:51.000135
2023-01-06:17:28:52.000095
2023-01-06:17:28:53.000109
2023-01-06:17:28:54.000108
2023-01-06:17:28:55.000132
2023-01-06:17:28:56.000166
2023-01-06:17:28:57.000099
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
2

In bash 5, you can get microsecond precision from EPOCHREALTIME. However, printf itself has no way to access that directly, so you need to extract the microseconds yourself.

$ echo $EPOCHREALTIME; printf '%(%F:%T)T.%d\n' "$EPOCHSECONDS" "${EPOCHREALTIME#*.}"; echo $EPOCHREALTIME
1554006709.936990
2019-03-31:00:31:49.937048
1554006709.937083

This takes a little time, but the result appears to be accurate to about 0.05 milliseconds.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • 2
    Please see [my answer](https://stackoverflow.com/a/58557346/1765658) ; there is at least two issues: 1) Asking two time on `$EPOCHREALTIME` and 2) Using `printf %d` on a variable that could hold string like `012345` (will be interpreted as octal by `%d`) – F. Hauri - Give Up GitHub Oct 25 '19 at 11:13
  • Try this: `errcnt=3;while ! read -t .001 foo ;do printf '%(%F:%T)T.%d\n' "$EPOCHSECONDS" "${EPOCHREALTIME#*.}" || ((errcnt--)) || break; done 2>&1 | tail -n20` and try this again... – F. Hauri - Give Up GitHub Oct 25 '19 at 13:36
  • 2
    ... And you use `EPOCHSECONDS` and `EPOCHREALTIME` which are not *rounded* in same way! Please try command posted on previous command or even this one: `errcnt=5;v1=$EPOCHSECONDS v2=$EPOCHREALTIME ;echo $v1 ${v2%.*};while [ "$v1" = "${v2%.*}" ] || ((errcnt--));do read -t .0001; v1=$EPOCHSECONDS v2=$EPOCHREALTIME ;echo $v1 ${v2%.*} - ${v2#*.};done` – F. Hauri - Give Up GitHub Oct 29 '19 at 09:19
  • With bash 5 this command works faster and have nicer look for me: `printf "%(${TIMESTAMP_FORMAT})T.%s %s\n" ${EPOCHREALTIME/./ } 'some text'` where `TIMESTAMP_FORMAT='%Y-%m-%dT%H:%M:%S'` or other – Mikhail Feb 09 '21 at 13:27