Search code examples
c++datedatetimetimec++-chrono

Converting time from one environment into another in C++


I get uint64_t value from the server side (that's running .NET app) into my application that's written in standard C++ (that has to run on Windows and Linux).

This number represents Windows Filetime - aka 100-nanosecond intervals since the epoch which is 1601-01-01 00:00:00 UTC.

I need in my application to return a string representation of the time, with an accuracy of nanoseconds (which is the accuracy i get in the value from the server) so i must use the chrono library.

Since the epoch of C++ is January 1, 0001 to January 1, 1970, i first need to to calculate the offset from 1970 to 1601 and subtract it from the number i get from the server. In order to do that i first have to represent the value i got from the server as a chrono::time_point, and calculate the epoch 1601-01-01 00:00:00 UTC in 100-nanosecond intervals scale so it and the value i got are on the same scale.

After i have the addjustedTimeFromServer - which is the value i get from the server minus (in the chrono::time_point form) the offset, i need to convert it into std::time_t in order to extract the value with accuracy of seconds, and then from the chrono::time_point i need to extract the fractional_seconds which will give me the accuracy of nanoseconds, and i will concatenate them to the string representing the time.

Here is my code. It doesn't do what i need:

using FileTime = duration<int64_t, ratio<1, 10000000>>;
struct std::tm tm;

//create time point for epoch of Windows Filetime (1601-01-01 00:00:00 UTC))
std::istringstream ss("1601-01-01 00:00:00");
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); 
std::time_t tt = mktime(&tm);
std::chrono::system_clock::time_point offset =std::chrono::system_clock::from_time_t(tt); 

//convert the offset into 100-nanosecond intervals scale
auto offset_ns = std::chrono::time_point_cast<std::chrono::nanoseconds>(offset);
auto offset_100ns = FileTime(offset_ns.time_since_epoch());
//substract the offset from i so now it starts from 1970 like the epoch of C++
auto iDuration = FileTime(static_cast<int64_t>(i));
//auto iDuration_ns = std::chrono::time_point_cast<std::chrono::nanoseconds>(iDuration); //doesn't compile - but that's the idea of what i want to do in this line


std::chrono::system_clock::time_point adjustedTime = iDuration/*iDuration_ns*/ - offset /*-offset_100ns*/; //the commented out parts are what i think is the correct thing to do (scale wise) but they don't compile

//convert the time_point into the string representation i need (extract the regular time, up to seconds, with time_t and the nanosecond part with ns.count())
nanoseconds ns = duration_cast<nanoseconds>(adjustedTime.time_since_epoch());
seconds s = duration_cast<seconds>(ns);
std::time_t t = s.count();
std::size_t fractional_seconds = ns.count() % 10000000;
std::cout << std::ctime(&t) << std::endl;
std::cout << fractional_seconds << std::endl;

The code doesn't work and i am not sure how to fix it.The first problem (even before all the scale conversion issue) is that mktime(&tm) gives me an incorrect value. Since tm represents a value that's before C++ epoch, mktime(&tm) returns -1. I need to somehow overcome it, since i have to calculate the time_point of the .NET Filetime epoch (1601-01-01 00:00:00 UTC) in order to subtract it from the value i get from the server.

I will appriciate help with this issue and with the program in general.

P.S I just print in this code but in the final version i will concatenate both parts to the same string (the part given by ctime(&t) and the part in fractional_seconds)


Solution

  • Here is code to do this which was actually donated to this site by a member of the MSVC std::lib team (Billy O'Neal).

    Repeated here:

    // filetime_duration has the same layout as FILETIME; 100ns intervals
    using filetime_duration = duration<int64_t, ratio<1, 10'000'000>>;
    // January 1, 1601 (NT epoch) - January 1, 1970 (Unix epoch):
    constexpr duration<int64_t> nt_to_unix_epoch{INT64_C(-11644473600)};
    
    system_clock::time_point
    FILETIME_to_system_clock(FILETIME fileTime)
    {
        const filetime_duration asDuration{static_cast<int64_t>(
            (static_cast<uint64_t>(fileTime.dwHighDateTime) << 32)
                | fileTime.dwLowDateTime)};
        const auto withUnixEpoch = asDuration + nt_to_unix_epoch;
        return system_clock::time_point{
            duration_cast<system_clock::duration>(withUnixEpoch)};
    }
    

    This converts to system_clock::time_point, which has nanoseconds precision on Linux and 100-nanosecond precision on Windows.

    Using my date/time library, it is easy to format system_clock::time_point to full precision with any format you want.

    Also, here it is without using the Windows FILETIME structure:

    #include "date.h"
    #include <string>
    #include <iostream>
    
    std::chrono::system_clock::time_point
    FILETIME_to_system_clock(std::uint64_t fileTime)
    {
        using namespace std;
        using namespace std::chrono;
        // filetime_duration has the same layout as FILETIME; 100ns intervals
        using filetime_duration =duration<int64_t, ratio<1, 10000000>>;
        // January 1, 1601 (NT epoch) - January 1, 1970 (Unix epoch):
        constexpr duration<int64_t> nt_to_unix_epoch{INT64_C(-11644473600)};
    
        const filetime_duration asDuration{static_cast<int64_t>(fileTime)};
        const auto withUnixEpoch = asDuration + nt_to_unix_epoch;
        return system_clock::time_point{
               duration_cast<system_clock::duration>(withUnixEpoch)};
    }
    
    int
    main()
    {
        std::string s = date::format("%F %T", FILETIME_to_system_clock(131400356659154460));
        std::cout << s << '\n';
    }
    

    This just output for me:

    2017-05-23 17:54:25.915446
    

    Note that this is only to microseconds precision. On Linux, this will format to nanoseconds precision because system_clock::time_point has nanoseconds precision on that platform.

    If that weren't the case, you could force nanosecond-precision like this:

        using namespace std::chrono;
        std::string s = date::format("%F %T",
            time_point_cast<nanoseconds>(FILETIME_to_system_clock(131400356659154460)));
    

    Output for me:

    2017-05-23 17:54:25.915446000
    

    In this update the precision of the output chrono::time_point is the same as Window's FILETIME: 100-ns precision:

    #include "date.h"
    #include <string>
    #include <iostream>
    
    using filetime_duration = std::chrono::duration<std::int64_t, std::ratio<1, 10000000>>;
    using FileTime = std::chrono::time_point<std::chrono::system_clock, filetime_duration>;
    // or more simply:
    // using FileTime = date::sys_time<filetime_duration>;
    
    FileTime
    FILETIME_to_system_clock(std::uint64_t fileTime)
    {
        using namespace std;
        using namespace std::chrono;
        // filetime_duration has the same layout as FILETIME; 100ns intervals
        // January 1, 1601 (NT epoch) - January 1, 1970 (Unix epoch):
        constexpr seconds nt_to_unix_epoch{-11644473600};
    
        const filetime_duration asDuration{static_cast<int64_t>(fileTime)};
        return FileTime{asDuration + nt_to_unix_epoch};
    }
    
    int
    main()
    {
        using namespace std::chrono;
        std::string s = date::format("%F %T", FILETIME_to_system_clock(131400356659154461));
        std::cout << s << '\n';
    }
    

    Output:

    2017-05-23 17:54:25.9154461