Lloyd Rochester's Geek Blog

Timestamps in C

In this post I’ll provide some ways to create, convert and print timestamps using C. We’ll first create a Unix epoch which corresponds to seconds since January 1st 1970 at 00:00:00 UTC. We will also represent the epoch in milliseconds, as a double, and finally convert to an ISO 8601 Timestamp. We’ll conclude with the challenge of using fractional seconds.

Table of Contents

Epoch using clock_gettime from time.h

Get the epoch in seconds and nanoseconds using clock_gettime. This is POSIX.1-2001. We will use CLOCK_REALTIME since all implementations will support this per man 3 clock_gettime.

#define _GNU_SOURCE
#include <time.h>
#include <error.h>

struct timespec tv;
if(clock_gettime(CLOCK_REALTIME, &tv))
  perror("error clock_gettime\n");

tv.tv_sec;   // a long int with seconds
tv.tv_nsec;  // a long with nanoseconds

Epoch using gettimeofday from sys/time.h

An older method and thus arguably the most portable. Get the epoch in seconds and microseconds.

#include <sys/time.h>

struct timeval ts;
gettimeofday(&ts, NULL); // return value can be ignored
ts.tv_sec; // seconds
ts.tv_usec // microseconds

Converting the Epoch to a double

Python for example represents the epoch as a number with the integer part as seconds and the decimal part as fractions of a second. For example the double epoch looks like 1607095578.328412294. We’ll have 9 figures after the decimal point for the nanoseconds. Not to say the clock is actually this accurate.

double
epoch_double(struct timespec *tv)
{
  char time_str[32];

  sprintf(time_str, "%ld.%.9ld", tv->tv_sec, tv->tv_nsec);

  return atof(time_str);
}

Calling looks like:

struct timespec tv;

if(clock_gettime(CLOCK_REALTIME, &tv))
  perror("error clock_gettime\n");

// prints 1607095578.328412294
printf("%.9f\n", epoch_double(&tv));

Converting the Epoch to Milliseconds

Java and Javascript like to represent the epoch as an integer in milliseconds. We use our epoch_double from above for this call. Note, we will to use math.h for the round function. Thus, we also need to link with the math library with -lm.

long int
epoch_millis(struct timespec *tv)
{
  double epoch;
  epoch = epoch_double(tv);
  epoch = round(epoch*1e3);

  return (long int) epoch;
}

This call looks like:

struct timespec tv;

if(clock_gettime(CLOCK_REALTIME, &tv))
  perror("error clock_gettime\n");

// prints 1607089375345 for 1607089375 seconds and 344958300 nanoseconds
printf("%ld\n", epoch_millis(&tv));

Time in ISO 8601 Format

We can get an ISO 8601 Formatted Timestamp. We’ll do some extra work so that we have the factional part of seconds which will make the output look like 2020-12-04T14:20:22.257Z for example. This function supports only UTC timezones.

char *
rfc8601_timespec(struct timespec *tv)
{
  char time_str[127];
  double fractional_seconds;
  int milliseconds;
  struct tm tm; // our "broken down time"
  char *rfc8601;

  rfc8601 = malloc(256);

  memset(&tm, 0, sizeof(struct tm));
  sprintf(time_str, "%ld UTC", tv->tv_sec);

  // convert our timespec into broken down time
  strptime(time_str, "%s %U", &tm);

  // do the math to convert nanoseconds to integer milliseconds
  fractional_seconds = (double) tv->tv_nsec;
  fractional_seconds /= 1e6;
  fractional_seconds = round(fractional_seconds);
  milliseconds = (int) fractional_seconds;

  // print date and time without milliseconds
  strftime(time_str, sizeof(time_str), "%Y-%m-%dT%H:%M:%S", &tm);

  // add on the fractional seconds and Z for the UTC Timezone
  sprintf(rfc8601, "%s.%dZ", time_str, milliseconds);

  return rfc8601;
}

To call this function do the following:

struct timespec tv;
char *rfc8601;

if(clock_gettime(CLOCK_REALTIME, &tv))
  perror("error clock_gettime\n");

rfc8601 = rfc8601_timespec(&tv);

// prints 2020-12-04T14:20:22.257Z
printf("%s\n", rfc8601);
free(rfc8601);

Fractional Seconds - The Problem

If you either want to convert an epoch from struct timespec or from struct timeval to a printable time you’re stuck dealing with fractional seconds! The time.h library doesn’t help much.

Why?

When using time.h the best method to print time to a string is using the strftime function. This function takes the broken down time. Let’s look at this structure:

struct tm {
    int tm_sec;        /* seconds */
    int tm_min;        /* minutes */
    int tm_hour;       /* hours */
    int tm_mday;       /* day of the month */
    int tm_mon;        /* month */
    int tm_year;       /* year */
    int tm_wday;       /* day of the week */
    int tm_yday;       /* day in the year */
    int tm_isdst;      /* daylight saving time */
};

What is missing?? I don’t see any fractional seconds here!

When we get the epoch we have the following which contain fractional seconds in microseconds and nanoseconds.

In time.h

struct timespec {
    time_t   tv_sec;  /* seconds */
    long     tv_nsec; /* nanoseconds */
};

Or for the sys/time.h we have a timeval.

struct timeval {
   time_t      tv_sec;  /* seconds */
   suseconds_t tv_usec; /* microseconds */
};

Both of these give fractional seconds, however, the broken down time in struct tm leaves us hanging.

Accuracy of the Clock

We can use the clock_getres function to see our accuracy. I used it like so:

void
print_clock_res(struct timespec *tv)
{
  if(clock_getres(CLOCK_REALTIME, tv))
    perror("clock_getres\n");

  if(tv->tv_nsec)
    printf("high resolution accuracy\n");
  else
    printf("low resolution accuracy\n");
}

In this case the value of tv->tv_nsec was set to 1 and tv->tv_sec was set to 0. There are also ways to link with a high resolution time hrt using -lrt which I don’t have. Ideally, we would know if the accuracy is in milliseconds, microseconds, nanoseconds ….