Date in milliseconds on OpenWRT on Arduino YUN

8,322

Solution 1

Actually there is also a package called coreutils-date! Didn't know about it! There all standard functionality is included!

Solution 2

On OpenWRT, date is busybox, which has limitations, but this is not strictly one of them. The underlying problem is that the libc (uClibc) does not support this GNU strftime extension. (Though neither does glibc, more on that below.)

You should have lua by default, but that won't help without some other non-default modules.

hwclock calls gettimeofday() for comparing/setting the RTC (hardware clock), but it won't output sub-second resolution (accessing RTCs can be sufficient slow that it might not be useful anyway). Other than that OpenWRT only provides the ancient rdate, which only has whole-second resolution.

There appears to be no straightforward way to get an accurate time stamp directly from /proc, the most useful time stamp is in /proc/timer_list (3rd line) which is the uptime in nanoseconds (the resolution will depend on the platform).

If your busybox was built with CONFIG_BUSYBOX_CONFIG_ADJTIMEX set, then you should be able to use adjtimex to read the kernel clock (though note that the busybox version has both different arguments and different output to the standard adjtimex.

Normal version, adjtimex -p, last line of output:

   raw time:  1416419719s 146628us = 1416419719.146628

Busybox version, adjtimex (without -p !), last 3 lines:

   [...]
   time.tv_sec:  1416420386
   time.tv_usec: 732653
   return value: 0 (clock synchronized)

Goldilocks's is a fine solution, assuming you have your OpenWRT cross build setup (highly recommended!). Your coreutils-date solution works because while coreutils is glibc aware, it is not exclusively glibc. It comes with its own standalone implementation of strftime (derived from glibc), and uses that to wrap up (via strftime_case()) the underlying strftime so as to support various extensions (and falls back to the uClibc version otherwise).

Even glibc (up to the current 2.23) doesn't support %N, the coreutils strftime() derived from the canonical glibc version adds %N and %:z and a few other changes. Variations and patched versions of strftime() abound (including versions in bash and gawk).

Solution 3

If you have a C compiler on it and can't find anything else, this will report, e.g.

> millitime && sleep 1 && millitime
14/11/2014 9:39:49:364
14/11/2014 9:39:50:368
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>

int main (void) {
    struct timeval now;
    struct tm *parsed;

    if (gettimeofday(&now, NULL) == -1) {
        fprintf (
            stderr,
            "gettimeofday() failed: %s\n",
            strerror(errno)
        );
        return 1;
    }

    parsed = localtime((const time_t*)&now.tv_sec);
    if (!parsed) {
        fprintf (
            stderr,
            "localtime() failed: %s\n",
            strerror(errno)
        );
        return 1;
    }

    printf (
        "%d/%d/%d %d:%02d:%02d:%03d\n",
        parsed->tm_mday,
        parsed->tm_mon + 1,
        parsed->tm_year + 1900,
        parsed->tm_hour,
        parsed->tm_min,
        parsed->tm_sec,
        now.tv_usec / 1000
    );

    return 0;
}  

With gcc, just compile it gcc whatever.c -o millitime. If there's an error (which would be very weird), it reports to stderr and exits with a status of 1. Otherwise it reports to stdout and exits 0.

The milliseconds are rounded down from microseconds.

Solution 4

I've found that I can get timing information to the hundredth of a second from /proc/uptime in an embedded Linux system with busybox, though converting it to an epoch time is hard because I cannot find any record of the boot time.

Here's some quick sh code:

read UPTIME < /proc/uptime
UPTIME="${UPTIME%%[^0-9.]*}"

If all you need is a timer with precision to 100ths of seconds, this is good enough (time zero, your local epoch, will merely be the time the system booted).

Otherwise, you can cheat and trigger a log entry:

printf '\u04' |nc $HOSTNAME 22 >/dev/null 2>&1 # trigger log entry by poking ssh
LOG="$(logread -l 1 -t)"                       # read last log entry w/ timestamp
LOG="${LOG##* 20?? \[}"                        # remove text before timestamp
echo "${LOG%%\]*}"                             # remove text after timestamp

This consumes 0.026s (median; min=0.025s, max=0.028s, out of 12 runs). Also note that it does pollute your logs ... and I don't know what happens when the logs get "too big" (for whatever value that is); keep in mind logs are stored in memory.

Therefore, if you're going to be polling the time a lot, you should probably only create one bad log entry at the beginning and combine the two above methods as follows:

get_uptime_ms() {
  local MS
  read UPTIME < /proc/uptime
  UPTIME="${UPTIME%%[^0-9.]*}" # strip the other listed time
  MS="${UPTIME##*.}0"          # extra zero turns 100ths of seconds into 1000ths
  UPTIME="${UPTIME%%.*}$MS"    # the time since boot in milliseconds
}

get_uptime_ms
printf '\u04' |nc $HOSTNAME 22 >/dev/null 2>&1  # poke ssh to trigger log entry 
NOW="$(logread -l 1 -t)"                        # last log entry, with timestamp
NOW="${NOW#* 20?? \[}"                          # remove text before timestamp
NOW="${NOW%%\]*}"                               # remove text after timestamp
MS="${NOW##*.}"                                 # just the milliseconds
NOW="${NOW%%.*}$MS"                             # append ms to s w/out dot

BOOT=$(( NOW - UPTIME ))

date_ms() {
  local S
  get_uptime_ms
  NOW=$(( BOOT + UPTIME))
  S="${NOW%???}"           # $NOW in seconds
  echo "$S.${NOW#$S}"      # put the dot back in, then append milliseconds
}

# now you can run date_ms as much as you like
# and it'll have the epoch time with millisecond precision.

date_ms
date +%s
date_ms

This establishes something to use as the original boot time by subtracting the uptime (to 100th of a second) from the log trigger's timestamp (precise to 1000th of a second) and then updates it with the uptime. This therefore yields 100ths of a second rather than 1000ths, but it only injects one extra entry into logd and the other method isn't much more precise since it costs 0.026s to run. (It's decently consistent on my system, so you could consider running it a bunch of times and subtracting that value.)

Share:
8,322

Related videos on Youtube

Tschwen
Author by

Tschwen

Updated on September 18, 2022

Comments

  • Tschwen
    Tschwen almost 2 years

    I am using OpenWRT on the Arduino YUN and I am trying to get the exact date in milliseconds (DD/MM/YYYY h:min:sec:ms) by getting the time by an timeserver.

    Unfortunately date +%N just returns %N, but not the nanoseconds. I heard +%N is not included in OpenWRT date.

    So is there any way to get the date (including milliseconds) how I want it?

    • Costas
      Costas over 9 years
      Do you have date --rfc-3339=ns
    • Tschwen
      Tschwen over 9 years
      No: date: unrecognized option --rfc-3339=ns
  • rkagerer
    rkagerer almost 9 years
    Thanks. awk 'NR==3 {print $3}' /proc/timer_list came in handy for me when I needed finer resolution time than any of these utilities granted under the build I'm using.
  • minghua
    minghua about 6 years
    The /proc/timer_list 3rd line works for me. Not using openwrt, just an embedded linux on busybox.
  • Adam Katz
    Adam Katz over 5 years
    My OpenWRT router doesn't have /proc/timer_list and also lacks an adjtimex command, but I was able to use /proc/uptime for 100th-second resolution and logread -t for millisecond resolution; see my answer below. No extra packages or compilation needed.
  • Maxattax
    Maxattax over 2 years
    adjtimex | grep -i tv | cut -d: -f2 | sed 's/^ *//g' | tr '\n' '.' | sed 's/.$/\n/' Produces 1649180458.33628