Merge 4730bffa1f into 728e26e426
This commit is contained in:
commit
3e4b2a5cf3
@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <map>
|
||||
@ -292,6 +293,25 @@ struct convert<Binary> {
|
||||
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
|
||||
|
||||
159
src/convert.cpp
159
src/convert.cpp
@ -1,5 +1,10 @@
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
|
||||
#include "yaml-cpp/exceptions.h"
|
||||
#include "yaml-cpp/node/convert.h"
|
||||
|
||||
namespace {
|
||||
@ -73,3 +78,157 @@ bool convert<bool>::decode(const Node& node, bool& rhs) {
|
||||
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
|
||||
constexpr uint16_t MS_PER_S = 1000;
|
||||
constexpr 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
|
||||
constexpr uint16_t TM_BASE_YEAR = 1900;
|
||||
constexpr uint16_t MIN_PER_HR = 60;
|
||||
constexpr uint16_t MS_PER_S = 1000;
|
||||
constexpr uint16_t S_PER_MIN = 60;
|
||||
constexpr uint16_t S_PER_HR = 3600;
|
||||
constexpr 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
|
||||
|
||||
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) {
|
||||
Node doc = Load(ex2_23);
|
||||
EXPECT_EQ(3, doc.size());
|
||||
|
||||
Loading…
Reference in New Issue
Block a user