Skip to content

Commit cf289ad

Browse files
committed
Merge 'types: time_point_to_string: harden against out of range timestamps' from Benny Halevy
The time point is multiplied by an adjustment factor of 1000 for boost::posix_time::time_duration::ticks_per_second() = 1000000 when calling boost::posix_time::milliseconds(count) and that may lead to integer overflow as reported by the UndefinedBehaviorSanitizer. See scylladb#10830 (comment) This change checks for possible overflow in advance and prints the raw counter value in this case, along with an explanation. Refs scylladb#10830 Signed-off-by: Benny Halevy <[email protected]> Closes scylladb#10831 * github.com:scylladb/scylla: test: types: add test cases for timestamp_type to_string format types: time_point_to_string: harden against out of range timestamps
2 parents e499f45 + e5b7ce4 commit cf289ad

File tree

2 files changed

+43
-8
lines changed

2 files changed

+43
-8
lines changed

test/boost/types_test.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,26 @@ void test_timestamp_like_string_conversions(data_type timestamp_type) {
278278

279279
BOOST_REQUIRE_EQUAL(timestamp_type->to_string(timestamp_type->decompose(tp)), "2015-07-03T00:00:00");
280280

281+
// test fractional milliseconds
282+
tp = db_clock::time_point(db_clock::duration(1435881600123));
283+
BOOST_REQUIRE_EQUAL(timestamp_type->to_string(timestamp_type->decompose(tp)), "2015-07-03T00:00:00.123000");
284+
285+
// test time_stamps around the unix epoch time
286+
tp = db_clock::time_point(db_clock::duration(0));
287+
BOOST_REQUIRE_EQUAL(timestamp_type->to_string(timestamp_type->decompose(tp)), "1970-01-01T00:00:00");
288+
tp = db_clock::time_point(db_clock::duration(456));
289+
BOOST_REQUIRE_EQUAL(timestamp_type->to_string(timestamp_type->decompose(tp)), "1970-01-01T00:00:00.456000");
290+
tp = db_clock::time_point(db_clock::duration(-456));
291+
BOOST_REQUIRE_EQUAL(timestamp_type->to_string(timestamp_type->decompose(tp)), "1969-12-31T23:59:59.544000");
292+
293+
// test time_stamps around year 0
294+
tp = db_clock::time_point(db_clock::duration(-62167219200000));
295+
BOOST_REQUIRE_EQUAL(timestamp_type->to_string(timestamp_type->decompose(tp)), "0-01-01T00:00:00");
296+
tp = db_clock::time_point(db_clock::duration(-62167219199211));
297+
BOOST_REQUIRE_EQUAL(timestamp_type->to_string(timestamp_type->decompose(tp)), "0-01-01T00:00:00.789000");
298+
tp = db_clock::time_point(db_clock::duration(-62167219200789));
299+
BOOST_REQUIRE_EQUAL(timestamp_type->to_string(timestamp_type->decompose(tp)), "-1-12-31T23:59:59.211000");
300+
281301
auto now = time(nullptr);
282302
::tm local_now;
283303
::localtime_r(&now, &local_now);

types.cc

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
#include <string>
2828
#include <regex>
2929
#include <concepts>
30+
#include <ctime>
31+
#include <cstdlib>
32+
#include <fmt/chrono.h>
3033
#include <boost/iterator/transform_iterator.hpp>
3134
#include <boost/range/adaptor/filtered.hpp>
3235
#include <boost/range/numeric.hpp>
@@ -67,14 +70,26 @@ requires requires {
6770
sstring
6871
time_point_to_string(const T& tp)
6972
{
70-
int64_t count = tp.time_since_epoch().count();
71-
auto time = boost::posix_time::from_time_t(0) + boost::posix_time::milliseconds(count);
72-
constexpr std::string_view units = "milliseconds";
73-
try {
74-
return boost::posix_time::to_iso_extended_string(time);
75-
} catch (const std::exception& e) {
76-
return format("{} {} ({})", count, units, e.what());
77-
}
73+
auto count = tp.time_since_epoch().count();
74+
auto d = std::div(int64_t(count), int64_t(1000));
75+
std::time_t seconds = d.quot;
76+
std::tm tm;
77+
if (!gmtime_r(&seconds, &tm)) {
78+
return fmt::format("{} milliseconds (out of range)", count);
79+
}
80+
auto millis = d.rem;
81+
if (!millis) {
82+
return fmt::format("{:%Y-%m-%dT%H:%M:%S}", tm);
83+
}
84+
// adjust seconds for time points earlier than posix epoch
85+
// to keep the fractional millis positive
86+
if (millis < 0) {
87+
millis += 1000;
88+
seconds--;
89+
gmtime_r(&seconds, &tm);
90+
}
91+
auto micros = millis * 1000;
92+
return fmt::format("{:%Y-%m-%dT%H:%M:%S}.{:06d}", tm, micros);
7893
}
7994

8095
sstring simple_date_to_string(const uint32_t days_count) {

0 commit comments

Comments
 (0)