0

Assume we have an integer representing a date, in the form 20160120. I would like to convert this to days since epoch (1970-01-01) so that it corresponds to a POSIX-style date. So for example int 20160718 would become int 17000.

My attempt is below, where I first convert the int to a string, then to seconds since epoch, then to days since epoch. One problem with this is that I have to shift the date by 3 hours in order to get the correct date (see the std::to_string(d*100 + 3)) line. I suspect this has something to do with timezones? I am in UTC - 1. I am not sure how to deal with that.

So I wonder if there is a less complicated way of doing it, and if not– how I could fix the timezone issue.

#include <iostream>
#include <chrono>
#include <time.h>
#include <iomanip>
#include <sstream>
#include <string>

typedef std::chrono::duration<int, std::ratio<60 * 60 * 24>> days_type;
typedef std::chrono::system_clock sysclock;

int convert(const unsigned d){
    std::tm t = {};
    std::istringstream ss(std::to_string(d*100 + 3));   
    ss >> std::get_time(&t, "%Y%m%d%H");

    time_t t_ =  mktime(&t);

    /*
     Now convert seconds since epoch to days since epoch via chrono...
     */

    sysclock::time_point tp = sysclock::from_time_t(t_);

    std::chrono::time_point<sysclock, days_type> tp_day = 
    std::chrono::time_point_cast<days_type>(tp);

    return tp_day.time_since_epoch().count();
}


int main(){
    int d = 20160718;
    std::cout << d << ": " << convert(d) << std::endl; //17000
    return 0;
}
Slug Pue
  • 256
  • 1
  • 2
  • 15

3 Answers3

5

Using a date library:

#include "date.h"
#include <iostream>

int convert(unsigned d) {
  date::year_month_day ymd{date::year(d / 100 / 100),
                           date::month(d / 100 % 100), date::day(d % 100)};
  return date::sys_days{ymd}.time_since_epoch().count();
}

int main() {
  int d = 20160718;
  std::cout << d << ": " << convert(d) << '\n'; // 17000
}

See it run

Of course the convert() API would be improved by being more strongly typed, for example by directly returning a date::sys_days to represent the time point.

bames53
  • 86,085
  • 15
  • 179
  • 244
  • 3
    +1 The performance of this answer will run circles around the other answers. If you have an aversion to using this open source code you can get the algorithm it uses from here: http://howardhinnant.github.io/date_algorithms.html#days_from_civil and code it yourself. – Howard Hinnant Nov 05 '16 at 15:56
1

time_t may not be 0 at the start of the POSIX epoch on your system.

Here is a program that shows the different results:

#include "date.h" // uses this library: https://github.com/HowardHinnant/date
#include <iostream>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <string>

time_t convert_local(int d)
{
    std::tm t{};
    auto ts{std::to_string(d)};
    ts.insert(8, " ");
    ts.insert(6, " ");
    ts.insert(4, " "); // this is a workaround for a VS2015 bug: http://stackoverflow.com/q/21172767/1460794
    std::istringstream ss(ts);
    ss >> std::get_time(&t, "%Y %m %d");
    if(!ss) std::cout << "parse fail.\n";

    time_t t_{mktime(&t)};
    return t_;
}

time_t convert_UTC(int d)
{
    std::tm base_t{};
    base_t.tm_mday = 1;
    base_t.tm_mon = 0;
    base_t.tm_year = 70;
    base_t.tm_wday = 4;
    auto base_tt{std::mktime(&base_t)};

    std::tm t{};
    auto ts{std::to_string(d)};
    ts.insert(8, " ");
    ts.insert(6, " ");
    ts.insert(4, " "); // this is a workaround for a VS2015 bug: http://stackoverflow.com/q/21172767/1460794
    std::istringstream ss(ts);
    ss >> std::get_time(&t, "%Y %m %d");
    if(!ss) std::cout << "parse fail.\n";

    time_t t_{mktime(&t)};
    return t_ - base_tt;
}

typedef std::chrono::duration<int, std::ratio<60 * 60 * 24>> days_type;
typedef std::chrono::system_clock sysclock;

int days_since_epoch(time_t t)
{
    sysclock::time_point tp = sysclock::from_time_t(t);
    std::chrono::time_point<sysclock, days_type> tp_day =
        std::chrono::time_point_cast<days_type>(tp);
    return tp_day.time_since_epoch().count();
}

int main()
{
    std::tm t{};

    t.tm_mday = 1;
    t.tm_mon = 0;
    t.tm_year = 70;
    t.tm_wday = 4;

    auto tt{std::mktime(&t)};
    auto tp{std::chrono::system_clock::from_time_t(tt)};

    time_t t0{0};
    auto tp0{std::chrono::system_clock::from_time_t(t0)};

    using namespace date;
    std::cout << tp << " for Jan 1 1970 00:00 UTC in my timezone (-6)\n";
    std::cout << std::chrono::system_clock::now() << " is the time now in my timezone (-6)\n";
    std::cout << tp0 << " is what we get from time_t{0} in my timezone (-6)\n";

    int d = 20160718;
    std::cout << "\n\n";
    std::cout << d << " converted to time_t is " << convert_local(d) << " but it considers local timezone on my system.\n";
    std::cout << "Now, using this time_t, converted to days since epoch: " << days_since_epoch(convert_local(d));

    std::cout << "\n\n";
    std::cout << d << " converted to time_t, adjusted for local timezone is " << convert_UTC(d) << ".\n";
    std::cout << "Now, using this time_t, converted to days since epoch: " << days_since_epoch(convert_UTC(d));

    return 0;
}

On my system it produces:

1970-01-01 06:00:00.0000000 for Jan 1 1970 00:00 UTC in my timezone (-6)
2016-11-05 14:42:19.1299886 is the time now in my timezone (-6)
1970-01-01 00:00:00.0000000 is what we get from time_t{0} in my timezone (-6)


20160718 converted to time_t is 1468821600 but it considers local timezone on my system.
Now, using this time_t, converted to days since epoch: 17000

20160718 converted to time_t, adjusted for local timezone is 1468800000.
Now, using this time_t, converted to days since epoch: 17000

On another server (live demo) it produces:

1970-01-01 00:00:00.000000000 for Jan 1 1970 00:00 UTC in my timezone
2016-11-05 14:37:15.661541264 is the time now in my timezone
1970-01-01 00:00:00.000000000 is what we get from time_t{0} in my timezone


20160718 converted to time_t is 1468800000 but it considers local timezone on my system.
Now, using this time_t, converted to days since epoch: 17000

20160718 converted to time_t, adjusted for local timezone is 1468800000.
Now, using this time_t, converted to days since epoch: 17000

In the end, it would probably be best to get the two time points you would like to work with explicitly (by specifying the day, month and year) for each.

This way you could use system_clock and it would not matter because we're only looking to calculate a duration in days:

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

auto get_epoch_tp()
{
    std::tm base_t{};
    base_t.tm_mday = 1;
    base_t.tm_mon = 0;
    base_t.tm_year = 70;
    base_t.tm_wday = 4;
    auto base_tt{std::mktime(&base_t)};
    auto tp{std::chrono::system_clock::from_time_t(base_tt)};
    return tp;
}

int get_days_since_epoch(int d)
{
    std::tm t{};
    auto ts{std::to_string(d)};
    ts.insert(8, " ");
    ts.insert(6, " ");
    ts.insert(4, " "); // this is a workaround for a VS2015 bug: http://stackoverflow.com/q/21172767/1460794
    std::istringstream ss(ts);
    ss >> std::get_time(&t, "%Y %m %d");
    if(!ss) std::cout << "parse fail.\n";
    time_t tt{mktime(&t)};

    auto tp{std::chrono::system_clock::from_time_t(tt)};

    using days_type = std::chrono::duration<int, std::ratio<60 * 60 * 24>>;
    auto duration{tp - get_epoch_tp()};
    auto days{std::chrono::duration_cast<days_type>(duration)};
    return days.count();
}

int main()
{
    int d = 20160718;
    std::cout << d << ": " << get_days_since_epoch(d) << std::endl; //17000
}

demo

wally
  • 10,717
  • 5
  • 39
  • 72
0

You can do just simple math after converting to time.

#include <iostream>
#include <time.h>
#include <iomanip>
#include <sstream>

int convert(const unsigned d){
    std::tm t = {};
    std::istringstream ss(std::to_string(d));  
    ss >> std::get_time(&t, "%Y%m%d");

    time_t t_ = mktime(&t);
    return (int)t_/(60*60*24);
}


int main(){
    int d = 20160718;
    std::cout << d << ": " << convert(d) << std::endl; //17000
    return 0;
}
cpatricio
  • 457
  • 1
  • 5
  • 11
  • Well, to begin with this give me 16999, not 17000. But in general – what about timezones, leap years, leap seconds and so on? These things is why I didn't simply divide by number of seconds in a day. – Slug Pue Nov 05 '16 at 13:46
  • I dont think that this is bad idea. This approach is and easy way to have function that is bijective. Day should be seen as a unit of time that takes exactly 86400s. – Luka Rahne Nov 05 '16 at 15:03
  • @LukaRahne it would be nice if second-increments and dates were a bijective map, but that is not the case in the real world... – Slug Pue Nov 05 '16 at 15:09