Search code examples
c++windowsparsingc++-chrono

Converting tm to time_t on windows in UTC - _mkgmtime returning -1


I am trying to parse UTC ECMA date format strings into a std::chrono::system_clock::time_point on Windows.

My plan:

  1. Use scanf to load the string into a struct tm
  2. Use _mkgmtime to turn that into a time_t (I am using _mkgmtime for this, because it uses UTC time, unlike something like mktime, which uses local time).
  3. Use std::chrono::system_clock::from_time_t to convert to a time_point

However, step 2 is always returning -1 for me, giving me bogus values for step 3.

The tm struct seems valid, so I'm not sure what I'm doing wrong.

#include <chrono>
#include <iostream>

using namespace std;

int main() {
  string timeStr = "2020-12-01T06:06:48Z";

  struct tm tm = {0};
  float s;
  if (sscanf_s(timeStr.c_str(), "%d-%d-%dT%d:%d:%fZ", &tm.tm_year, &tm.tm_mon,
               &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &s) == 6) {
    tm.tm_sec = static_cast<int>(s);

    time_t t = _mkgmtime(&tm);
    cout << "YEAR: " << tm.tm_year << endl;  // 2020
    cout << "MON:  " << tm.tm_mon << endl;   // 12
    cout << "MDAY: " << tm.tm_mday << endl;  // 1
    cout << "HOUR: " << tm.tm_hour << endl;  // 6
    cout << "MIN:  " << tm.tm_min << endl;   // 6
    cout << "SEC:  " << tm.tm_sec << endl;   // 48
    cout << "TIME: " << t << endl;           // -1

    // TODO once this is solved:
    auto tp = chrono::system_clock::from_time_t(t);
  }
}

Compiler is MSVC 2019, C++17, x64. I cannot upgrade to C++20, or else I would use the new chrono tools for this.


Solution

  • In a tm structure the member tm_year is the number of years since 1900. Feeding in 2020 means you're actually providing year 3920.

    It looks like _mkgmtime hates that year. On my system _mkgmtime64 doesn't appear to work either. It should fit in a 64 bit time_t, but definitely out of range for the 32 bit version. I can't find a clear-cut, documented reason for 3920 to be rejected or a cut-off point after which the function gives up. Make sure you have a 64 bit time_t.

    But none of that matters in this case since we want the year 2020. Adding

    tm.tm_year -= 1900;
    tm.tm_mon -= 1; // valid range 0 to 11, not 1 to 12
    

    before converting will fix things up.