Printing current time in milliseconds or nanoseconds with printf builtin
Asked Answered
S

2

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.

Suction answered 31/3, 2019 at 0:47 Comment(4)
Just use date if you need thatJoellajoelle
Yes, date works. However, I prefer to use the builtin printf instead of using an external command like date.Suction
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.Joellajoelle
@Suction 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 ! (Second part, where I tell about Hires sleep.Sivan
C
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.

Cerotype answered 31/3, 2019 at 4:13 Comment(4)
Please see my answer ; 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)Sivan
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...Sivan
... 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#*.};doneSivan
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 otherGloat
S
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
Sivan answered 25/10, 2019 at 11:6 Comment(1)
Have a look at my function epochVariableDiff to see how both variables could differ!Sivan
C
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.

Cerotype answered 31/3, 2019 at 4:13 Comment(4)
Please see my answer ; 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)Sivan
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...Sivan
... 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#*.};doneSivan
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 otherGloat

© 2022 - 2024 — McMap. All rights reserved.