Support parsing of timestamp strings to C++11 time_points.
This commit is contained in:
parent
908d38ebef
commit
96f9a6747d
@ -7,6 +7,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
@ -281,6 +282,25 @@ struct convert<Binary> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// std::chrono::time_point<std::chrono::system_clock>
|
||||||
|
// Notes:
|
||||||
|
// - Formats supported in spec at http://http://yaml.org/type/timestamp.html
|
||||||
|
// canonical: 2001-12-15T02:59:43.1Z
|
||||||
|
// iso8601: 2001-12-14t21:59:43.10-05:00
|
||||||
|
// spaced: 2001-12-14 21:59:43.10 -5
|
||||||
|
// date: 2002-12-14
|
||||||
|
// - Some platforms cannot handle time_points before January 1st, 1970 00:00Z
|
||||||
|
template <>
|
||||||
|
struct convert<std::chrono::time_point<std::chrono::system_clock>> {
|
||||||
|
|
||||||
|
YAML_CPP_API static Node encode(
|
||||||
|
const std::chrono::time_point<std::chrono::system_clock>& rhs);
|
||||||
|
|
||||||
|
YAML_CPP_API static bool decode(const Node& node,
|
||||||
|
std::chrono::time_point<std::chrono::system_clock>& rhs);
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // NODE_CONVERT_H_62B23520_7C8E_11DE_8A39_0800200C9A66
|
#endif // NODE_CONVERT_H_62B23520_7C8E_11DE_8A39_0800200C9A66
|
||||||
|
|||||||
159
src/convert.cpp
159
src/convert.cpp
@ -1,5 +1,10 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <ctime>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "yaml-cpp/exceptions.h"
|
||||||
#include "yaml-cpp/node/convert.h"
|
#include "yaml-cpp/node/convert.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -73,3 +78,157 @@ bool convert<bool>::decode(const Node& node, bool& rhs) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace YAML {
|
||||||
|
Node convert<std::chrono::time_point<std::chrono::system_clock>>::encode(
|
||||||
|
const std::chrono::time_point<std::chrono::system_clock>& rhs
|
||||||
|
) {
|
||||||
|
// Constants
|
||||||
|
const uint16_t MS_PER_S = 1000;
|
||||||
|
const uint16_t TM_BASE_YEAR = 1900;
|
||||||
|
|
||||||
|
std::time_t tt = std::chrono::system_clock::to_time_t(rhs);
|
||||||
|
std::tm utc_tm = *std::gmtime(&tt);
|
||||||
|
|
||||||
|
using namespace std::chrono;
|
||||||
|
uint16_t ms = duration_cast<milliseconds>(rhs.time_since_epoch())
|
||||||
|
.count() % MS_PER_S;
|
||||||
|
|
||||||
|
std::stringstream canonical;
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
canonical << setw(4) << setfill('0') << TM_BASE_YEAR + utc_tm.tm_year << "-"
|
||||||
|
<< setw(2) << setfill('0') << utc_tm.tm_mon + 1 << "-"
|
||||||
|
<< setw(2) << setfill('0') << utc_tm.tm_mday << "T"
|
||||||
|
<< setw(2) << setfill('0') << utc_tm.tm_hour << ":"
|
||||||
|
<< setw(2) << setfill('0') << utc_tm.tm_min << ":"
|
||||||
|
<< std::setw(2) << std::setfill('0') << utc_tm.tm_sec << "."
|
||||||
|
<< ms / 100 << "Z";
|
||||||
|
}
|
||||||
|
return Node(canonical.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool convert<std::chrono::time_point<std::chrono::system_clock>>::decode(
|
||||||
|
const Node& node,
|
||||||
|
std::chrono::time_point<std::chrono::system_clock>& rhs
|
||||||
|
) {
|
||||||
|
|
||||||
|
if (!node.IsScalar()) { return false; }
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const uint16_t TM_BASE_YEAR = 1900;
|
||||||
|
const uint16_t MIN_PER_HR = 60;
|
||||||
|
const uint16_t MS_PER_S = 1000;
|
||||||
|
const uint16_t S_PER_MIN = 60;
|
||||||
|
const uint16_t S_PER_HR = 3600;
|
||||||
|
const uint32_t S_PER_DAY = 86400;
|
||||||
|
|
||||||
|
const std::string s = node.Scalar();
|
||||||
|
|
||||||
|
// Working variables.
|
||||||
|
std::tm l_tm;
|
||||||
|
std::time_t l_time_t;
|
||||||
|
uint16_t year, month, day, hour = 0, minute = 0;
|
||||||
|
double second = 0.0f, zone = 0.0f;
|
||||||
|
|
||||||
|
// Regex patterns for date, time, and timezone.
|
||||||
|
std::regex r_yr("[0-9][0-9][0-9][0-9]-[0-9][0-9]?-[0-9][0-9]?");
|
||||||
|
std::regex r_tm("[0-9][0-9]?:[0-9][0-9]:[0-9][0-9].[0-9][0-9]?");
|
||||||
|
std::regex r_zn("[Z]|[+|-][0-9][0-9]:[0-9][0-9]| [+|-][0-9]");
|
||||||
|
std::smatch m;
|
||||||
|
|
||||||
|
// Parse the date string.
|
||||||
|
if(std::regex_search(s.begin(), s.end(), m, r_yr)) {
|
||||||
|
std::stringstream ss(m[0]);
|
||||||
|
std::string item;
|
||||||
|
std::vector<std::string> elems;
|
||||||
|
while (std::getline(ss, item, '-')) {
|
||||||
|
elems.push_back(item);
|
||||||
|
}
|
||||||
|
year = std::stoi(elems[0]); // Year
|
||||||
|
month = std::stoi(elems[1]); // Month
|
||||||
|
day = std::stoi(elems[2]); // Day
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// No date found in the string.
|
||||||
|
throw YAML::TypedBadConversion<
|
||||||
|
std::chrono::time_point<std::chrono::system_clock>>(node.Mark());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the time string.
|
||||||
|
if(std::regex_search(s.begin(), s.end(), m, r_tm)) {
|
||||||
|
std::stringstream ss(m[0]);
|
||||||
|
std::string item;
|
||||||
|
std::vector<std::string> elems;
|
||||||
|
while (std::getline(ss, item, ':')) {
|
||||||
|
elems.push_back(item);
|
||||||
|
}
|
||||||
|
hour = std::stoi(elems[0]); // Hours
|
||||||
|
minute = std::stoi(elems[1]); // Minutes
|
||||||
|
second = std::stod(elems[2]); // Seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the timezone string.
|
||||||
|
if(std::regex_search(s.begin(), s.end(), m, r_zn)) {
|
||||||
|
std::string z = m[0];
|
||||||
|
// Declared Zulu.
|
||||||
|
if(z.find('Z') != std::string::npos) {
|
||||||
|
zone = 0.0f;
|
||||||
|
} // Potentially non-whole hour.
|
||||||
|
else if (z.find(':') != std::string::npos) {
|
||||||
|
std::stringstream ss(m[0]);
|
||||||
|
std::string item;
|
||||||
|
std::vector<std::string> elems;
|
||||||
|
while (std::getline(ss, item, ':')) {
|
||||||
|
elems.push_back(item);
|
||||||
|
}
|
||||||
|
zone = stod(elems[0]); // Apply whole hours.
|
||||||
|
zone > 0 // Apply partial hours.
|
||||||
|
? zone += stod(elems[1]) / MIN_PER_HR
|
||||||
|
: zone -= stod(elems[1]) / MIN_PER_HR;
|
||||||
|
} // Assumed Zulu.
|
||||||
|
else {
|
||||||
|
zone = stod(z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the date stored avoiding mktime's problematic local-time return.
|
||||||
|
l_tm.tm_mday = day;
|
||||||
|
l_tm.tm_mon = month - 1;
|
||||||
|
l_tm.tm_year = year - TM_BASE_YEAR;
|
||||||
|
l_tm.tm_hour = 12; // Noon
|
||||||
|
l_tm.tm_min = 0;
|
||||||
|
l_tm.tm_sec = 0;
|
||||||
|
l_time_t = std::mktime(&l_tm);
|
||||||
|
if(l_time_t == -1) {
|
||||||
|
// Invalid std::time_t value parsed from the std::tm;
|
||||||
|
throw YAML::TypedBadConversion<
|
||||||
|
std::chrono::time_point<std::chrono::system_clock>>(node.Mark());
|
||||||
|
}
|
||||||
|
l_time_t -= static_cast<time_t>(l_time_t % S_PER_DAY);
|
||||||
|
|
||||||
|
// Add time of day in seconds having dodged mktime's annoyances.
|
||||||
|
l_time_t += static_cast<time_t>(
|
||||||
|
// Hours Minutes Seconds
|
||||||
|
(hour * S_PER_HR) + (minute * S_PER_MIN) + second
|
||||||
|
);
|
||||||
|
|
||||||
|
// Adjust for the time zone;
|
||||||
|
l_time_t -= static_cast<time_t>(zone * S_PER_HR);
|
||||||
|
|
||||||
|
// Create time_point.
|
||||||
|
using namespace std::chrono;
|
||||||
|
time_point<system_clock> l_time_point = system_clock::from_time_t(l_time_t);
|
||||||
|
|
||||||
|
// Add milliseconds.
|
||||||
|
uint32_t ms = static_cast<uint32_t>(second * MS_PER_S) % MS_PER_S;
|
||||||
|
std::chrono::milliseconds dur_ms{ms};
|
||||||
|
l_time_point += duration_cast<system_clock::duration>(dur_ms);
|
||||||
|
|
||||||
|
// Set rhs.
|
||||||
|
rhs = l_time_point;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@ -239,6 +239,78 @@ TEST(NodeSpecTest, Ex2_18_MultiLineFlowScalars) {
|
|||||||
|
|
||||||
// TODO: 2.19 - 2.22 schema tags
|
// TODO: 2.19 - 2.22 schema tags
|
||||||
|
|
||||||
|
TEST(NodeSpecTest, Ex2_22_Timestamps_untagged) {
|
||||||
|
|
||||||
|
// Invalid time_t
|
||||||
|
Node tt("1234-99-99");
|
||||||
|
|
||||||
|
// Un-parsable Scalar
|
||||||
|
Node us("asdf");
|
||||||
|
|
||||||
|
ASSERT_THROW(tt.as<std::chrono::system_clock::time_point>(),
|
||||||
|
YAML::TypedBadConversion<std::chrono::time_point
|
||||||
|
<std::chrono::system_clock>>);
|
||||||
|
ASSERT_THROW(us.as<std::chrono::system_clock::time_point>(),
|
||||||
|
YAML::TypedBadConversion<std::chrono::time_point
|
||||||
|
<std::chrono::system_clock>>);
|
||||||
|
|
||||||
|
// Valid timestamps
|
||||||
|
std::vector<std::tuple<std::string, std::string>> timestamps;
|
||||||
|
// Spec Example Tests
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-15T02:59:43.1Z",
|
||||||
|
"2001-12-15T02:59:43.1Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-14t21:59:43.10-06:00",
|
||||||
|
"2001-12-15T03:59:43.1Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-14 21:59:43.10 -5",
|
||||||
|
"2001-12-15T02:59:43.1Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-15 2:59:43.10",
|
||||||
|
"2001-12-15T02:59:43.1Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2002-12-14",
|
||||||
|
"2002-12-14T00:00:00.0Z"));
|
||||||
|
// Variable Digit Count Tests
|
||||||
|
timestamps.push_back(std::make_tuple("2000-12-3",
|
||||||
|
"2000-12-03T00:00:00.0Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2000-1-23",
|
||||||
|
"2000-01-23T00:00:00.0Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2000-1-2",
|
||||||
|
"2000-01-02T00:00:00.0Z"));
|
||||||
|
// Canonical Tests
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-15T02:59:43.1Z",
|
||||||
|
"2001-12-15T02:59:43.1Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-15T2:09:43.1Z",
|
||||||
|
"2001-12-15T02:09:43.1Z"));
|
||||||
|
// ISO8601 Subset Tests
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-14t21:59:43.10-05:00",
|
||||||
|
"2001-12-15T02:59:43.1Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-14t1:59:43.10-05:30",
|
||||||
|
"2001-12-14T07:29:43.1Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-14t1:59:43.10+05:30",
|
||||||
|
"2001-12-13T20:29:43.1Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-14t1:59:43.10+05:00",
|
||||||
|
"2001-12-13T20:59:43.1Z"));
|
||||||
|
// Spaced Date/Time/Zone Tests
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-14 1:59:43.10 -5",
|
||||||
|
"2001-12-14T06:59:43.1Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-14 21:59:43.10 +5",
|
||||||
|
"2001-12-14T16:59:43.1Z"));
|
||||||
|
// No Timezone Test
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-14 1:59:43.1",
|
||||||
|
"2001-12-14T01:59:43.1Z"));
|
||||||
|
timestamps.push_back(std::make_tuple("2001-12-14 21:59:43.12",
|
||||||
|
"2001-12-14T21:59:43.1Z"));
|
||||||
|
|
||||||
|
Node s_node, tp_node;
|
||||||
|
for(auto t : timestamps) {
|
||||||
|
s_node = std::get<0>(t);
|
||||||
|
tp_node = s_node.as<std::chrono::system_clock::time_point>();
|
||||||
|
|
||||||
|
EXPECT_TRUE(s_node.IsScalar());
|
||||||
|
EXPECT_TRUE(tp_node.IsScalar());
|
||||||
|
|
||||||
|
EXPECT_EQ(std::get<1>(t), tp_node.Scalar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST(NodeSpecTest, Ex2_23_VariousExplicitTags) {
|
TEST(NodeSpecTest, Ex2_23_VariousExplicitTags) {
|
||||||
Node doc = Load(ex2_23);
|
Node doc = Load(ex2_23);
|
||||||
EXPECT_EQ(3, doc.size());
|
EXPECT_EQ(3, doc.size());
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user