26 July 2014

How to convert string to DateTime and vice versa

Recently I was looking for a convenient way, to parse a string representation of DateTime to integral value representing epoch time. And I couldn't find an existing class to make that easily, for example giving the string as an input to constructor. After about a couple of hours of research I found a brief way to achieve that using a mix of C and C++ tools.

First of all, there is an std::tm struct defined in <ctime> header, which represents a calendar date and a time with all the relevant members, the date is reckoned from start of year 1900, thus, if we try to represent current year (2014) the year field of tm structure will be equal to 114.
Second, in the same <ctime> header there is an auxiliary function, mktime, which accepts a pointer to std::tm object and returns std::time_t. The latter is a typedef not defined by C++ standard specification. However, it is almost always an integral type, so we can store it in an std::int64_t to be on the safe side. std::time_t represents the number of seconds elapsed since epoch (Jan 1 1900, 00:00:00), the same as POSIX time.

Alright, now that we have found a good type to store date and time in, we need the last step, getting all this info from a string, and that is where the most difficult part comes. std::tm is a POD struct, so no parse methods for users. std::time_t is just a typedef of a built-in type, so again, not much use.
Fortunately, there is a great set of tools that help developers deal with streams, and one of them is i/o manipulators. The one that we are going to use here is std::get_time from <iomanip> header. If passed as an argument to input stream's operator>>, it formats the stream buffer with the provided format string and writes the resulting dateTime into an std::tm structure. And that solved my problem.

Enough text, let's look into the code:

#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>

// Converts UTC time string to a time_t value.
std::time_t getEpochTime(const std::wstring& dateTime)
{
       // Let's consider we are getting all the input in
       // this format: '2014-07-25T20:17:22Z' (T denotes
       // start of Time part, Z denotes UTC zone).
       // A better approach would be to pass in the format as well.
       static const std::wstring dateTimeFormat{ L"%Y-%m-%dT%H:%M:%SZ" };

       // Create a stream which we will use to parse the string,
       // which we provide to constructor of stream to fill the buffer.
       std::wistringstream ss{ dateTime };

       // Create a tm object to store the parsed date and time.
       std::tm dt;

       // Now we read from buffer using get_time manipulator
       // and formatting the input appropriately.
       ss >> std::get_time(&dt, dateTimeFormat.c_str());

       // Convert the tm structure to time_t value and return.
       return std::mktime(&dt);
}

int main(void)
{
       // Test to see if our function works properly.
       std::wstring dateTimeStr{ L"2014-07-25T20:17:22Z" };
       std::wcout << L"The epoch time value representing " << dateTimeStr
              << L" is " << getEpochTime(dateTimeStr) << std::endl;

       return 0;
}

If you want to verify that get_time correctly populated the tm object, just add a couple of outputs  after operator>> line, something like this:

       std::wcout << dt.tm_year << std::endl;
       std::wcout << dt.tm_mon << std::endl;
       std::wcout << dt.tm_mday << std::endl;
       std::wcout << dt.tm_hour << std::endl;
       std::wcout << dt.tm_min << std::endl;
       std::wcout << dt.tm_sec << std::endl;

To convert the epoch time value back to std::tm structure you can use either of std::localtime and std::gmtime functions from <ctime> header. And finally, if you want to convert std::tm to string, there is an analogous manipulator std::put_time which is used with output stream's operator<<.

Thanks for reading, I hope some people will use this approach when they encounter the same problem.