Retrouver gettimeofday dans Windows

Windows n'offre pas la fonction gettimeofday si chère aux programmeurs ayant pris leurs habitudes avec les systèmes d'exploitation UNIX. En outre, Windows ne compte pas le temps à partir de la même date de référence que les systèmes UNIX. L'epoch utilisé par Windows est le premier janvier 1601 alors que sous les systèmes UNIX il s'agit plutôt du premier janvier 1970.

Il est toutefois possible de trouver sur MSDN une fonction, manquant lamentablement de commentaires, qui permet de convertir un time_t de la librairie standard du C défini en fonction de l'epoch UNIX en un FILETIME défini en fonction de l'epoch Windows. Le code de cette fonction est reproduit ci-dessous.

#include <windows.h>
#include <time.h>

void TimetToFileTime( time_t t, LPFILETIME pft )
{
    LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000;
    pft->dwLowDateTime = (DWORD) ll;
    pft->dwHighDateTime = ll >>32;
}

Quelques opérations arithmétiques révèlent bien vite la logique derrière cette fonction.

116444736000000000 / 10   = 11644473600000000  microsecondes
11644473600000000  / 1000 = 11644473600000     millisecondes
11644473600        / 1000 = 11644473600        secondes
11644473600        / 60   = 194074560          minutes
194074560          / 60   = 3234576.0          heures
3234576            / 24   = 134774.0           jours
134774             / 365  = 369.2              années

En effet, la différence entre les années 1970 et 1601 est bien de 369 ans et des poussières.

Implanter gettimeofday sous Windows consiste donc tout simplement à effectuer le chemin inverse de la fonction TimetToFileTime. C'est-à-dire que 369 ans doivent être retranchés à la valeur retournée par la fonction GetSystemTimeAsFileTime.

#include <windows.h>

int _gettimeofday(struct timeval* p, void* tz) {
    ULARGE_INTEGER ul; // As specified on MSDN.
    FILETIME ft;

    // Returns a 64-bit value representing the number of
    // 100-nanosecond intervals since January 1, 1601 (UTC).
    GetSystemTimeAsFileTime(&ft);

    // Fill ULARGE_INTEGER low and high parts.
    ul.LowPart = ft.dwLowDateTime;
    ul.HighPart = ft.dwHighDateTime;
    // Convert to microseconds.
    ul.QuadPart /= 10ULL;
    // Remove Windows to UNIX Epoch delta.
    ul.QuadPart -= 11644473600000000ULL;
    // Modulo to retrieve the microseconds.
    p->tv_usec = (long) (ul.QuadPart % 1000000LL);
    // Divide to retrieve the seconds.
    p->tv_sec = (long) (ul.QuadPart / 1000000LL);

    return 0;
}

Quelques références utiles: