Skip to content

Commit 938b989

Browse files
committed
Optionally allow fractional seconds in timestamps
Fixes #267
1 parent 345d190 commit 938b989

File tree

3 files changed

+73
-4
lines changed

3 files changed

+73
-4
lines changed

include/osmium/osm/object.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,9 @@ namespace osmium {
307307
*/
308308
OSMObject& set_timestamp(const char* timestamp) {
309309
assert(timestamp);
310-
m_timestamp = detail::parse_timestamp(timestamp);
311-
if (timestamp[20] != '\0') {
310+
const char** str = &timestamp;
311+
m_timestamp = detail::parse_timestamp(str);
312+
if (**str != '\0') {
312313
throw std::invalid_argument{"can not parse timestamp: garbage after timestamp"};
313314
}
314315
return *this;

include/osmium/osm/timestamp.hpp

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,30 @@ namespace osmium {
7979
out += static_cast<char>('0' + value);
8080
}
8181

82-
inline std::time_t parse_timestamp(const char* str) {
82+
inline bool fractional_seconds(const char** s) noexcept {
83+
const char* str = *s;
84+
85+
if (*str != '.' && *str != ',') {
86+
return false;
87+
}
88+
89+
++str;
90+
if (*str < '0' || *str > '9') {
91+
return false;
92+
}
93+
94+
do {
95+
++str;
96+
} while (*str >= '0' && *str <= '9');
97+
98+
*s = str;
99+
return *str == 'Z';
100+
}
101+
102+
inline std::time_t parse_timestamp(const char** s) {
103+
const char* str = *s;
104+
*s += 19;
105+
83106
static const std::array<int, 12> mon_lengths = {{
84107
31, 29, 31, 30, 31, 30,
85108
31, 31, 30, 31, 30, 31
@@ -104,7 +127,8 @@ namespace osmium {
104127
str[16] == ':' &&
105128
str[17] >= '0' && str[17] <= '9' &&
106129
str[18] >= '0' && str[18] <= '9' &&
107-
str[19] == 'Z') {
130+
(str[19] == 'Z' || fractional_seconds(s))) {
131+
++(*s);
108132
std::tm tm; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
109133
tm.tm_year = (str[ 0] - '0') * 1000 +
110134
(str[ 1] - '0') * 100 +
@@ -134,6 +158,11 @@ namespace osmium {
134158
throw std::invalid_argument{std::string{"can not parse timestamp: '"} + str + "'"};
135159
}
136160

161+
inline std::time_t parse_timestamp(const char* s) {
162+
const char** str = &s;
163+
return parse_timestamp(str);
164+
}
165+
137166
} // namespace detail
138167

139168
/**

test/t/osm/test_timestamp.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,39 @@ TEST_CASE("Valid timestamps") {
126126
}
127127
}
128128

129+
TEST_CASE("Valid timestamps with fractional seconds") {
130+
const std::vector<std::string> test_cases = {
131+
"2016-03-31T23:59:59.123Z",
132+
"2016-03-31T23:59:59,4Z",
133+
"2016-03-31T23:59:59,000000000000000001Z",
134+
"2016-03-31T23:59:59.99Z"
135+
};
136+
137+
for (const auto& tc : test_cases) {
138+
const osmium::Timestamp t{tc};
139+
REQUIRE("2016-03-31T23:59:59Z" == t.to_iso());
140+
REQUIRE("2016-03-31T23:59:59Z" == t.to_iso_all());
141+
}
142+
}
143+
144+
TEST_CASE("Timestamp parsing leaves pointer after timestamp") {
145+
const std::vector<std::string> test_cases = {
146+
"2016-03-31T23:59:59Z#",
147+
"2016-03-31T23:59:59.123Z#",
148+
"2016-03-31T23:59:59,4Z#",
149+
"2016-03-31T23:59:59,000000000000000001Z#",
150+
"2016-03-31T23:59:59.99Z#"
151+
};
152+
153+
for (const auto& tc : test_cases) {
154+
const char *s = tc.data();
155+
const char **str = &s;
156+
auto const timestamp = osmium::detail::parse_timestamp(str);
157+
REQUIRE(**str == '#');
158+
REQUIRE(osmium::Timestamp(timestamp).to_iso() == "2016-03-31T23:59:59Z");
159+
}
160+
}
161+
129162
TEST_CASE("Invalid timestamps") {
130163
REQUIRE_THROWS_AS(osmium::Timestamp{""}, std::invalid_argument);
131164
REQUIRE_THROWS_AS(osmium::Timestamp{"x"}, std::invalid_argument);
@@ -149,5 +182,11 @@ TEST_CASE("Invalid timestamps") {
149182
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-32T00:00:00Z"}, std::invalid_argument);
150183
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-02-30T00:00:00Z"}, std::invalid_argument);
151184
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-32T00:00:00Z"}, std::invalid_argument);
185+
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00"}, std::invalid_argument);
186+
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00,Z"}, std::invalid_argument);
187+
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00,xZ"}, std::invalid_argument);
188+
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00,123mZ"}, std::invalid_argument);
189+
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00.@Z"}, std::invalid_argument);
190+
REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-01T00:00:00.@"}, std::invalid_argument);
152191
}
153192

0 commit comments

Comments
 (0)