Why are my time conversions inconsistent?

Quick background: my application accesses multiple data sources that report timestamped data, each timestamp is reported as Unix epoch seconds, e.g. data reports 1656331210. The user can input a requested time to report as an ISO date string, such as 2022-06-27T12:00:10Z. These two values are considered equivalent. My problem is consistently converting back and forth.

My function formatTimestamp shown below correctly converts the given timestamp value to an ISO string. However, my function to convert from a string to the same value produces a different value. I know there is an error in my logic and/or understanding.

(As a side note, I’ve scoured the docs for chrono and Howard Hinnant’s date.h libraries for understanding as well. I stumble greatly on converting from one type to another and can never seem to come up with a simple and consistent solution. I know there is one, but I’m not there yet.)

Please take a look at my sample code below and correct the error of my ways:

#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

uint64_t from_isoTimeString(std::string iso_time_string)
{
   //--- takes an UTC ISO formatted time/date string and converts it to a timestamp value
   std::tm tmb;
   std::stringstream ss(iso_time_string);
   ss >> std::get_time(&tmb, "%Y-%m-%dT%TZ");
   if (ss.fail())
   {
      std::string errmsg = "unable to convert Timestamp '" + iso_time_string + "' from ISO 8601 format";
      throw std::invalid_argument(errmsg);
   }

   std::time_t tt;
   tt = mktime(&tmb);              // returns 1656345610, expecting 1656331210
   tt = mktime(&tmb) - _timezone;  // returns 1656327610, expecting 1656331210
   return static_cast<uint64_t>(tt);
}

std::string formatTimestamp(uint64_t epoch_seconds, std::string timestamp_format)
{
   std::time_t tt = epoch_seconds;

   std::tm tmb;
   gmtime_s(&tmb, &tt);

   std::stringstream ss;
   ss << std::put_time(&tmb, timestamp_format.c_str());
   if (ss.fail())
   {
      std::string err_msg = "unable to convert Timestamp to " + timestamp_format + " format";
      throw std::invalid_argument(err_msg);
   }
   return ss.str();
}


int main() 
{
   uint64_t time1_uint = 1656331210;
   std::string time1_str = "2022-06-27T12:00:10Z";

   std::cout << "            time1_str: " << time1_str << std::endl;
   std::cout << " time1_uint as string: " << formatTimestamp(time1_uint, "%Y-%m-%dT%TZ" ) << std::endl;
   std::cout << "           time1_uint: " << time1_uint << std::endl;
   std::cout << "time1_str from string: " << from_isoTimeString(time1_str) << std::endl;
}

>Solution :

The use of the C time API can be very confusing for your use case because it involves conversions to and from your local time zone, which is a completely unnecessary complication.

Fwiw, I’ve rewritten your conversion functions in terms of my date.h header below:

uint64_t
from_isoTimeString(std::string iso_time_string)
{
   //--- takes an UTC ISO formatted time/date string and converts it to a timestamp value
   date::sys_seconds tmb;
   std::stringstream ss(iso_time_string);
   ss >> date::parse("%Y-%m-%dT%TZ", tmb);
   if (ss.fail())
   {
      std::string errmsg = "unable to convert Timestamp '" + iso_time_string + "' from ISO 8601 format";
      throw std::invalid_argument(errmsg);
   }
   return static_cast<uint64_t>(tmb.time_since_epoch().count());
}

std::string
formatTimestamp(uint64_t epoch_seconds, std::string timestamp_format)
{
   date::sys_seconds tt{std::chrono::seconds{epoch_seconds}};

   std::stringstream ss;
   ss << date::format(timestamp_format, tt);
   if (ss.fail())
   {
      std::string err_msg = "unable to convert Timestamp to " + timestamp_format + " format";
      throw std::invalid_argument(err_msg);
   }
   return ss.str();
}

I’ve changed as little as possible within each function. Now your local time zone is not part of the computation. And this code will easily port to C++20 (when available) by changing a few date:: to std::chrono:: or std::.

Your main() needs no changes at all, and the output is now:

            time1_str: 2022-06-27T12:00:10Z
 time1_uint as string: 2022-06-27T12:00:10Z
           time1_uint: 1656331210
time1_str from string: 1656331210

Leave a Reply