C++ Converting a time string to seconds from the epoch

33,163

Solution 1

Using C++11 functionality we can now use streams to parse times:

The iomanip std::get_time will convert a string based on a set of format parameters and convert them into a struct tz object.

You can then use std::mktime() to convert this into an epoch value.

#include <iostream>
#include <sstream>
#include <locale>
#include <iomanip>

int main()
{
    std::tm t = {};
    std::istringstream ss("2010-11-04T23:23:01Z");

    if (ss >> std::get_time(&t, "%Y-%m-%dT%H:%M:%S"))
    {
        std::cout << std::put_time(&t, "%c") << "\n"
                  << std::mktime(&t) << "\n";
    }
    else
    {
        std::cout << "Parse failed\n";
    }
    return 0;
}

Solution 2

This is ISO8601 format. You can use strptime function to parse it with %FT%T%z argument. It is not a part of the C++ Standard though you can use open source implementation of it (this, for instance).

Solution 3

You can use a function such as strptime to convert a string to a struct tm, instead of parsing it manually.

Solution 4

Problem here is that mktime uses local time not UTC time.

Linux provides timegm which is what you want (i.e. mktime for UTC time).

Here is my solution, which I forced to only accept "Zulu" (Z timezone). Note that strptime doesn't actually seem to parse the time zone correctly, even though glib seems to have some support for that. That is why I just throw an exception if the string doesn't end in 'Z'.

static double EpochTime(const std::string& iso8601Time)
{
    struct tm t;
    if (iso8601Time.back() != 'Z') throw PBException("Non Zulu 8601 timezone not supported");
    char* ptr = strptime(iso8601Time.c_str(), "%FT%T", &t);
    if( ptr == nullptr)
    {
        throw PBException("strptime failed, can't parse " + iso8601Time);
    }
    double t2 = timegm(&t); // UTC
    if (*ptr)
    {
        double fraction = atof(ptr);
        t2 += fraction;
    }
    return t2;
}

Solution 5

It's not an exact dup but you will find @Cubbi's answer from here useful, I wager. This specifically assumes UTC input.

Boost also support direct conversion from ISO 8601 via boost::posix_time::from_iso_string which calls boost::date_time::parse_iso_time, here again you would just strip the trailing 'Z' and treat the TZ as implicit UTC.

#include <iostream>
#include <boost/date_time.hpp>

namespace bt = boost::posix_time;

const std::locale formats[] = {
std::locale(std::locale::classic(),new bt::time_input_facet("%Y-%m-%d %H:%M:%S")),
std::locale(std::locale::classic(),new bt::time_input_facet("%Y/%m/%d %H:%M:%S")),
std::locale(std::locale::classic(),new bt::time_input_facet("%d.%m.%Y %H:%M:%S")),
std::locale(std::locale::classic(),new bt::time_input_facet("%Y-%m-%d"))};
const size_t formats_n = sizeof(formats)/sizeof(formats[0]);

std::time_t pt_to_time_t(const bt::ptime& pt)
{
    bt::ptime timet_start(boost::gregorian::date(1970,1,1));
    bt::time_duration diff = pt - timet_start;
    return diff.ticks()/bt::time_duration::rep_type::ticks_per_second;

}
void seconds_from_epoch(const std::string& s)
{
    bt::ptime pt;
    for(size_t i=0; i<formats_n; ++i)
    {
        std::istringstream is(s);
        is.imbue(formats[i]);
        is >> pt;
        if(pt != bt::ptime()) break;
    }
    std::cout << " ptime is " << pt << '\n';
    std::cout << " seconds from epoch are " << pt_to_time_t(pt) << '\n';
}
int main()
{
    seconds_from_epoch("2004-03-21 12:45:33");
    seconds_from_epoch("2004/03/21 12:45:33");
    seconds_from_epoch("23.09.2004 04:12:21");
    seconds_from_epoch("2003-02-11");
}
Share:
33,163
Martin York
Author by

Martin York

Updated on January 21, 2020

Comments

  • Martin York
    Martin York over 4 years

    I have a string with the following format:

    2010-11-04T23:23:01Z

    The Z indicates that the time is UTC.
    I would rather store this as a epoch time to make comparison easy.

    What is the recomended method for doing this?

    Currently (after a quck search) the simplist algorithm is:

    1: <Convert string to struct_tm: by manually parsing string>
    2: Use mktime() to convert struct_tm to epoch time.
    
    // Problem here is that mktime uses local time not UTC time.
    
  • caf
    caf over 13 years
    That "Seconds east of UTC" value is clearly bogus, since the value shown is in excess of 4 million years. Timezones should be within a range of +/- 50000 seconds of UTC.
  • Dirk Eddelbuettel
    Dirk Eddelbuettel over 13 years
    Darn. You're right. And even when I define _BSD_SOURCE (which the mktime / ctime manual page had mentions) it still comes out as too large. I'm missing something else.
  • Zabuzard
    Zabuzard almost 5 years
    This code does not address OPs issue (I do not know why he accepted it). std::mktime assumes local timezone and outputs the wrong time. It gives 1288909381 on my machine (GMT+01:00 at that date), which is 10 pm, not 11 pm at UTC. The correct timestamp however would have been 1288912981.
  • Zabuzard
    Zabuzard almost 5 years
    That only gives you the offset for today, not for the specific date in question. (Offsets can change, and there is also DST).
  • Martin York
    Martin York almost 5 years
    @Zabuza Would you not think the OP knows what he wants better than you? If you don't know why he accepted it then maybe you should clarify that first otherwise your assertion This code does not address OPs issue is suspect.