2

I am writing current GMT time as string as follow :

  const std::time_t now = std::time(nullptr);
  std::stringstream ss;
  ss << std::put_time(std::gmtime(&now), "%Y-%m-%d %H:%M:%S");

Later I want to do the reverse operation, reading time from the stringstream as GMT, and compare it to current timestamp :

std::tm tm = {};
ssTimestamp >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
const std::time_t&& time = std::mktime(&tm);
const double timestampDiff((std::difftime(std::time(nullptr), time)));

Something is missing in the code below, because the decoded time is never converted to GMT, thus I end up with 1 hour time difference due to my local timezone

P.S : Can use only standard libraries, and can' t change date string format

Othman Benchekroun
  • 1,998
  • 2
  • 17
  • 36
  • You have no time zone information in your string, so mktime uses the default time zone (local). A solution would be to add the time zone information in your string (%Z). – Gwen Jul 04 '19 at 09:08
  • I can't change the string format, is there a way to specify the timezone while decoding ? – Othman Benchekroun Jul 04 '19 at 09:10
  • Then modify the string just before calling get_time. See answer below. – Gwen Jul 04 '19 at 09:31

3 Answers3

4

The C++20 spec has a convenient way to do this:

using namespace std::chrono;
sys_seconds tp;
ssTimestamp >> parse("%Y-%m-%d %H:%M:%S", tp);
std::time_t time = system_clock::to_time_t(tp);

No vendor has yet implemented this part of C++20, but there is an example implementation here in namespace date.

There is no library support to do this operation in C++ prior to C++20.

The best you can do using only standard libraries is to parse the fields into a tm using std::get_time (as your question shows), and then convert that {y, m, d, h, M, s} structure to a time_t using your own math, and the assumption (which is generally true) that std::time_t is Unix Time with a precision of seconds.

Here is a collection of public domain calendrical algorithms to help you do that. This is not a 3rd party library. It is a cookbook for writing your own date library.

For example:

#include <ctime>

std::time_t
to_time_t(std::tm const& tm)
{
    int y = tm.tm_year + 1900;
    unsigned m = tm.tm_mon + 1;
    unsigned d = tm.tm_mday;
    y -= m <= 2;
    const int era = (y >= 0 ? y : y-399) / 400;
    const unsigned yoe = static_cast<unsigned>(y - era * 400);      // [0, 399]
    const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1;  // [0, 365]
    const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;         // [0, 146096]
    return (era * 146097 + static_cast<int>(doe) - 719468)*86400 +
           tm.tm_hour*3600 + tm.tm_min*60 + tm.tm_sec;
}

The link above has a very in-depth description of this algorithm and unit tests to make sure it works over a range of +/- millions of years.

The above to_time_t is essentially a portable version of timegm that ships on linux and bsd platforms. This function is also called _mkgmtime on Windows.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
0

The tm struct doesn't store the timezone information, it's mktime that by default uses the local timezone. Following this thread, the best option would be to use:

#include "time.h" 
timestamp = mktime(&tm) - timezone; //or _timezone

if timezone or _timezone is available to your compiler. A comment in the linked answer warns that it may raise issues with daylight saving time, but it should not apply to GMT.

Gwen
  • 1,436
  • 3
  • 23
  • 31
0

I recently tried to solve a very similar problem. I was trying to convert a string to a specific timezone regardless what is the current timezone of a computer. Here is the solution that I came up with and works as expected:

std::time_t from_time_str(std::string time_str) {
    std::stringstream ss;
    std::tm tm = {};
    ss << time_str;
    ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
    std::time_t t = std::mktime(&tm);
    std::tm* gm_tm = std::gmtime(&t);
    gm_tm->tm_isdst = false;
    std::time_t gm_t = std::mktime(gm_tm);
    std::time_t gm_offset = (gm_t - t);
    std::time_t real_gm_t = t - gm_offset;
    return real_gm_t;
}

The idea is use the function gmtime to get the gmtime of the timestamp so we could calculate the offset of the target computer's timezone. We then subtract the offset to get the GM time.

Note that the line gm_tm->tm_isdst = false; is required for any timezone that has daylight saving is enable, otherwise the gmtime is calculated with daylight saving offset (1 hour off) and this should not be the desired effect of calculating the actual GM time.

Kenneth Tang
  • 166
  • 3
  • 7