Getting time since Unix epoch on both Windows NT and *NIX systems

The following snippet of C includes implementations for both Windows NT and POSIX-compliant systems to get the time (with microsecond resolution) since the Unix epoch. In the case of Windows NT, the time is registered as 100-nanosecond ticks since the NT epoch (1/1/1601), so it requires a bit of conversion.

I feel like this is the best solution I could've come up with, but it still feels a bit clunky. See if you can help:

#if defined(__unix__) || defined(unix) || defined(__unix) || defined(__CYGWIN__)
#include <sys/time.h>
static struct timeval lnm_current_time;
#elif defined(_WIN32) || defined(__WINDOWS__)
#include <windows.h>
#include <sysinfoapi.h>
static FILETIME lnm_win32_filetime;
#else
#error lognestmonster: Neither Windows NT nor a POSIX-compliant system were detected.\
Implement your own system time functions or compile on a compliant system.
#endif

uint64_t lnm_getus(void) {
uint64_t us;
#if defined(__unix__) || defined(unix) || defined(__unix) || defined(__CYGWIN__)
gettimeofday(&lnm_current_time, NULL);
us = (lnm_current_time.tv_sec*1000000+lnm_current_time.tv_usec);
#elif defined(_WIN32) || defined(__WINDOWS__)
// get system time in ticks
GetSystemTimeAsFileTime(&lnm_win32_filetime);
// load time from two 32-bit words into one 64-bit integer
us = lnm_win32_filetime.dwHighDateTime;
us = us << 32;
us |= lnm_win32_filetime.dwLowDateTime;
// convert to microseconds
us /= 10;
// convert from time since Windows NT epoch to time since Unix epoch
us -= 11644473600000000ULL;
#endif
return us;
}
$$$$


Using static file-scope variables

static struct timeval lnm_current_time;
// ...
static FILETIME lnm_win32_filetime;


makes your code thread-unsafe: Two “simultaneous” invocations of your function from different threads access the same memory. (For other potential drawbacks of static file-scope variables see for example Are file-scope static variables in C as bad as extern global variables? on Software Engineering.)

And there is no reason to use static variables here. With function local variables

uint64_t lnm_getus(void) {
uint64_t us;
#if defined(__unix__) || defined(unix) || defined(__unix) || defined(__CYGWIN__)
struct timeval lnm_current_time;
gettimeofday(&lnm_current_time, NULL);
// ...
#elif defined(_WIN32) || defined(__WINDOWS__)
FILETIME lnm_win32_filetime;
GetSystemTimeAsFileTime(&lnm_win32_filetime);
// ...
#endif
return us;
}


the variables are defined exactly where they are needed, which makes the code easier to read and to argue about, and avoids the threading problem.

In larger programs, lots of "compiler switches" (#ifdefs) make the code very hard to read, "clunky" if you will. It's not an issue in a tiny program such as this, but for larger programs you should consider a more "polymorphic" approach.

Like for example having a generic "epoch.h" as the platform-independent API. Link this with with a corresponding "epoch_unix.c" or "epoch_windows.c" file, where the C file contains everything OS-specific. Manage which one that gets linked using different builds and/or version control.

Some minor details:

• You forgot #include <stdint.h>.

• lnm_current_time.tv_sec*1000000+lnm_current_time.tv_usec. Make a habit of always declaring integer constants with a large enough type, no matter where they happen to be in an expression.

That is, 1000000ULL, or if you prefer UINT64_C(1000000), the former giving type unsigned long long and the latter giving type uint_least64_t. The latter is ever so slightly more portable, but a bit harder to read.

• The multiplication will implicitly convert from int to the appropriate type, won't it? (And it's epoch, not epoc.) Mar 31 '20 at 16:01
• @S.S.Anne Only if lnm_current_time.tv_sec is of an appropriate type. Which is hard to tell, you have to dig deep in documentation to find out just what time_t` actually is. Apr 1 '20 at 6:59