I'm currently trying to parse some info about the start time of an experiment as listed in a log file. After reading in the file important info, e.g column titles, start time, time between measurements, is parsed using <regex>
.
I'm trying to use the std::chrono::from_stream(...)
function to parse a string with the format "DD/MM/YYYY at hh:mm:ss" into a std::chrono::time_point
, example of a string:
08/03/2021 at 09:37:25
At the moment I'm attempting this using the following function which attempts to construct a duration from a provided string to parse & a string to parse it with, then converting that to a time_point so I have control over the clock used:
#include <chrono>
#include <string>
#include <sstream>
#include <iostream>
using nano = std::chrono::duration<std::uint64_t, std::nano>;
template <typename Duration>
Duration TimeFormat(const std::string& str,
const std::string& fmt,
const Duration& default_val)
{
Duration dur;
std::stringstream ss{ str };
std::chrono::from_stream(ss, fmt.c_str(), dur);
/*
from_stream sets the failbit of the input stream if it fails to parse
any part of the input format string or if it receives any contradictory
information.
*/
if (ss.good())
{
std::cout << "Successful parse!" << std::endl;
std::cout << dur.count() << std::endl;
return dur;
}
else
{
std::cout << "Failed parse!" << std::endl;
std::cout << dur.count() << std::endl;
return default_val;
}
}
int main()
{
/*
The file is already read in, and regex matches the correct line from the log file and a
format pattern from a related config file.
*/
/*
Two different lines in the log file give:
- str1 = test start time.
- str2 = time between each measurement.
*/
std::string str1("08/03/2021 at 09:37:25"), str2("00:00:05");
std::string fmt1("%d/%m/%Y at %H:%M:%S"), fmt2("%H:%M:%S");
auto test1 = TimeFormat<nano>(str1, fmt1, nano::zero());
/*
--> "Failed parse!" & test1.count() = 14757395258967641292
A little research indicates that this is what VS initializes variables to
in debug mode. If run in release mode test1.count() = 0 in my tests.
*/
auto test2 = TimeFormat<nano>(str2, fmt2, nano::zero());
/*
--> "Failed parse!" & test2.count() = 5000000000 (5 billion nanoseconds)
Chose nanoseconds because it also has to handle windows file times which are measured
relative to 01/01/1601 in hundreds of nanoseconds. Might be worth pointing out.
What's weird is that it fails even though the value it reads is correct.
*/
/*
... Convert to a time_point after this,
e.g auto t1 = std::chrono::time_point<std::chrono::high_resolution_clock, nano>(test1);
*/
}
The MS documentation for from_stream can be found here. With details about different format characters just after the from_stream docs.
ss.is_good()
?
Is that a type-o in your question or an extension in the Visual Studio std::lib?
I'm going to guess it is a type-o and that you meant ss.good()
...
The good()
member function checks if all state flags are off:
failbit
badbit
eofbit
eofbit
in particular often does not mean "error". It simply means that the parsing reached the end of the stream. You are interpreting "end of stream" as a parsing error.
Instead check failbit
or badbit
. This is most easily done with the fail()
member function.
if (!ss.fail())
...
Any idea why it still won't pass the first string though?
I'm not 100% positive if it is a bug in the VS implementation, or a bug in the C++ spec, or a bug in neither. Either way, it wouldn't return what you're expecting.
For me (using the C++20 chrono preview library), the first parse is successful and returns
34645000000000
which if printed out in hh:mm:ss.fffffffff format is:
09:37:25.000000000
That is, only the time-part is contributing to the return value. This is clearly not what you intended. Your first test appears to intend to parse a time_point
, not a duration
.
Here is a slightly rewritten program that I think will do what you want, parsing a time_point
in the first test, and a duration
in the second:
#include <chrono>
#include <string>
#include <sstream>
#include <iostream>
using nano = std::chrono::duration<std::uint64_t, std::nano>;
template <typename TimeType>
TimeType TimeFormat(const std::string& str,
const std::string& fmt,
const TimeType& default_val)
{
TimeType dur;
std::stringstream ss{ str };
std::chrono::from_stream(ss, fmt.c_str(), dur);
/*
from_stream sets the failbit of the input stream if it fails to parse
any part of the input format string or if it receives any contradictory
information.
*/
if (!ss.fail())
{
std::cout << "Successful parse!" << std::endl;
std::cout << dur << std::endl;
return dur;
}
else
{
std::cout << "Failed parse!" << std::endl;
std::cout << dur << std::endl;
return default_val;
}
}
int main()
{
std::string str1("08/03/2021 at 09:37:25"), str2("00:00:05");
std::string fmt1("%d/%m/%Y at %H:%M:%S"), fmt2("%H:%M:%S");
auto test1 = TimeFormat(str1, fmt1, std::chrono::sys_time<nano>{});
auto test2 = TimeFormat(str2, fmt2, nano::zero());
}
For me this outputs:
Successful parse!
2021-03-08 09:37:25.000000000
Successful parse!
5000000000ns
If one wanted the output of the first test in terms of nanoseconds since epoch, then one could extract that from the dur
time_point
variable with dur.time_since_epoch()
. This would then output:
1615196245000000000ns