On Windows, how do you get time in nanoseconds in C?

I recently decided to go back to dabbling in OCaml. The easier path would have been to install it in a Linux VM and play with it there, but I decided to build it from source to use Microsoft’s C compiler. The instructions are clear and easy to follow. The advantage of compiling with cl is that while the actual build does need the Cygwin tools, the resulting binaries have no restrictions on them as they are not linked with the Cygwin DLL.

After ocaml itself was built and installed, it was opam next. Similarly, build and install were uneventful, made easy by the availability of excellent instructions.

Once opam was installed and initialized, I tried opam install core to install JaneStreet’s Core library. The installation failed because core_kernel depends on time_now and time_now does not compile with MS Visual C:

  * install time_now v0.14.0

<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
▼ retrieved time_now.v0.14.0  (cached)
[ERROR] The compilation of time_now.v0.14.0 failed at "dune build -p time_now -j 1".

#=== ERROR while compiling time_now.v0.14.0 ===================================#
# context     2.1.0~rc2 | win32/x86_64 | ocaml.4.12.0 | https://opam.ocaml.org#6609b442
# path        ~\.opam\default\.opam-switch\build\time_now.v0.14.0
# command     ~\.opam\default\bin\dune.exe build -p time_now -j 1
# exit-code   1
# env-file    ~\.opam\log\time_now-8632-6ce4ee.env
# output-file ~\.opam\log\time_now-8632-6ce4ee.out
### output ###
#           cl src/time_now_stubs.obj (exit 2)
# (cd _build/default/src && "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\
VC\Tools\MSVC\14.29.30037\bin\HostX64\x64\cl.exe" -nologo -O2 -Gy- -MD -D_CRT_SECURE_NO_DEP
RECATE -nologo -O2 -Gy- -MD -I c:/opt/ocaml/lib/ocaml -I C:\opt\cygwin64\home\user\.opam\d
efault\lib\base -I C:\opt\cygwin64\home\user\.opam\default\lib\base\base_internalhash_type
s -I C:\opt\cygwin64\home\user\.[...]
# time_now_stubs.c
# time_now_stubs.c(25): fatal error C1083: Cannot open include file: 'sys/time.h': No such
file or directory

If you look at the time_now source code, the reason is easy to easy to see: POSIX clock_gettime is not available, so we try to compile the fallback which uses gettimeofday whose prototype comes from the non-standard sys/time.h in that while it is generally available on Linux, it is not in any C standard. A simple Google search for getting time in nanoseconds on Windows yields a number of cases where people wrote a simple function that provided the same prototype and similar functionality to use with older Microsoft compilers: E.g., gettimeofday in PostgreSQL, gettimeofday on Stackoverflow, or an implementation in Webstone.

I decided to dig a little deeper instead of copying and pasting one of these solutions.

It turns out, MS Visual C has supported timespec_get since Visual Studio 2015:

C:\> cl /?
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24215.1 for x64
...

Compiling and executing the following C program:

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

static uint64_t
time_now(void)
{
    struct timespec ts;

    if (timespec_get(&ts, TIME_UTC) != TIME_UTC)
    {
        fputs("timespec_get failed!", stderr);
        return 0;
    }
    return 1000000000 * ts.tv_sec + ts.tv_nsec;
}

int main(void)
{
    printf("%" PRIu64 "\n", time_now());
    return EXIT_SUCCESS;
}

gives:

C:\> cl t.c
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24215.1 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

t.c
Microsoft (R) Incremental Linker Version 14.00.24215.1
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:t.exe
t.obj

C:\> perl -E "my $t = `t`; say $t; say scalar localtime($t/1_000_000_000)"
1626610211198802200

Sun Jul 18 08:10:11 2021

It seems like the time has about 100 ns resolution:

C:\> perl -E "system 't' for 1 .. 10"
1626610285846156200
1626610285862472200
1626610285875507600
1626610285886970500
1626610285902410000
1626610285918170900
1626610285932019000
1626610285946177400
1626610285960105900
1626610285973125100

At this point, I decided that going from the state of the world where time_now had no implementation that worked with cl to one where it works with versions of the compiler released since 2015 was good enough progress that I opened a PR to provide a standard timespec_get implementation when time_now is being compiled with recent versions of cl. The PR is limited in scope on purpose as I do not want to impose on the library authors a choice to move away from stuff that works well enough on POSIX/Linux systems just to cater to this specific case.

I also added the information to two of the questions I found on Stackoverflow: What should I use to replace gettimeofday() on Windows? and Equivalent of gettimeday() for Windows. The original questions/answers predate the availability of timespec_get and are reasonable, but, these days, it is possible to use the standard C function.

Finally, if you are using C++, not that Visual C++ provides C++17 and experimental C++20 support and it pays to keep an eye on chrono.

PS: You can discuss this post on HN.