Skip to content

Commit

Permalink
Optionally allow fractional seconds in timestamps
Browse files Browse the repository at this point in the history
Fixes #267
  • Loading branch information
joto committed Sep 4, 2023
1 parent 345d190 commit 938b989
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 4 deletions.
5 changes: 3 additions & 2 deletions include/osmium/osm/object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,9 @@ namespace osmium {
*/
OSMObject& set_timestamp(const char* timestamp) {
assert(timestamp);
m_timestamp = detail::parse_timestamp(timestamp);
if (timestamp[20] != '\0') {
const char** str = &timestamp;
m_timestamp = detail::parse_timestamp(str);
if (**str != '\0') {
throw std::invalid_argument{"can not parse timestamp: garbage after timestamp"};
}
return *this;
Expand Down
33 changes: 31 additions & 2 deletions include/osmium/osm/timestamp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,30 @@ namespace osmium {
out += static_cast<char>('0' + value);
}

inline std::time_t parse_timestamp(const char* str) {
inline bool fractional_seconds(const char** s) noexcept {
const char* str = *s;

if (*str != '.' && *str != ',') {
return false;
}

++str;
if (*str < '0' || *str > '9') {
return false;
}

do {
++str;
} while (*str >= '0' && *str <= '9');

*s = str;
return *str == 'Z';
}

inline std::time_t parse_timestamp(const char** s) {
const char* str = *s;
*s += 19;

static const std::array<int, 12> mon_lengths = {{
31, 29, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31
Expand All @@ -104,7 +127,8 @@ namespace osmium {
str[16] == ':' &&
str[17] >= '0' && str[17] <= '9' &&
str[18] >= '0' && str[18] <= '9' &&
str[19] == 'Z') {
(str[19] == 'Z' || fractional_seconds(s))) {
++(*s);
std::tm tm; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
tm.tm_year = (str[ 0] - '0') * 1000 +
(str[ 1] - '0') * 100 +
Expand Down Expand Up @@ -134,6 +158,11 @@ namespace osmium {
throw std::invalid_argument{std::string{"can not parse timestamp: '"} + str + "'"};
}

inline std::time_t parse_timestamp(const char* s) {
const char** str = &s;
return parse_timestamp(str);
}

} // namespace detail

/**
Expand Down
39 changes: 39 additions & 0 deletions test/t/osm/test_timestamp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,39 @@ TEST_CASE("Valid timestamps") {
}
}

TEST_CASE("Valid timestamps with fractional seconds") {
const std::vector<std::string> test_cases = {
"2016-03-31T23:59:59.123Z",
"2016-03-31T23:59:59,4Z",
"2016-03-31T23:59:59,000000000000000001Z",
"2016-03-31T23:59:59.99Z"
};

for (const auto& tc : test_cases) {
const osmium::Timestamp t{tc};
REQUIRE("2016-03-31T23:59:59Z" == t.to_iso());
REQUIRE("2016-03-31T23:59:59Z" == t.to_iso_all());
}
}

TEST_CASE("Timestamp parsing leaves pointer after timestamp") {
const std::vector<std::string> test_cases = {
"2016-03-31T23:59:59Z#",
"2016-03-31T23:59:59.123Z#",
"2016-03-31T23:59:59,4Z#",
"2016-03-31T23:59:59,000000000000000001Z#",
"2016-03-31T23:59:59.99Z#"
};

for (const auto& tc : test_cases) {
const char *s = tc.data();
const char **str = &s;
auto const timestamp = osmium::detail::parse_timestamp(str);
REQUIRE(**str == '#');
REQUIRE(osmium::Timestamp(timestamp).to_iso() == "2016-03-31T23:59:59Z");
}
}

TEST_CASE("Invalid timestamps") {
REQUIRE_THROWS_AS(osmium::Timestamp{""}, std::invalid_argument);
REQUIRE_THROWS_AS(osmium::Timestamp{"x"}, std::invalid_argument);
Expand All @@ -149,5 +182,11 @@ TEST_CASE("Invalid timestamps") {
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-32T00:00:00Z"}, std::invalid_argument);
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-02-30T00:00:00Z"}, std::invalid_argument);
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-32T00:00:00Z"}, std::invalid_argument);
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00"}, std::invalid_argument);
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00,Z"}, std::invalid_argument);
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00,xZ"}, std::invalid_argument);
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00,123mZ"}, std::invalid_argument);
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00.@Z"}, std::invalid_argument);
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00.@"}, std::invalid_argument);
}

0 comments on commit 938b989

Please sign in to comment.