Skip to content

Commit 4bdbca8

Browse files
Krishna Paifacebook-github-bot
authored andcommitted
feat: Add support for casting TIME to VARCHAR (facebookincubator#14887)
Summary: Adds support for casting TIME to VARCHAR. Casting accounts for the local time zone and also formats it in standard HH:MM:SS.mmm format. Differential Revision: D82653023
1 parent 426516c commit 4bdbca8

File tree

9 files changed

+332
-1
lines changed

9 files changed

+332
-1
lines changed

velox/expression/CastExpr.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,77 @@ VectorPtr CastExpr::castFromIntervalDayTime(
213213
}
214214
}
215215

216+
VectorPtr CastExpr::castFromTime(
217+
const SelectivityVector& rows,
218+
const BaseVector& input,
219+
exec::EvalCtx& context,
220+
const TypePtr& toType) {
221+
VectorPtr castResult;
222+
context.ensureWritable(rows, toType, castResult);
223+
(*castResult).clearNulls(rows);
224+
225+
auto* inputFlatVector = input.as<SimpleVector<int64_t>>();
226+
switch (toType->kind()) {
227+
case TypeKind::VARCHAR: {
228+
// Get session timezone
229+
const auto* timeZone =
230+
getTimeZoneFromConfig(context.execCtx()->queryCtx()->queryConfig());
231+
// Get session start time
232+
const auto startTimeMs =
233+
context.execCtx()->queryCtx()->queryConfig().sessionStartTimeMs();
234+
auto systemDay = std::chrono::milliseconds{startTimeMs} / kMillisInDay;
235+
236+
auto* resultFlatVector = castResult->as<FlatVector<StringView>>();
237+
238+
Buffer* buffer = resultFlatVector->getBufferWithSpace(
239+
rows.countSelected() * TimeType::kTimeToVarcharRowSize,
240+
true /*exactSize*/);
241+
char* rawBuffer = buffer->asMutable<char>() + buffer->size();
242+
243+
applyToSelectedNoThrowLocal(context, rows, castResult, [&](int row) {
244+
try {
245+
// Use timezone-aware conversion
246+
auto systemTime =
247+
systemDay.count() * kMillisInDay + inputFlatVector->valueAt(row);
248+
249+
int64_t adjustedTime{0};
250+
if (timeZone) {
251+
adjustedTime =
252+
(timeZone->to_local(std::chrono::milliseconds{systemTime}) %
253+
kMillisInDay)
254+
.count();
255+
} else {
256+
adjustedTime = systemTime % kMillisInDay;
257+
}
258+
259+
if (adjustedTime < 0) {
260+
adjustedTime += kMillisInDay;
261+
}
262+
263+
auto output = TIME()->valueToString(adjustedTime, rawBuffer);
264+
resultFlatVector->setNoCopy(row, output);
265+
rawBuffer += output.size();
266+
} catch (const VeloxException& ue) {
267+
if (!ue.isUserError()) {
268+
throw;
269+
}
270+
VELOX_USER_FAIL(
271+
makeErrorMessage(input, row, toType) + " " + ue.message());
272+
} catch (const std::exception& e) {
273+
VELOX_USER_FAIL(
274+
makeErrorMessage(input, row, toType) + " " + e.what());
275+
}
276+
});
277+
278+
buffer->setSize(rawBuffer - buffer->asMutable<char>());
279+
return castResult;
280+
}
281+
default:
282+
VELOX_UNSUPPORTED(
283+
"Cast from TIME to {} is not supported", toType->toString());
284+
}
285+
}
286+
216287
namespace {
217288
void propagateErrorsOrSetNulls(
218289
bool setNullInResultAtError,
@@ -650,6 +721,8 @@ void CastExpr::applyPeeled(
650721
"Cast from {} to {} is not supported",
651722
fromType->toString(),
652723
toType->toString());
724+
} else if (fromType->isTime()) {
725+
result = castFromTime(rows, input, context, toType);
653726
} else if (toType->isShortDecimal()) {
654727
result = applyDecimal<int64_t>(rows, input, context, fromType, toType);
655728
} else if (toType->isLongDecimal()) {

velox/expression/CastExpr.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ class CastExpr : public SpecialForm {
204204
exec::EvalCtx& context,
205205
const TypePtr& toType);
206206

207+
VectorPtr castFromTime(
208+
const SelectivityVector& rows,
209+
const BaseVector& input,
210+
exec::EvalCtx& context,
211+
const TypePtr& toType);
212+
207213
template <typename TInput, typename TOutput>
208214
void applyDecimalCastKernel(
209215
const SelectivityVector& rows,

velox/expression/fuzzer/SpecialFormSignatureGenerator.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ void SpecialFormSignatureGenerator::addCastFromTimestampSignature(
6363
signatures.push_back(makeCastSignature("timestamp", toType));
6464
}
6565

66+
void SpecialFormSignatureGenerator::addCastFromTimeSignature(
67+
const std::string& toType,
68+
std::vector<exec::FunctionSignaturePtr>& signatures) const {
69+
signatures.push_back(makeCastSignature("time", toType));
70+
}
71+
6672
void SpecialFormSignatureGenerator::addCastFromDateSignature(
6773
const std::string& toType,
6874
std::vector<exec::FunctionSignaturePtr>& signatures) const {
@@ -179,6 +185,7 @@ SpecialFormSignatureGenerator::getSignaturesForCast() const {
179185
addCastFromVarcharSignature("varchar", signatures);
180186
addCastFromDateSignature("varchar", signatures);
181187
addCastFromTimestampSignature("varchar", signatures);
188+
addCastFromTimeSignature("varchar", signatures);
182189

183190
// To timestamp type.
184191
addCastFromVarcharSignature("timestamp", signatures);

velox/expression/fuzzer/SpecialFormSignatureGenerator.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ class SpecialFormSignatureGenerator {
5757
const std::string& toType,
5858
std::vector<exec::FunctionSignaturePtr>& signatures) const;
5959

60+
/// Generates signatures for cast from time to the given type and adds
61+
/// them to signatures.
62+
void addCastFromTimeSignature(
63+
const std::string& toType,
64+
std::vector<exec::FunctionSignaturePtr>& signatures) const;
65+
6066
/// Generates signatures for cast from date to the given type and adds them to
6167
/// signatures.
6268
void addCastFromDateSignature(

velox/expression/tests/CastExprTest.cpp

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "velox/functions/prestosql/tests/utils/FunctionBaseTest.h"
2828
#include "velox/type/Type.h"
2929
#include "velox/type/tests/utils/CustomTypesForTesting.h"
30+
#include "velox/type/tz/TimeZoneMap.h"
3031
#include "velox/vector/BaseVector.h"
3132
#include "velox/vector/TypeAliases.h"
3233

@@ -2975,5 +2976,195 @@ TEST_F(CastExprTest, skipUnnecessaryChildrenOfComplexTypes) {
29752976
testCast(mapVector, expectedMapVector);
29762977
}
29772978
}
2979+
2980+
TEST_F(CastExprTest, timeToVarcharCast) {
2981+
{
2982+
// Test casting TIME to VARCHAR
2983+
2984+
// Test various TIME values (milliseconds since midnight)
2985+
// 0 = 00:00:00.000
2986+
// 3661000 = 01:01:01.000
2987+
// 43200000 = 12:00:00.000 (noon)
2988+
// 86399999 = 23:59:59.999
2989+
auto timeVector =
2990+
makeFlatVector<int64_t>({0, 3661000, 43200000, 86399999}, TIME());
2991+
2992+
auto result = evaluate<FlatVector<StringView>>(
2993+
"cast(c0 as varchar)", makeRowVector({timeVector}));
2994+
2995+
auto expected = makeFlatVector<StringView>(
2996+
{"00:00:00.000", "01:01:01.000", "12:00:00.000", "23:59:59.999"});
2997+
2998+
assertEqualVectors(expected, result);
2999+
}
3000+
3001+
{
3002+
// Test casting TIME to VARCHAR with nulls
3003+
auto timeVector = makeNullableFlatVector<int64_t>(
3004+
{0, std::nullopt, 43200000, std::nullopt}, TIME());
3005+
3006+
auto result = evaluate<FlatVector<StringView>>(
3007+
"cast(c0 as varchar)", makeRowVector({timeVector}));
3008+
3009+
auto expected = makeNullableFlatVector<StringView>(
3010+
{"00:00:00.000", std::nullopt, "12:00:00.000", std::nullopt});
3011+
3012+
assertEqualVectors(expected, result);
3013+
}
3014+
3015+
{
3016+
// Test try_cast for TIME to VARCHAR
3017+
auto timeVector = makeFlatVector<int64_t>({0, 43200000}, TIME());
3018+
3019+
auto result = evaluate<FlatVector<StringView>>(
3020+
"try_cast(c0 as varchar)", makeRowVector({timeVector}));
3021+
3022+
auto expected =
3023+
makeFlatVector<StringView>({"00:00:00.000", "12:00:00.000"});
3024+
3025+
assertEqualVectors(expected, result);
3026+
}
3027+
3028+
{
3029+
// Test across different time zones with America/Los_Angeles timezone
3030+
3031+
// Test various TIME values (milliseconds since midnight)
3032+
// Los Angeles is UTC-8 (standard time) or UTC-7 (daylight saving time)
3033+
auto timeVector = makeFlatVector<int64_t>(
3034+
{
3035+
0, // 00:00:00.000 UTC -> should adjust for timezone
3036+
3661000, // 01:01:01.000 UTC -> should adjust for timezone
3037+
43200000, // 12:00:00.000 UTC -> should adjust for timezone
3038+
86399999, // 23:59:59.999 UTC -> should adjust for timezone
3039+
25200000, // 07:00:00.000 UTC -> should adjust for timezone
3040+
72000000 // 20:00:00.000 UTC -> should adjust for timezone
3041+
},
3042+
TIME());
3043+
3044+
// With timezone displacement, the times should be adjusted
3045+
// Note: The exact expected values depend on the timezone implementation
3046+
// This test verifies that timezone-aware casting is working
3047+
3048+
{
3049+
// In Daylight savings time at session start time below.
3050+
setSessionStartTimeAndTimeZone(
3051+
1756710000000, "America/Los_Angeles"); // 2025-09-01T00:00:00.000
3052+
3053+
auto result = evaluate<FlatVector<StringView>>(
3054+
"cast(c0 as varchar)", makeRowVector({timeVector}));
3055+
3056+
auto expected = makeFlatVector<StringView>({
3057+
"17:00:00.000", // 00:00:00.000 - 7 hours = 17:00:00.000 (previous
3058+
// day)
3059+
"18:01:01.000", // 01:01:01.000 - 7 hours = 18:01:01.000 (previous
3060+
// day)
3061+
"05:00:00.000", // 12:00:00.000 - 7 hours = 05:00:00.000
3062+
"16:59:59.999", // 23:59:59.999 - 7 hours = 16:59:59.999
3063+
"00:00:00.000", // 07:00:00.000 - 7 hours = 00:00:00.000 (mid night)
3064+
"13:00:00.000" // 20:00:00.000 - 7 hours = 12:00:00.000
3065+
});
3066+
3067+
assertEqualVectors(expected, result);
3068+
}
3069+
3070+
{
3071+
// Not In Daylight savings time at session start time below.
3072+
setSessionStartTimeAndTimeZone(
3073+
1762761600000, "America/Los_Angeles"); // 025-11-10T00:00:00.000
3074+
3075+
auto result = evaluate<FlatVector<StringView>>(
3076+
"cast(c0 as varchar)", makeRowVector({timeVector}));
3077+
3078+
auto expected = makeFlatVector<StringView>({
3079+
"16:00:00.000", // 00:00:00.000 - 8 hours = 16:00:00.000 (previous
3080+
// day)
3081+
"17:01:01.000", // 01:01:01.000 - 8 hours = 17:01:01.000 (previous
3082+
// day)
3083+
"04:00:00.000", // 12:00:00.000 - 8 hours = 04:00:00.000
3084+
"15:59:59.999", // 23:59:59.999 - 8 hours = 15:59:59.999
3085+
"23:00:00.000", // 07:00:00.000 - 8 hours = 23:00:00.000 (previous
3086+
// day)
3087+
"12:00:00.000" // 20:00:00.000 - 8 hours = 12:00:00.000
3088+
});
3089+
3090+
assertEqualVectors(expected, result);
3091+
}
3092+
3093+
{
3094+
// Try this again with a different timezone
3095+
setTimezone("Australia/Perth");
3096+
3097+
auto result = evaluate<FlatVector<StringView>>(
3098+
"cast(c0 as varchar)", makeRowVector({timeVector}));
3099+
3100+
// Perth is always UTC+8 (no daylight saving time)
3101+
auto expected = makeFlatVector<StringView>({
3102+
"08:00:00.000", // 00:00:00.000 + 8 hours
3103+
"09:01:01.000", // 01:01:01.000 + 8 hours
3104+
"20:00:00.000", // 12:00:00.000 + 8 hours
3105+
"07:59:59.999", // 23:59:59.999 + 8 hours (wraps around)
3106+
"15:00:00.000", // 07:00:00.000 + 8 hours
3107+
"04:00:00.000" // 20:00:00.000 + 8 hours (wraps around)
3108+
});
3109+
3110+
assertEqualVectors(expected, result);
3111+
}
3112+
}
3113+
3114+
{
3115+
// Test during daylight saving time , March 10, 2024 09:00:00 AM UTC
3116+
// Spring forward. The clock jumps forward 1 hour, from 2:00 AM to 3:00 AM.
3117+
setSessionStartTimeAndTimeZone(1710061200000, "America/Los_Angeles");
3118+
3119+
auto timeVector = makeFlatVector<int64_t>(
3120+
{
3121+
0, // 00:00:00.000 UTC -> should adjust for timezone
3122+
3661000, // 01:01:01.000 UTC -> should adjust for timezone
3123+
43200000, // 12:00:00.000 UTC -> should adjust for timezone
3124+
86399999, // 23:59:59.999 UTC -> should adjust for timezone
3125+
25200000, // 07:00:00.000 UTC -> should adjust for timezone
3126+
72000000 // 20:00:00.000 UTC -> should adjust for timezone
3127+
},
3128+
TIME());
3129+
3130+
auto result = evaluate<FlatVector<StringView>>(
3131+
"cast(c0 as varchar)", makeRowVector({timeVector}));
3132+
3133+
auto expected = makeFlatVector<StringView>({
3134+
"16:00:00.000", // 00:00:00.000 - 8 hours = 16:00:00.000 (previous
3135+
// day)
3136+
"17:01:01.000", // 01:01:01.000 - 8 hours = 17:01:01.000 (previous
3137+
// day)
3138+
"05:00:00.000", // 12:00:00.000 - 7 hours = 05:00:00.000
3139+
"16:59:59.999", // 23:59:59.999 - 7 hours = 16:59:59.999
3140+
"23:00:00.000", // 07:00:00.000 - 8 hours = 23:00:00.000 (previous
3141+
// day)
3142+
"13:00:00.000" // 20:00:00.000 - 7 hours = 13:00:00.000
3143+
});
3144+
3145+
assertEqualVectors(expected, result);
3146+
3147+
// Fall Back. The clock jumps back 1 hour, from 2:00 AM to 1:00 AM.
3148+
setSessionStartTimeAndTimeZone(1730620800000, "America/Los_Angeles");
3149+
3150+
result = evaluate<FlatVector<StringView>>(
3151+
"cast(c0 as varchar)", makeRowVector({timeVector}));
3152+
3153+
expected = makeFlatVector<StringView>({
3154+
"17:00:00.000", // 00:00:00.000 - 7 hours = 17:00:00.000 (previous
3155+
// day)
3156+
"18:01:01.000", // 01:01:01.000 - 7 hours = 18:01:01.000 (previous
3157+
// day)
3158+
"04:00:00.000", // 12:00:00.000 - 8 hours = 04:00:00.000
3159+
"15:59:59.999", // 23:59:59.999 - 8 hours = 15:59:59.999
3160+
"00:00:00.000", // 07:00:00.000 - 7 hours = 00:00:00.000 (previous
3161+
// day)
3162+
"12:00:00.000" // 20:00:00.000 - 8 hours = 12:00:00.000
3163+
});
3164+
3165+
assertEqualVectors(expected, result);
3166+
}
3167+
}
3168+
29783169
} // namespace
29793170
} // namespace facebook::velox::test

velox/functions/prestosql/tests/utils/FunctionBaseTest.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ class FunctionBaseTest : public testing::Test,
6161
});
6262
}
6363

64+
void setSessionStartTimeAndTimeZone(
65+
const int64_t sessionStartTimeMs,
66+
const std::string& timeZoneName) {
67+
queryCtx_->testingOverrideConfigUnsafe({
68+
{core::QueryConfig::kSessionStartTime,
69+
std::to_string(sessionStartTimeMs)},
70+
{core::QueryConfig::kSessionTimezone, timeZoneName},
71+
{core::QueryConfig::kAdjustTimestampToTimezone, "true"},
72+
});
73+
}
74+
6475
protected:
6576
static void SetUpTestCase();
6677

velox/type/Type.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,34 @@ folly::dynamic TimeType::serialize() const {
15021502
return obj;
15031503
}
15041504

1505+
StringView TimeType::valueToString(int64_t value, char* const startPos) const {
1506+
// Ensure the value is within valid TIME range
1507+
VELOX_USER_CHECK(
1508+
!(value < 0 || value >= 86400000),
1509+
"TIME value {} is out of range [0, 86400000)",
1510+
value);
1511+
1512+
int64_t hours = value / kMillisInHour;
1513+
int64_t remainingMs = value % kMillisInHour;
1514+
int64_t minutes = remainingMs / kMillisInMinute;
1515+
remainingMs = remainingMs % kMillisInMinute;
1516+
int64_t seconds = remainingMs / kMillisInSecond;
1517+
int64_t millis = remainingMs % kMillisInSecond;
1518+
1519+
// TIME is represented as milliseconds since midnight
1520+
// Convert to HH:mm:ss.SSS format
1521+
1522+
fmt::format_to_n(
1523+
startPos,
1524+
kTimeToVarcharRowSize,
1525+
"{:02d}:{:02d}:{:02d}.{:03d}",
1526+
hours,
1527+
minutes,
1528+
seconds,
1529+
millis);
1530+
return StringView{startPos, kTimeToVarcharRowSize};
1531+
}
1532+
15051533
std::string stringifyTruncatedElementList(
15061534
size_t size,
15071535
const std::function<void(std::stringstream&, size_t)>& stringifyElement,

0 commit comments

Comments
 (0)