Skip to content

Commit 3602194

Browse files
Krishna Paifacebook-github-bot
authored andcommitted
feat: Add support for Time with Timezone type (facebookincubator#14999)
Summary: Add support for TIME WITH TIME ZONE Type. TIME WITH TIME ZONE represents time from midnight in milliseconds precision at a particular timezone. Its physical type is BIGINT. The high 52 bits of bigint store signed integer for milliseconds in UTC. The lower 12 bits store the time zone offsets minutes. This allows the time to be converted at any point of time without ambiguity of daylight savings time. Time zone offsets range from -14:00 hours to +14:00 hours. When converting to string we encode the offset hours instead of the timezone name as per [issue](prestodb/presto#25957). NOTE: I will be adding support for CastOperator, CustomComparator , InputGenerator in subsequent PR's/Diffs. Reviewed By: kevinwilfong Differential Revision: D83195237
1 parent 846b3dc commit 3602194

File tree

9 files changed

+395
-9
lines changed

9 files changed

+395
-9
lines changed

velox/docs/develop/types.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ TDIGEST VARBINARY
182182
QDIGEST VARBINARY
183183
BIGINT_ENUM BIGINT
184184
VARCHAR_ENUM VARCHAR
185+
TIME WITH TIME ZONE BIGINT
185186
======================== =====================
186187

187188
TIMESTAMP WITH TIME ZONE represents a time point in milliseconds precision
@@ -247,6 +248,12 @@ VarcharEnumParameter as the key.
247248
Casting is only permitted to and from VARCHAR type, and is case-sensitive. Casting between different enum types is not permitted.
248249
Comparison operations are only allowed between values of the same enum type.
249250

251+
TIME WITH TIME ZONE represents time from midnight in milliseconds precision at a particular timezone.
252+
Its physical type is BIGINT. The high 52 bits of bigint store signed integer for milliseconds in UTC.
253+
The lower 12 bits store the time zone offsets minutes. This allows the time to be converted at any point of
254+
time without ambiguity of daylight savings time. Time zone offsets range from -14:00 hours to +14:00 hours.
255+
256+
250257
Spark Types
251258
~~~~~~~~~~~~
252259
The `data types <https://spark.apache.org/docs/latest/sql-ref-datatypes.html>`_ in Spark have some semantic differences compared to those in

velox/expression/fuzzer/ExpressionFuzzerTest.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ std::unordered_set<std::string> skipFunctions = {
257257
"geometry_to_bing_tiles",
258258
"geometry_to_dissolved_bing_tiles",
259259
"geometry_union",
260+
"localtime",
260261
};
261262

262263
std::unordered_set<std::string> skipFunctionsSOT = {

velox/functions/prestosql/types/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ velox_add_library(
3030
SfmSketchRegistration.cpp
3131
TDigestRegistration.cpp
3232
TimestampWithTimeZoneRegistration.cpp
33+
TimeWithTimezoneRegistration.cpp
3334
UuidRegistration.cpp
3435
VarcharEnumRegistration.cpp
3536
VarcharEnumType.cpp
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "velox/functions/prestosql/types/TimeWithTimezoneType.h"
18+
#include "velox/functions/prestosql/types/TimestampWithTimeZoneType.h"
19+
#include "velox/type/Type.h"
20+
21+
namespace facebook::velox {
22+
23+
folly::dynamic TimeWithTimezoneType::serialize() const {
24+
folly::dynamic obj = folly::dynamic::object;
25+
obj["name"] = "Type";
26+
obj["type"] = name();
27+
return obj;
28+
}
29+
30+
std::string TimeWithTimezoneType::valueToString(int64_t value) const {
31+
// TIME WITH TIME ZONE is encoded similarly to TIMESTAMP WITH TIME ZONE
32+
// with the most significnat 52 bits representing the time component and the
33+
// least 12 bits representing the timezone minutes. This is different from
34+
// TIMESTAMP WITH TIMEZONE where the last 12 bits represent the timezone
35+
// offset. The timezone offset minutes are stored by value, encoded in the
36+
// type itself. This allows the type to be used in a timezone-agnostic manner.
37+
//
38+
// The time component is a 52 bit value representing the number of
39+
// milliseconds since midnight in UTC.
40+
41+
int64_t timeComponent = unpackMillisUtc(value);
42+
43+
// Ensure time component is within valid range
44+
VELOX_CHECK_GE(timeComponent, 0, "Time component is negative");
45+
VELOX_CHECK_LE(timeComponent, kMillisInDay, "Time component is too large");
46+
47+
int64_t hours = timeComponent / kMillisInHour;
48+
int64_t remainingMs = timeComponent % kMillisInHour;
49+
int64_t minutes = remainingMs / kMillisInMinute;
50+
remainingMs = remainingMs % kMillisInMinute;
51+
int64_t seconds = remainingMs / kMillisInSecond;
52+
int64_t millis = remainingMs % kMillisInSecond;
53+
54+
// TimeZone's are encoded as a 12 bit value.
55+
// This represents a range of -14:00 to +14:00, with 0 representing UTC.
56+
// The range is from -840 to 840 minutes, we thus encode by doing bias
57+
// encoding and taking 840 as the bias.
58+
auto timezoneMinutes = unpackZoneKeyId(value);
59+
60+
VELOX_CHECK_GE(timezoneMinutes, 0, "Timezone offset is less than -14:00");
61+
VELOX_CHECK_LE(
62+
timezoneMinutes, 1680, "Timezone offset is greater than +14:00");
63+
64+
auto decodedMinutes = timezoneMinutes >= kTimeZoneBias
65+
? timezoneMinutes - kTimeZoneBias
66+
: kTimeZoneBias - timezoneMinutes;
67+
68+
const auto isBehindUTCString = timezoneMinutes >= kTimeZoneBias ? "+" : "-";
69+
70+
int16_t offsetHours = decodedMinutes / kMinutesInHour;
71+
int16_t remainingOffsetMinutes = decodedMinutes % kMinutesInHour;
72+
73+
return fmt::format(
74+
"{:02d}:{:02d}:{:02d}.{:03d}{}{:02d}:{:02d}",
75+
hours,
76+
minutes,
77+
seconds,
78+
millis,
79+
isBehindUTCString,
80+
offsetHours,
81+
remainingOffsetMinutes);
82+
}
83+
84+
namespace {
85+
86+
class TimeWithTimezoneTypeFactory : public CustomTypeFactory {
87+
public:
88+
TimeWithTimezoneTypeFactory() = default;
89+
90+
TypePtr getType(const std::vector<TypeParameter>& parameters) const override {
91+
VELOX_CHECK(parameters.empty());
92+
return TIME_WITH_TIME_ZONE();
93+
}
94+
95+
// Type casting from and to TimestampWithTimezone is not supported yet.
96+
exec::CastOperatorPtr getCastOperator() const override {
97+
return nullptr;
98+
}
99+
100+
AbstractInputGeneratorPtr getInputGenerator(
101+
const InputGeneratorConfig& config) const override {
102+
return nullptr;
103+
}
104+
};
105+
} // namespace
106+
107+
void registerTimeWithTimezoneType() {
108+
registerCustomType(
109+
"time with time zone",
110+
std::make_unique<const TimeWithTimezoneTypeFactory>());
111+
}
112+
113+
} // namespace facebook::velox
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
namespace facebook::velox {
20+
void registerTimeWithTimezoneType();
21+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include "velox/type/Type.h"
20+
21+
namespace facebook::velox {
22+
23+
/// Represents TIME WITH TIME ZONE as a bigint.
24+
/// This type stores time with timezone information, typically encoded as:
25+
/// - Most significant bits: milliseconds since midnight UTC (similar to TIME)
26+
/// - Least significant bits : timezone information
27+
class TimeWithTimezoneType final : public BigintType {
28+
TimeWithTimezoneType() = default;
29+
30+
public:
31+
static constexpr int16_t kTimeZoneBias = 840;
32+
static constexpr int16_t kMinutesInHour = 60;
33+
34+
static std::shared_ptr<const TimeWithTimezoneType> get() {
35+
VELOX_CONSTEXPR_SINGLETON TimeWithTimezoneType kInstance;
36+
return {std::shared_ptr<const TimeWithTimezoneType>{}, &kInstance};
37+
}
38+
39+
bool equivalent(const Type& other) const override {
40+
// Pointer comparison works since this type is a singleton.
41+
return this == &other;
42+
}
43+
44+
const char* name() const override {
45+
return "TIME WITH TIME ZONE";
46+
}
47+
48+
std::string toString() const override {
49+
return name();
50+
}
51+
52+
/// Returns the time with timezone 'value' formatted as HH:MM:SS.mmmZZ
53+
/// where the timezone offset is included in the representation.
54+
std::string valueToString(int64_t value) const;
55+
56+
folly::dynamic serialize() const override;
57+
58+
static TypePtr deserialize(const folly::dynamic& /*obj*/) {
59+
return TimeWithTimezoneType::get();
60+
}
61+
62+
bool isOrderable() const override {
63+
return true;
64+
}
65+
66+
bool isComparable() const override {
67+
return true;
68+
}
69+
70+
/// Encodes the timezone offset in the upper bits of the bigint.
71+
/// The timezone offset is encoded as an integer in the range [-840, 840]
72+
/// representing the number of minutes from UTC. The bias is added to the
73+
/// timezone offset to ensure that the timezone offset is positive.
74+
/// Typically called before a call to pack which will encode the timezone
75+
/// offset along with the time value.
76+
static inline int16_t biasEncode(int16_t timeZoneOffsetMinutes) {
77+
VELOX_CHECK(
78+
-kTimeZoneBias <= timeZoneOffsetMinutes &&
79+
timeZoneOffsetMinutes <= kTimeZoneBias,
80+
"Timezone offset must be between -840 and 840 minutes. Got: ",
81+
timeZoneOffsetMinutes);
82+
return timeZoneOffsetMinutes + kTimeZoneBias;
83+
}
84+
};
85+
86+
inline bool isTimeWithTimeZone(const TypePtr& other) {
87+
return TimeWithTimezoneType::get() == other;
88+
}
89+
90+
using TimeWithTimezoneTypePtr = std::shared_ptr<const TimeWithTimezoneType>;
91+
92+
FOLLY_ALWAYS_INLINE TimeWithTimezoneTypePtr TIME_WITH_TIME_ZONE() {
93+
return TimeWithTimezoneType::get();
94+
}
95+
96+
} // namespace facebook::velox

velox/functions/prestosql/types/parser/tests/TypeParserTest.cpp

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ static const TypePtr& TIMESTAMP_WITH_TIME_ZONE() {
4646
return instance;
4747
}
4848

49+
static const TypePtr& TIME_WITH_TIME_ZONE() {
50+
static const TypePtr instance{new CustomType()};
51+
return instance;
52+
}
53+
4954
class TypeFactory : public CustomTypeFactory {
5055
public:
5156
TypeFactory(const TypePtr& type) : type_(type) {}
@@ -76,6 +81,9 @@ class TypeParserTest : public ::testing::Test {
7681
registerCustomType(
7782
"timestamp with time zone",
7883
std::make_unique<const TypeFactory>(TIMESTAMP_WITH_TIME_ZONE()));
84+
registerCustomType(
85+
"time with time zone",
86+
std::make_unique<const TypeFactory>(TIME_WITH_TIME_ZONE()));
7987
}
8088
};
8189

@@ -106,6 +114,10 @@ TEST_F(TypeParserTest, time) {
106114
ASSERT_EQ(*parseType("time"), *TIME());
107115
}
108116

117+
TEST_F(TypeParserTest, timeWithTimeZoneType) {
118+
ASSERT_EQ(*parseType("time with time zone"), *TIME_WITH_TIME_ZONE());
119+
}
120+
109121
TEST_F(TypeParserTest, arrayType) {
110122
ASSERT_EQ(*parseType("array(bigint)"), *ARRAY(BIGINT()));
111123

@@ -269,11 +281,6 @@ TEST_F(TypeParserTest, rowType) {
269281
}
270282

271283
TEST_F(TypeParserTest, typesWithSpaces) {
272-
// Type is not registered.
273-
VELOX_ASSERT_UNSUPPORTED_THROW(
274-
parseType("row(time time with time zone)"),
275-
"Failed to parse type [time with time zone]. Type not registered.");
276-
277284
ASSERT_EQ(
278285
*parseType("timestamp with time zone"), *TIMESTAMP_WITH_TIME_ZONE());
279286

@@ -285,10 +292,6 @@ TEST_F(TypeParserTest, typesWithSpaces) {
285292
ASSERT_EQ(
286293
*parseType("row(double double precision)"), *ROW({"double"}, {DOUBLE()}));
287294

288-
VELOX_ASSERT_THROW(
289-
parseType("row(time with time zone)"),
290-
"Failed to parse type [with time zone]");
291-
292295
ASSERT_EQ(*parseType("row(double precision)"), *ROW({DOUBLE()}));
293296

294297
ASSERT_EQ(

velox/functions/prestosql/types/tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ add_executable(
1818
HyperLogLogTypeTest.cpp
1919
JsonTypeTest.cpp
2020
TimestampWithTimeZoneTypeTest.cpp
21+
TimeWithTimezoneTypeTest.cpp
2122
TDigestTypeTest.cpp
2223
QDigestTypeTest.cpp
2324
TypeTestBase.cpp

0 commit comments

Comments
 (0)