From 8404d66243589de0f404c153c9e8cfb7b1586e4b Mon Sep 17 00:00:00 2001 From: svm1 Date: Mon, 4 Mar 2024 18:07:07 -0800 Subject: [PATCH] implement to_iso8601() for timestamp input --- velox/docs/functions/presto/datetime.rst | 3 ++ velox/functions/prestosql/DateTimeFunctions.h | 43 +++++++++++++++++ .../DateTimeFunctionsRegistration.cpp | 2 + .../prestosql/tests/DateTimeFunctionsTest.cpp | 47 +++++++++++++++++++ velox/type/Timestamp.cpp | 6 ++- 5 files changed, 99 insertions(+), 2 deletions(-) diff --git a/velox/docs/functions/presto/datetime.rst b/velox/docs/functions/presto/datetime.rst index 682c7d38fdb3f..e6333175d78ad 100644 --- a/velox/docs/functions/presto/datetime.rst +++ b/velox/docs/functions/presto/datetime.rst @@ -106,6 +106,9 @@ Date and Time Functions Returns ``timestamp`` as a UNIX timestamp. +.. function:: to_iso8601(x) -> varchar + + Formats timestamp ``x`` as an ISO 8601 string. Truncation Function ------------------- diff --git a/velox/functions/prestosql/DateTimeFunctions.h b/velox/functions/prestosql/DateTimeFunctions.h index a141aa050322f..7bdbed6aedeb6 100644 --- a/velox/functions/prestosql/DateTimeFunctions.h +++ b/velox/functions/prestosql/DateTimeFunctions.h @@ -15,7 +15,13 @@ */ #pragma once +#include +#include +#include +#include #include +#include "velox/external/date/date.h" +#include "velox/external/date/tz.h" #include "velox/functions/lib/DateTimeFormatter.h" #include "velox/functions/lib/TimeUtils.h" #include "velox/functions/prestosql/DateTimeImpl.h" @@ -1399,4 +1405,41 @@ struct TimeZoneMinuteFunction : public TimestampWithTimezoneSupport { } }; +template +struct ToISO8601FromTimestampFunction { + VELOX_DEFINE_FUNCTION_TYPES(T); + + std::string sessionTzName = ""; + const date::time_zone* sessionTimezone = nullptr; + + FOLLY_ALWAYS_INLINE void initialize( + const core::QueryConfig& config, + const arg_type* timestamp) { + sessionTimezone = getTimeZoneFromConfig(config); + sessionTzName = (sessionTimezone != NULL ? sessionTimezone->name() : NULL); + } + + FOLLY_ALWAYS_INLINE void call( + out_type& result, + const arg_type& timestamp) { + std::string tzOffsetStr = ""; + Timestamp ts = timestamp; + TimestampToStringOptions options; + options.mode = TimestampToStringOptions::Mode::kFull; + options.zeroPaddingYear = true; + options.precision = TimestampToStringOptions::Precision::kMilliseconds; + auto tsStr = ts.toString(options); + + if (sessionTzName == "UTC") { + tzOffsetStr = " UTC"; + } else if (sessionTimezone != NULL) { + auto formatStr = "%Ez"; + date::local_time tp{ + std::chrono::nanoseconds{timestamp.toNanos()}}; + tzOffsetStr = format(formatStr, date::zoned_time{sessionTzName, tp}); + } + result = tsStr + tzOffsetStr; + } +}; + } // namespace facebook::velox::functions diff --git a/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp b/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp index 5b82a4a83adf1..c336ab24def38 100644 --- a/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp +++ b/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp @@ -175,6 +175,8 @@ void registerSimpleFunctions(const std::string& prefix) { registerFunction( {prefix + "date_parse"}); registerFunction({prefix + "current_date"}); + registerFunction( + {prefix + "to_iso8601"}); } } // namespace diff --git a/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp b/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp index 2b991032eec49..f8775dddf1526 100644 --- a/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp +++ b/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp @@ -3836,3 +3836,50 @@ TEST_F(DateTimeFunctionsTest, fromUnixtimeDouble) { }); assertEqualVectors(expected, actual); } + +TEST_F(DateTimeFunctionsTest, toISO8601TestTimestamp) { + const auto toISO8601 = [&](std::optional timestamp) { + return evaluateOnce("to_iso8601(c0)", timestamp); + }; + + auto getTimestamp = [](const std::string& timestampStr) { + return util::fromTimestampString(StringView{timestampStr}); + }; + + setQueryTimeZone("UTC"); + + EXPECT_EQ(std::nullopt, toISO8601(std::nullopt)); + + EXPECT_EQ( + "2017-07-17T19:54:57.828 UTC", + toISO8601(Timestamp(1500321297, 827910000))); + + setQueryTimeZone("America/Denver"); + + EXPECT_EQ( + "2017-07-17T19:54:57.828-06:00", + toISO8601(Timestamp(1500321297, 827910000))); + + EXPECT_EQ( + "2017-07-17T19:54:57.820-06:00", + toISO8601(Timestamp(1500321297, 820000000))); + + setQueryTimeZone("Australia/Adelaide"); + + EXPECT_EQ( + "2017-07-17T19:54:57.127+09:30", + toISO8601(Timestamp(1500321297, 126548219))); + + EXPECT_EQ( + "2017-07-17T19:54:57.095+09:30", + toISO8601(Timestamp(1500321297, 95421806))); + + EXPECT_EQ( + "1970-01-01T12:00:12.002+09:30", toISO8601(Timestamp(43212, 1599247))); + + setQueryTimeZone("America/Los_Angeles"); + + EXPECT_EQ( + "1880-10-30T06:21:47.001-07:52", + toISO8601(Timestamp(-2813938693, 818583))); +} diff --git a/velox/type/Timestamp.cpp b/velox/type/Timestamp.cpp index 9e1ab503446ea..dd9c267abdbef 100644 --- a/velox/type/Timestamp.cpp +++ b/velox/type/Timestamp.cpp @@ -255,6 +255,8 @@ std::string Timestamp::tmToString( const TimestampToStringOptions& options) { VELOX_DCHECK_GE(nanos, 0); VELOX_DCHECK_LT(nanos, 1'000'000'000); + double kNanosecondsInMillisecond = 1'000'000; + double kNanosecondsInMicrosecond = 1'000; auto precisionWidth = static_cast(options.precision); std::string out; out.reserve(getCapacity(options)); @@ -298,10 +300,10 @@ std::string Timestamp::tmToString( out += ':'; appendSmallInt(tmValue.tm_sec, out); if (options.precision == TimestampToStringOptions::Precision::kMilliseconds) { - nanos /= 1'000'000; + nanos = round(nanos / kNanosecondsInMillisecond); } else if ( options.precision == TimestampToStringOptions::Precision::kMicroseconds) { - nanos /= 1'000; + nanos = round(nanos / kNanosecondsInMicrosecond); } if (options.skipTrailingZeros && nanos == 0) { return out;