Skip to content

Commit 98edcd7

Browse files
pedroerpfacebook-github-bot
authored andcommitted
Refactor time point boundary check code (facebookincubator#10587)
Summary: Pull Request resolved: facebookincubator#10587 Refactor time point boundary check code to make it less fragmented, and adding more tests. Reviewed By: kagamiori Differential Revision: D60322812 fbshipit-source-id: e3483b972305d33ad3c7661964546626a2c16d6d
1 parent f8f538e commit 98edcd7

File tree

6 files changed

+78
-63
lines changed

6 files changed

+78
-63
lines changed

velox/type/Timestamp.cpp

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,8 @@ Timestamp Timestamp::now() {
5858
}
5959

6060
void Timestamp::toGMT(const tz::TimeZone& zone) {
61-
// Magic number -2^39 + 24*3600. This number and any number lower than that
62-
// will cause time_zone::to_sys() to SIGABRT. We don't want that to happen.
63-
VELOX_USER_CHECK_GT(
64-
seconds_,
65-
-1096193779200l + 86400l,
66-
"Timestamp seconds out of range for time zone adjustment");
67-
68-
VELOX_USER_CHECK_LE(
69-
seconds_,
70-
kMaxSeconds,
71-
"Timestamp seconds out of range for time zone adjustment");
72-
7361
std::chrono::seconds sysSeconds;
62+
7463
try {
7564
sysSeconds = zone.to_sys(std::chrono::seconds(seconds_));
7665
} catch (const date::ambiguous_local_time&) {
@@ -96,49 +85,16 @@ void Timestamp::toGMT(int16_t tzID) {
9685
}
9786
}
9887

99-
namespace {
100-
void validateTimePoint(const std::chrono::time_point<
101-
std::chrono::system_clock,
102-
std::chrono::milliseconds>& timePoint) {
103-
// Due to the limit of std::chrono we can only represent time in
104-
// [-32767-01-01, 32767-12-31] date range
105-
const auto minTimePoint = date::sys_days{
106-
date::year_month_day(date::year::min(), date::month(1), date::day(1))};
107-
const auto maxTimePoint = date::sys_days{
108-
date::year_month_day(date::year::max(), date::month(12), date::day(31))};
109-
if (timePoint < minTimePoint || timePoint > maxTimePoint) {
110-
VELOX_USER_FAIL(
111-
"Timestamp is outside of supported range of [{}-{}-{}, {}-{}-{}]",
112-
(int)date::year::min(),
113-
"01",
114-
"01",
115-
(int)date::year::max(),
116-
"12",
117-
"31");
118-
}
119-
}
120-
} // namespace
121-
12288
std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>
12389
Timestamp::toTimePointMs(bool allowOverflow) const {
12490
using namespace std::chrono;
12591
auto tp = time_point<system_clock, milliseconds>(
12692
milliseconds(allowOverflow ? toMillisAllowOverflow() : toMillis()));
127-
validateTimePoint(tp);
128-
return tp;
129-
}
130-
131-
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
132-
Timestamp::toTimePointSec() const {
133-
using namespace std::chrono;
134-
auto tp = time_point<system_clock, seconds>(seconds(seconds_));
135-
validateTimePoint(tp);
93+
tz::validateRange(tp);
13694
return tp;
13795
}
13896

13997
void Timestamp::toTimezone(const tz::TimeZone& zone) {
140-
auto tp = toTimePointSec();
141-
14298
try {
14399
seconds_ = zone.to_local(std::chrono::seconds(seconds_)).count();
144100
} catch (const std::invalid_argument& e) {

velox/type/Timestamp.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -207,14 +207,6 @@ struct Timestamp {
207207
std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>
208208
toTimePointMs(bool allowOverflow = false) const;
209209

210-
/// Exports the current timestamp as a std::chrono::time_point of second
211-
/// precision.
212-
///
213-
/// Due to the limit of velox/external/date, throws if timestamp is outside of
214-
/// [-32767-01-01, 32767-12-31] range.
215-
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
216-
toTimePointSec() const;
217-
218210
static Timestamp fromMillis(int64_t millis) {
219211
if (millis >= 0 || millis % 1'000 == 0) {
220212
return Timestamp(millis / 1'000, (millis % 1'000) * 1'000'000);

velox/type/tests/TimestampTest.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -397,17 +397,12 @@ TEST(TimestampTest, outOfRange) {
397397
auto* timezone = tz::locateZone("GMT");
398398
Timestamp t1(-3217830796800, 0);
399399

400-
VELOX_ASSERT_THROW(
401-
t1.toTimePointMs(), "Timestamp is outside of supported range");
402-
VELOX_ASSERT_THROW(
403-
t1.toTimePointSec(), "Timestamp is outside of supported range");
404-
VELOX_ASSERT_THROW(
405-
t1.toTimezone(*timezone), "Timestamp is outside of supported range");
400+
std::string expected = "Timepoint is outside of supported year range";
401+
VELOX_ASSERT_THROW(t1.toTimePointMs(), expected);
402+
VELOX_ASSERT_THROW(t1.toTimezone(*timezone), expected);
406403

407404
timezone = tz::locateZone("America/Los_Angeles");
408-
VELOX_ASSERT_THROW(
409-
t1.toGMT(*timezone),
410-
"Timestamp seconds out of range for time zone adjustment");
405+
VELOX_ASSERT_THROW(t1.toGMT(*timezone), expected);
411406

412407
// #2. external/date doesn't understand OS_TZDB repetition rules. Therefore,
413408
// for timezones with pre-defined repetition rules for daylight savings, for

velox/type/tz/TimeZoneMap.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,33 @@ std::string normalizeTimeZone(const std::string& originalZoneId) {
167167
return originalZoneId;
168168
}
169169

170+
template <typename TDuration>
171+
void validateRangeImpl(time_point<TDuration> timePoint) {
172+
using namespace velox::date;
173+
static constexpr auto kMinYear = date::year::min();
174+
static constexpr auto kMaxYear = date::year::max();
175+
176+
auto year = year_month_day(floor<days>(timePoint)).year();
177+
178+
if (year < kMinYear || year > kMaxYear) {
179+
VELOX_USER_FAIL(
180+
"Timepoint is outside of supported year range: [{}, {}], got {}",
181+
(int)kMinYear,
182+
(int)kMaxYear,
183+
(int)year);
184+
}
185+
}
186+
170187
} // namespace
171188

189+
void validateRange(time_point<std::chrono::seconds> timePoint) {
190+
validateRangeImpl(timePoint);
191+
}
192+
193+
void validateRange(time_point<std::chrono::milliseconds> timePoint) {
194+
validateRangeImpl(timePoint);
195+
}
196+
172197
std::string getTimeZoneName(int64_t timeZoneID) {
173198
const auto& timeZoneDatabase = getTimeZoneDatabase();
174199

@@ -245,6 +270,7 @@ TimeZone::seconds TimeZone::to_sys(
245270
TimeZone::seconds timestamp,
246271
TimeZone::TChoose choose) const {
247272
date::local_seconds timePoint{timestamp};
273+
validateRange(date::sys_seconds{timestamp});
248274

249275
if (tz_ == nullptr) {
250276
// We can ignore `choose` as time offset conversions are always linear.
@@ -266,6 +292,7 @@ TimeZone::seconds TimeZone::to_sys(
266292

267293
TimeZone::seconds TimeZone::to_local(TimeZone::seconds timestamp) const {
268294
date::sys_seconds timePoint{timestamp};
295+
validateRange(timePoint);
269296

270297
// If this is an offset time zone.
271298
if (tz_ == nullptr) {

velox/type/tz/TimeZoneMap.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ int16_t getTimeZoneID(std::string_view timeZone, bool failOnError = true);
5757
/// [-14:00, +14:00] range.
5858
int16_t getTimeZoneID(int32_t offsetMinutes);
5959

60+
// Validates that the time point can be safely used by the external date
61+
// library.
62+
template <typename T>
63+
using time_point = std::chrono::time_point<std::chrono::system_clock, T>;
64+
65+
void validateRange(time_point<std::chrono::seconds> timePoint);
66+
void validateRange(time_point<std::chrono::milliseconds> timePoint);
67+
6068
/// TimeZone is the proxy object for time zone management. It provides access to
6169
/// time zone names, their IDs (as defined in TimeZoneDatabase.cpp and
6270
/// consistent with Presto), and utilities for timestamp conversion across

velox/type/tz/tests/TimeZoneMapTest.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "velox/common/base/Exceptions.h"
2020
#include "velox/common/base/tests/GTestUtils.h"
21+
#include "velox/external/date/date.h"
2122
#include "velox/type/tz/TimeZoneMap.h"
2223

2324
namespace facebook::velox::tz {
@@ -115,6 +116,42 @@ TEST(TimeZoneMapTest, offsetToSys) {
115116
EXPECT_NE(toSysTime("-07:00", ts), toSysTime("America/Los_Angeles", ts));
116117
}
117118

119+
TEST(TimeZoneMapTest, timePointBoundary) {
120+
using namespace date;
121+
122+
const auto* tz = locateZone("+00:01");
123+
EXPECT_NE(tz, nullptr);
124+
125+
auto trySysYear = [&](year y) {
126+
auto date = year_month_day(y, month(1), day(1));
127+
return tz->to_sys(sys_days{date}.time_since_epoch());
128+
};
129+
130+
auto tryLocalYear = [&](year y) {
131+
auto date = year_month_day(y, month(1), day(1));
132+
return tz->to_local(sys_days{date}.time_since_epoch());
133+
};
134+
135+
EXPECT_NO_THROW(trySysYear(year(0)));
136+
EXPECT_NO_THROW(trySysYear(year::max()));
137+
EXPECT_NO_THROW(trySysYear(year::min()));
138+
139+
EXPECT_NO_THROW(tryLocalYear(year(0)));
140+
EXPECT_NO_THROW(tryLocalYear(year::max()));
141+
EXPECT_NO_THROW(tryLocalYear(year::min()));
142+
143+
std::string expected = "Timepoint is outside of supported year range";
144+
VELOX_ASSERT_THROW(trySysYear(year(int(year::max()) + 1)), expected);
145+
VELOX_ASSERT_THROW(trySysYear(year(int(year::min()) - 1)), expected);
146+
147+
VELOX_ASSERT_THROW(tryLocalYear(year(int(year::max()) + 1)), expected);
148+
VELOX_ASSERT_THROW(tryLocalYear(year(int(year::min()) - 1)), expected);
149+
150+
// This time point triggers an assertion failure in external/date. Make sure
151+
// we catch and throw before getting to that point.
152+
VELOX_ASSERT_THROW(tz->to_sys(seconds{-1096193779200l - 86400l}), expected);
153+
}
154+
118155
TEST(TimeZoneMapTest, getTimeZoneName) {
119156
EXPECT_EQ("America/Los_Angeles", getTimeZoneName(1825));
120157
EXPECT_EQ("Europe/Moscow", getTimeZoneName(2079));

0 commit comments

Comments
 (0)