Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timezone conversions based on seconds #10563

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions velox/functions/lib/DateTimeFormatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1086,11 +1086,11 @@ int32_t DateTimeFormatter::format(
Timestamp t = timestamp;
if (timezone != nullptr) {
const auto utcSeconds = timestamp.getSeconds();
t.toTimezone(*timezone, allowOverflow);
t.toTimezone(*timezone);

offset = t.getSeconds() - utcSeconds;
}
const auto timePoint = t.toTimePoint(allowOverflow);
const auto timePoint = t.toTimePointMs(allowOverflow);
const auto daysTimePoint = date::floor<date::days>(timePoint);

const auto durationInTheDay = date::make_time(timePoint - daysTimePoint);
Expand Down
19 changes: 12 additions & 7 deletions velox/type/Timestamp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,22 +122,27 @@ void validateTimePoint(const std::chrono::time_point<
} // namespace

std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>
Timestamp::toTimePoint(bool allowOverflow) const {
Timestamp::toTimePointMs(bool allowOverflow) const {
using namespace std::chrono;
auto tp = time_point<system_clock, milliseconds>(
milliseconds(allowOverflow ? toMillisAllowOverflow() : toMillis()));
validateTimePoint(tp);
return tp;
}

void Timestamp::toTimezone(const tz::TimeZone& zone, bool allowOverflow) {
auto tp = toTimePoint(allowOverflow);
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
Timestamp::toTimePointSec() const {
using namespace std::chrono;
auto tp = time_point<system_clock, seconds>(seconds(seconds_));
validateTimePoint(tp);
return tp;
}

try {
auto epoch = zone.to_local(tp).time_since_epoch();
void Timestamp::toTimezone(const tz::TimeZone& zone) {
auto tp = toTimePointSec();

// NOTE: Round down to get the seconds of the current time point.
seconds_ = std::chrono::floor<std::chrono::seconds>(epoch).count();
try {
seconds_ = zone.to_local(tp).time_since_epoch().count();
} catch (const std::invalid_argument& e) {
// Invalid argument means we hit a conversion not supported by
// external/date. Need to throw a RuntimeError so that try() statements do
Expand Down
43 changes: 28 additions & 15 deletions velox/type/Timestamp.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,25 @@ struct Timestamp {
}
}

/// Due to the limit of std::chrono, throws if timestamp is outside of
/// Exports the current timestamp as a std::chrono::time_point of millisecond
/// precision. Note that the conversion may overflow since the internal
/// `seconds_` value will need to be multiplied by 1000.
///
/// If `allowOverflow` is true, integer overflow is allowed in converting
/// to milliseconds.
///
/// Due to the limit of velox/external/date, throws if timestamp is outside of
/// [-32767-01-01, 32767-12-31] range.
/// If allowOverflow is true, integer overflow is allowed in converting
/// timestamp to milliseconds.
std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>
toTimePoint(bool allowOverflow = false) const;
toTimePointMs(bool allowOverflow = false) const;

/// Exports the current timestamp as a std::chrono::time_point of second
/// precision.
///
/// Due to the limit of velox/external/date, throws if timestamp is outside of
/// [-32767-01-01, 32767-12-31] range.
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
toTimePointSec() const;

static Timestamp fromMillis(int64_t millis) {
if (millis >= 0 || millis % 1'000 == 0) {
Expand Down Expand Up @@ -330,23 +343,23 @@ struct Timestamp {
char* const startPosition);

// Assuming the timestamp represents a time at zone, converts it to the GMT
// time at the same moment.
// Example: Timestamp ts{0, 0};
// ts.Timezone("America/Los_Angeles");
// ts.toString() returns January 1, 1970 08:00:00
// time at the same moment. For example:
//
// Timestamp ts{0, 0};
// ts.Timezone("America/Los_Angeles");
// ts.toString(); // returns January 1, 1970 08:00:00
void toGMT(const tz::TimeZone& zone);

// Same as above, but accepts PrestoDB time zone ID.
void toGMT(int16_t tzID);

/// Assuming the timestamp represents a GMT time, converts it to the time at
/// the same moment at zone.
/// @param allowOverflow If true, integer overflow is allowed when converting
/// timestamp to TimePoint. Otherwise, user exception is thrown for overflow.
/// Example: Timestamp ts{0, 0};
/// ts.Timezone("America/Los_Angeles");
/// ts.toString() returns December 31, 1969 16:00:00
void toTimezone(const tz::TimeZone& zone, bool allowOverflow = false);
/// the same moment at zone. For example:
///
/// Timestamp ts{0, 0};
/// ts.Timezone("America/Los_Angeles");
/// ts.toString(); // returns December 31, 1969 16:00:00
void toTimezone(const tz::TimeZone& zone);

// Same as above, but accepts PrestoDB time zone ID.
void toTimezone(int16_t tzID);
Expand Down
8 changes: 4 additions & 4 deletions velox/type/tests/TimestampConversionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,22 +282,22 @@ TEST(DateTimeUtilTest, toGMT) {
EXPECT_EQ(ts, parseTimestamp("1970-01-01 08:00:00"));

// Set on a random date/time and try some variations.
ts = parseTimestamp("2020-04-23 04:23:37");
ts = parseTimestamp("2020-04-23 04:23:37.926");

// To LA:
auto tsCopy = ts;
tsCopy.toGMT(*laZone);
EXPECT_EQ(tsCopy, parseTimestamp("2020-04-23 11:23:37"));
EXPECT_EQ(tsCopy, parseTimestamp("2020-04-23 11:23:37.926"));

// To Sao Paulo:
tsCopy = ts;
tsCopy.toGMT(*tz::locateZone("America/Sao_Paulo"));
EXPECT_EQ(tsCopy, parseTimestamp("2020-04-23 07:23:37"));
EXPECT_EQ(tsCopy, parseTimestamp("2020-04-23 07:23:37.926"));

// Moscow:
tsCopy = ts;
tsCopy.toGMT(*tz::locateZone("Europe/Moscow"));
EXPECT_EQ(tsCopy, parseTimestamp("2020-04-23 01:23:37"));
EXPECT_EQ(tsCopy, parseTimestamp("2020-04-23 01:23:37.926"));

// Probe LA's daylight savings boundary (starts at 2021-13-14 02:00am).
// Before it starts, 8h offset:
Expand Down
14 changes: 10 additions & 4 deletions velox/type/tests/TimestampTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -398,16 +398,22 @@ TEST(TimestampTest, outOfRange) {
Timestamp t1(-3217830796800, 0);

VELOX_ASSERT_THROW(
t1.toTimePoint(), "Timestamp is outside of supported range");
t1.toTimePointMs(), "Timestamp is outside of supported range");
VELOX_ASSERT_THROW(
t1.toTimePointSec(), "Timestamp is outside of supported range");
VELOX_ASSERT_THROW(
t1.toTimezone(*timezone), "Timestamp is outside of supported range");

timezone = tz::locateZone("America/Los_Angeles");
VELOX_ASSERT_THROW(
t1.toGMT(*timezone),
"Timestamp seconds out of range for time zone adjustment");

// #2. external/date doesn't understand OS_TZDB repetition rules. Therefore,
// for timezones with pre-defined repetition rules for daylight savings, for
// example, it will throw for anything larger than 2037 (which is what is
// currently materialized in OS_TZDBs). America/Los_Angeles is an example of
// such timezone.
timezone = tz::locateZone("America/Los_Angeles");
Timestamp t2(32517359891, 0);
VELOX_ASSERT_THROW(
t2.toTimezone(*timezone),
Expand All @@ -420,12 +426,12 @@ TEST(TimestampTest, outOfRange) {
TEST(TimestampTest, overflow) {
Timestamp t(std::numeric_limits<int64_t>::max(), 0);
VELOX_ASSERT_THROW(
t.toTimePoint(false),
t.toTimePointMs(false),
fmt::format(
"Could not convert Timestamp({}, {}) to milliseconds",
std::numeric_limits<int64_t>::max(),
0));
ASSERT_NO_THROW(t.toTimePoint(true));
ASSERT_NO_THROW(t.toTimePointMs(true));
}
#endif

Expand Down
Loading