2

I am trying to convert a string timestamp expressed in the following format: "28.08.2017 03:59:55.0007" to a std::chrono::system_clock::time_point by preserving the microseconds precision. Is there any way to achieve this by using the standard library or boost? Thanks.

Michal Turlik
  • 192
  • 2
  • 6

3 Answers3

4

I'd make use of Howard Hinnant's date library https://howardhinnant.github.io/date/date.html

e.g.:

std::stringstream str( "28.08.2017 03:59:55.0007" );
str.imbue( std::locale() );
std::chrono::time_point< std::chrono::system_clock, std::chrono::microseconds > result;
date::from_stream( str, "%d.%m.%Y %H:%M:%S", result );
std::cout << result.time_since_epoch().count();
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • 1
    Thanks Alan but I can not use any other 3rd party deps other than boost. – Michal Turlik Feb 16 '18 at 13:25
  • @MichalTurlik Why couldn't you? – Holt Feb 16 '18 at 13:26
  • @MichalTurlik: It's just a piece of code. You're writing a piece of code now. Can you not use your own code either? – Lightness Races in Orbit Feb 16 '18 at 13:36
  • see https://wandbox.org/permlink/fq1AUV71rZqPcNR6 for a working example – Alan Birtles Feb 16 '18 at 13:38
  • "doesn't work" in what way, doesn't compile? doesn't return the right result? did you install the date library? – Alan Birtles Feb 16 '18 at 13:42
  • Hej Alan, it is not my project so I am not the one that can make such a decision – Michal Turlik Feb 16 '18 at 13:47
  • @holt I initially posted the format string with %y rather than %Y which is for a 2 digit rather than a 4 digit year – Alan Birtles Feb 16 '18 at 13:56
  • @AlanBirtles This indeed fix the problem. Note that you should use `time_point` rather than directly `system_clock::time_point` here because the standard does not guarantee that `system_clock::time_point` will be able to store microseconds. – Holt Feb 16 '18 at 13:58
  • @MichalTurlik its only a single header and is likely to become part of the standard library at some point, its MIT licensed and written by one of the C++ gods so I'd try to persuade your bosses to allow you to use it, makes date time code simpler and more reliable – Alan Birtles Feb 16 '18 at 13:58
  • @MichalTurlik It's worth pointing out here that [the solution you said solved your problem](https://stackoverflow.com/a/48827784/2642059) uses a POSIX library, which will likely never be incorporated into the standard. HowardHinnant's library is proposed for inclusion in the C++ standard currently, so this answer will likely be standard code in the near future. In any case it is worth pointing out that this can be accomplished simply by depending on the chrono library: https://stackoverflow.com/a/48829610/2642059 – Jonathan Mee Feb 16 '18 at 15:28
  • @Holt Good point. In GNU C++ standard library `system_clock::time_point` is of nanosecond resolution, in clang `libc++` it is of microsecond resolution. – Maxim Egorushkin Feb 16 '18 at 15:44
  • 1
    Upvoted: Link to the current proposal: https://wg21.link/p0355 I wouldn't bother imbueing `str` with the current global locale unless you anticipate the decimal point character that starts the fractional second to be localized. You can also use `%T` in place of `%H:%M:%S`. The only difference is stylistic. The syntax `str >> date::parse(format, time_point)` also works. You can stick with parsing into `std::chrono::system_clock::time_point`. While not specified, the coarsest shipping `system_clock::time_point` has microseconds precision, sufficient for this problem statement. – Howard Hinnant Feb 16 '18 at 23:30
  • @JonathanMee I would not be so sure. `strptime` exists because it solves a real problem and is a part of POSIX standard. The fact that C or C++ do not require this function is an oversight. It would not be the first time oversight, for example, [you can output a `double` as `hexfloat` in C++ but you cannot read it back because of the broken standard](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81122). `std::chrono` library is pretty rudimentary requiring each user to implement I/O functions, which is poor usability. – Maxim Egorushkin Feb 17 '18 at 00:26
  • @JonathanMee Reminds me of people claiming that threads before C++11 and shared libraries are undefined behaviour. Technically true, but useless information. I got that, now how do I do threads and shared libraries please? – Maxim Egorushkin Feb 17 '18 at 00:35
  • @MaximEgorushkin I'm all for making simpler ways to do things. For example, reading in a `hexfloat` is possible it's just labor intensive. A better way to do this would be appreciated. `strptime`... not so much. [`get_time` and `time_get` both solve the problem admirably.](https://stackoverflow.com/a/48829610/2642059) If you think that's not true I'd appreciate a counter example. – Jonathan Mee Feb 17 '18 at 00:35
  • @JonathanMee Counter-example: [std::get_time fails to parse output from std::put_time, but strptime can parse them (c++11)](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84216). – Maxim Egorushkin Feb 17 '18 at 00:36
  • @MaximEgorushkin Pointing out a bug in gcc is not the same thing as pointing out a deficiency in the standard. `get_time` is an area where gcc has historically under performed as compared to Visual Studio, so this deficiency is hardly surprising. Again, there is no motivation to adopt `strptime` into the standard, there is simply a need for gcc to correctly implement `get_time`. – Jonathan Mee Feb 17 '18 at 01:04
  • @JonathanMee `strptime`/`strftime` are not-portable C++-wide, however, POSIX required these at least since IEEE Std 1003.1-2001. The C++ alternatives are not on par yet. – Maxim Egorushkin Feb 17 '18 at 01:11
  • @MaximEgorushkin That statement is very absolute, as though you are aware of an actual deficiency in `get_time` rather than simply a bug in gcc's implementation. Is that accurate? Would you care to share? – Jonathan Mee Feb 17 '18 at 01:20
  • @JonathanMee I do not think you are being practical. – Maxim Egorushkin Feb 17 '18 at 04:29
  • 3
    `std::get_time` was a great step forward in C++11. But its fatal flaw is that its interface parses into `std::tm` which is limited to seconds precision. It inherits this limitation from `strptime` which its specification depends on. Indirectly, `strptime` _is_ in C++11, it is just that its interface is spelled `get_time`. https://wg21.link/p0355 introduces time_point _and_ duration parsing functionality that can handle sub-second precision (without trafficking through the old C API). – Howard Hinnant Feb 17 '18 at 15:55
  • @AlanBirtles unfortunately despite your good critics in relation to the Howard Hinnant's date library I can not add it to our project...if it would be me to decide I then would not make any complain with that. Thanks – Michal Turlik Feb 17 '18 at 17:47
2

Thought I'd add an answer since there isn't one available that exclusively uses the standard.
Given the input: istringstream timestamp("28.08.2017 03:59:55.0007"), this could be converted to a tm via get_time, but for the fractional seconds. The fractional seconds would need to be converted manually (this could be done by constructing chrono::microseconds from the rounded remainder divided by the micro ratio.) All this could be combined into something like this:

tm tmb;
double r;

timestamp >> get_time(&tmb, "%d.%m.%Y %T") >> r;

const auto output = chrono::time_point_cast<chrono::microseconds>(chrono::system_clock::from_time_t(mktime(&tmb))) + chrono::microseconds(lround(r * micro::den));

Live Example

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
1

One implementation can be:

#include <ctime>
#include <cmath>
#include <chrono>
#include <string>
#include <cstdint>
#include <stdexcept>

std::chrono::system_clock::time_point parse_my_timestamp(std::string const& timestamp) {
    auto error = [&timestamp]() { throw std::invalid_argument("Invalid timestamp: "  + timestamp); };
    std::tm tm;
    auto fraction = ::strptime(timestamp.c_str(), "%d.%m.%Y %H:%M:%S", &tm);
    if(!fraction)
        error();
    std::chrono::nanoseconds ns(0);
    if('.' == *fraction) {
        ++fraction;
        char* fraction_end = 0;
        std::chrono::nanoseconds fraction_value(std::strtoul(fraction, &fraction_end, 10));
        if(fraction_end != timestamp.data() + timestamp.size())
            error();
        auto fraction_len = fraction_end - fraction;
        if(fraction_len > 9)
            error();
        ns = fraction_value * static_cast<std::int32_t>(std::pow(10, 9 - fraction_len));
    }
    else if(fraction != timestamp.data() + timestamp.size())
        error();
    auto seconds_since_epoch = std::mktime(&tm); // Assumes timestamp is in localtime. For UTC use timegm.
    auto timepoint_ns = std::chrono::system_clock::from_time_t(seconds_since_epoch) + ns;
    return std::chrono::time_point_cast<std::chrono::system_clock::duration>(timepoint_ns);
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • Note that `timegm` is not standard if you need UTC time. – Holt Feb 16 '18 at 13:25
  • @Holt True, but it is widely available. – Maxim Egorushkin Feb 16 '18 at 13:27
  • I don't see a `strptime` on http://en.cppreference.com are you certain this exists in the standard? – Jonathan Mee Feb 16 '18 at 13:35
  • 1
    @JonathanMee `strptime` is not standard but you can use the standard [`get_time`](http://en.cppreference.com/w/cpp/io/manip/get_time) with a `stringstream`. – Holt Feb 16 '18 at 13:37
  • @JonathanMee: I'm sure it doesn't. It comes from POSIX. Simply use Google please. http://man7.org/linux/man-pages/man3/strptime.3.html – Lightness Races in Orbit Feb 16 '18 at 13:37
  • 1
    @Holt [`std::get_time` has been broken in gcc](https://gcc.gnu.org/bugzilla/buglist.cgi?quicksearch=get_time). – Maxim Egorushkin Feb 16 '18 at 13:47
  • @MaximEgorushkin thank you so much! You saved my life ;) the snippet code works like a charm :) – Michal Turlik Feb 16 '18 at 14:12
  • @MichalTurlik I typically wait a day just to make sure everyone has time to submit answers, but just be aware best practice is to accept answers that solve your problem. If you're going to leverage this answer be sure to reward the answerer with an acceptance! – Jonathan Mee Feb 16 '18 at 15:15
  • @JonathanMee the solution provided by Maxim really does the trick...I would only add that the microseconds need to be multiplied for 100 as their precision is being expressed in cents (please see the string format: 28.08.2017 03:59:55.0007) apart that the solution suits so well...the HowardHinnant variant proposed probably would be the best one as it makes no use of any posix implementation but as I have just expressed I am not in the position to go with it. This https://stackoverflow.com/questions/321849/strptime-equivalent-on-windows allowed me to overcome the strptime posix one – Michal Turlik Feb 17 '18 at 17:59
  • @MichalTurlik Fixed the fraction handling now, handles up to 1-nanosecond fraction precision. – Maxim Egorushkin Feb 17 '18 at 18:56
  • @MaximEgorushkin , thanks a lot :) ...just as we are I want to thank ALL the good people that have given me their help and support by providing all the working variants. – Michal Turlik Feb 18 '18 at 21:26
  • @MichalTurlik I'm a little perplexed at why you could link in a open source library to use Posix code on Windows but not use HowardHinnant's header. But to each his own. Either way, as I point out in [my answer](https://stackoverflow.com/a/48829610/2642059) for such a simple problem you really don't have a need for Posix or HowardHinnant's library. C++ intrinsically provides you the tools you need for an elegant solution. – Jonathan Mee Feb 19 '18 at 03:22