1

Currently, I have A solution and another semi-solution. The semi-solution is linked here, Get seconds since epoch in Linux but that gives the time since epoch for the current datetime. I need to have it tell me the seconds since epoch for a specified date instead. I do have a function for this, however I can't find the original post for it,

std::tm t = {};
std::istringstream ss(dateBuf);
ss >> std::get_time(&t, "%Y-%m-%d");
double seconds = (double)(std::mktime(&t));

There are a few issues I have with it though. Currently, dateBuf = "2020-01-01"; and I require it to be able to handle not just days, but hours, minutes, seconds... Also, I am hoping there is another way of doing this without allocating memory every time to make a new std::tm and std::istringstream and then pushing ss into it. I am wondering if there is a way to go straight from char datebuf[] to a double/long double with the seconds as the value. For all intensive purposes, the program will go over this statement many, many times to convert the dates to seconds and this just seems inefficient. Something such as

long double SecondsSinceEpoch(const char* date){
    //Taking in a date such as "2020-01-01 00:00:00" (Local Time)
    //Will return the value 1577836800 (GMT) and Local time of 1577797200

    //And also be able to take in a simpler date such as "2020-01-01"
}
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 7
    What version of C++ are you using? If you can use C++20, the new [date-time facilities](https://en.cppreference.com/w/cpp/chrono) are great. If not, [Howard Hinnant](https://stackoverflow.com/users/576911/howard-hinnant) has a [most excellent library](https://github.com/HowardHinnant/date) that the C++20 version is based off of. – NathanOliver Apr 07 '21 at 20:09
  • 1
    @NathanOliver possibly a hand in both, some applications I have might have C++ 20 and some might not be. Could you please give an example that would work from my post? –  Apr 07 '21 at 20:11
  • If I'm reading you correctly, you should be able to extend the format string you're passing to `std::get_time` to also read in hours, minutes, and seconds.[Documentation](https://en.cppreference.com/w/cpp/io/manip/get_time) – user4581301 Apr 07 '21 at 20:16
  • Yes, I can but it requires `std::tm` and `std::istringstream` which I want to avoid allocations all together and just convert from a plain `char datebuf[]` –  Apr 07 '21 at 20:17
  • It still needs a `std::stringstream`, but you can see Howard's/C++20's way here: https://stackoverflow.com/a/41613816/4342498 – NathanOliver Apr 07 '21 at 20:20
  • In that case, go old school. Pull the array apart with `scanf` and store the results into a `tm`'s members. – user4581301 Apr 07 '21 at 20:21
  • How can I achieve that? Also @NathanOliver i'll check out this library, seems quite promising. –  Apr 07 '21 at 20:23
  • Suggestion: If you go the non-Hinnant route, profile what you get against Hinnant's functions and see how they compare. – user4581301 Apr 07 '21 at 20:25
  • That's a good idea. I don't know how to do either atm, `scanf` into `tm`, i've not ever really used `scanf` or `tm` or storing in something in it? –  Apr 07 '21 at 20:26
  • 2
    For an old-school `scanf`-based answer, I made one here: https://stackoverflow.com/a/66795589/7582247 - I wouldn't worry about allocating the `std::tm` unless you do this many-many times each second though. – Ted Lyngmo Apr 07 '21 at 20:27
  • @NathanOliver since its `istringstream` for Howards way, maybe theres another possibility to speed it up? Currently, I read in input from a low level api of OleDB where I get data as a primitive time, `SDL_C_CHAR` and then load that into my `dateBuf`, but rather than that maybe I can load it into an `istringstream` –  Apr 07 '21 at 20:28
  • @TedLyngmo I probably will do 7+ million times a second lol –  Apr 07 '21 at 20:28
  • @prtwrt Ok, that's unfortunate. What is the source of all these dates? Perhaps itt'll be possible to change it at the soruce instead? – Ted Lyngmo Apr 07 '21 at 20:29
  • An access database –  Apr 07 '21 at 20:30
  • @prtwrt Hmm, I don't know Access, but isn't it possible to get the time point in another format than as strings that needs to be parsed? In the databases I've worked with you could make views or `SELECT` with transformations. That ought to be _a lot_ more effective than getting dates out as strings only to parse them again. Fix it at the source if you can. – Ted Lyngmo Apr 07 '21 at 20:34
  • 1
    That's a good idea, i'll try that. For now, your example with `sscanf_s` works nicely –  Apr 07 '21 at 20:38
  • 1
    Only thing I'd change in what Ted has is I'd pass in `const char *` to save converting the array to a `std::string` and then back again with `c_str()` – user4581301 Apr 07 '21 at 20:38
  • @user4581301 Excellent idea - or perhaps a `string_view` as a middle option. Also, for speed, instead of `throw`ing, `return (std::time_t)-1;` (`(time_t) -1` is also returned by `mktime` on error) - and use the `to_time_t` version of the function to get the `time_t` directly. – Ted Lyngmo Apr 07 '21 at 20:39
  • 1
    `string view` should optimize out for the asker's use-case, so it probably wouldn't be harmful. – user4581301 Apr 07 '21 at 20:43
  • 1
    @prtwrt I updated the `to_time_t` version in that answer. – Ted Lyngmo Apr 07 '21 at 21:14
  • Does this answer your question? [How to convert a time into epoch time?](https://stackoverflow.com/questions/11979655/how-to-convert-a-time-into-epoch-time) – June7 Apr 07 '21 at 23:09
  • 1
    Who's local time? The computer's currently set local time zone, or somebody else's? The reason I ask is because you seem very concerned about performance (no allocations), but discovering your time zone is going to be expensive. Do you have the option to append your local date time in the string with a UTC offset: `+0300` or `+03:00`? If so, things can get wicked fast. – Howard Hinnant Apr 08 '21 at 00:13
  • 1
    Oh, also, since you want to represent the seconds in a `long double`, are you wanting to handle fractions of a second in the input string? If so, localized decimal point or is the "C" '.' fine? – Howard Hinnant Apr 08 '21 at 00:17
  • 1
    BTW, if "2020-01-01 00:00:00" is the local time, the UTC seconds is 1577797200, the local seconds is 1577836800 and the UTC offset is +11:00. If instead the UTC offset is -11:00 and the local seconds is 1577797200 and the UTC seconds is 1577836800, then the local date/time is 2019-12-31 13:00:00. – Howard Hinnant Apr 08 '21 at 00:37
  • @HowardHinnant I do have the option to do this, but i'd have to allocate a string and append to it. The time I got in my program compared to the epoch time converter's "local time" matched, thats all it was compared to. My solution was in the end to create another field in the database that stores the seconds and just access it from there. For the Original Post, your library absolutely does answer the question. –  Apr 08 '21 at 01:56
  • I guess the real question is: can you fake your timezone? Or does it have to be looked up because the computer is mobile? Or the program might have to run on different computers in different timezones? Is your UTC offset knowable at coding time time? If you have to allocate a string and append to it, you're cutting your optimization opportunities. That being said, are you going for ultimate performance? Or a convenient solution? My library can get you a convenient solution quickly. But for the ultimate performance you're going to have to do your own parsing, and your own UTC offset. – Howard Hinnant Apr 08 '21 at 02:29
  • 99.9999% of the time, the time zone is going to be the same. I guess I am going for "ultimate performance" since its about 7million+ values every time I query the database, so converting 7 million times vs reading instead would be much more efficient. –  Apr 08 '21 at 02:35
  • Ok, what time zone? What do you want to do when the time zone isn't the same? And what, if any, error checking in the parsing do you want? A general purpose library is expensive because it can't make assumptions about any of these questions. I.e.: could be any time zone, the input could be malicious, all failures set the failbit in ios_base, which may throw an exception. – Howard Hinnant Apr 08 '21 at 02:43

1 Answers1

2

Here's the general purpose solution using the preview C++20 chrono library:

#include "date/tz.h"
#include <iostream>
#include <sstream>

long double
SecondsSinceEpoch(const char* date)
{
    //Taking in a date such as "2020-01-01 00:00:00" (Local Time)
    //And also be able to take in a simpler date such as "2020-01-01"
    //returns -1 on error
    using namespace date;
    using namespace std;
    using namespace std::chrono;

    istringstream in{date};
    local_days tpd;
    in >> parse("%F", tpd);
    if (in.fail())
        return -1;
    seconds s{};
    in >> parse(" %T", s);
    zoned_time zt{current_zone(), tpd + s};
    return zt.get_sys_time().time_since_epoch().count();
}

The expensive parts are calling current_zone() and parsing out of an istringstream (which may create a long string on the heap, depending on the implementation and the input).

The above can be made faster if we can substitute in a constant UTC offset for the time zone, and if we create customized parsing logic that doesn't have to do much error checking.

What this library excels at is taking the {y, m, d, h, M, s} integral values and turning them into a count of seconds. So if you can obtain those integral values more efficiently than strptime (or it's equivalent in this library), including applying the UTC offset to the local time, then this library can translate those 6 fields into a count of seconds with very few cpu clock cycles.

Update

One thing you can do if your time zone is constant is to just look it up once:

static auto tz = current_zone();
zoned_time zt{tz, tpd + s};

or:

static auto tz = locate_zone("Australia/Sydney");
zoned_time zt{tz, tpd + s};

Both of these options are roughly equivalent to each other in terms of performance (assuming your current time zone is always "Australia/Sydney".

If you do your own scanning into integral types, then turning those ints into dates looks more like:

// Return {y, m, d, h, M, s}
std::array<int, 6>
scan(const char* date);

long double
SecondsSinceEpoch(const char* date)
{
    //Taking in a date such as "2020-01-01 00:00:00" (Local Time)
    //And also be able to take in a simpler date such as "2020-01-01"
    //returns -1 on error
    using namespace date;
    using namespace std::chrono;

    auto a = scan(date);  // where you write scan
    auto tp = local_days{year{a[0]}/a[1]/a[2]} + hours{a[3]}
              + minutes{a[4]} + seconds{a[5]};
    static auto tz = locate_zone("Australia/Sydney");
    zoned_time zt{tz, tp};
    return zt.get_sys_time().time_since_epoch().count();
}

Use of the timezone lib does require some installation.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Time zone is in sydney, AEST I think is the name of the time zone. So if it is known, replacing `current_zone()` with `"AEST"` would be much more efficient / ideal? –  Apr 08 '21 at 04:07