From a1e54c39185298a4cc3a14d3381f10ab8871db54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=89=AC?= <654010905@qq.com> Date: Wed, 20 Apr 2022 06:11:31 -0500 Subject: [PATCH] Fix wrong result of datetime64 when negative (#35440) --- src/Core/tests/gtest_DecimalFunctions.cpp | 49 +++++++++++++++++++ src/IO/ReadHelpers.h | 9 ++++ src/IO/WriteHelpers.h | 7 +++ .../01277_fromUnixTimestamp64.reference | 4 +- ..._toDateTime_from_string_clamping.reference | 2 +- .../02207_subseconds_intervals.reference | 20 ++++---- .../02242_negative_datetime64.reference | 2 + .../0_stateless/02242_negative_datetime64.sql | 2 + 8 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 tests/queries/0_stateless/02242_negative_datetime64.reference create mode 100644 tests/queries/0_stateless/02242_negative_datetime64.sql diff --git a/src/Core/tests/gtest_DecimalFunctions.cpp b/src/Core/tests/gtest_DecimalFunctions.cpp index 1069a810d64..7517edda937 100644 --- a/src/Core/tests/gtest_DecimalFunctions.cpp +++ b/src/Core/tests/gtest_DecimalFunctions.cpp @@ -117,6 +117,31 @@ TEST_P(DecimalUtilsSplitAndCombineTest, getFractionalPartDecimal128) testGetFractional(GetParam()); } +class DecimalUtilsSplitAndCombineForDateTime64Test : public ::testing::TestWithParam +{}; + + +// Unfortunately typed parametrized tests () are not supported in this version of gtest, so I have to emulate by hand. +TEST_P(DecimalUtilsSplitAndCombineForDateTime64Test, splitDateTime64) +{ + testSplit(GetParam()); +} + +TEST_P(DecimalUtilsSplitAndCombineForDateTime64Test, combineDateTime64) +{ + testDecimalFromComponents(GetParam()); +} + +TEST_P(DecimalUtilsSplitAndCombineForDateTime64Test, getWholePartDateTime64) +{ + testGetWhole(GetParam()); +} + +TEST_P(DecimalUtilsSplitAndCombineForDateTime64Test, getFractionalPartDateTime64) +{ + testGetFractional(GetParam()); +} + } // Intentionally small values that fit into 32-bit in order to cover Decimal32, Decimal64 and Decimal128 with single set of data. @@ -170,3 +195,27 @@ INSTANTIATE_TEST_SUITE_P(Basic, } }) ); + +INSTANTIATE_TEST_SUITE_P(Basic, + DecimalUtilsSplitAndCombineForDateTime64Test, + ::testing::ValuesIn(std::initializer_list{ + { + "Negative timestamp 1965-12-12 12:12:12.123 UTC", + DateTime64(-127943267877), + 3, + { + -127943267, + 877 + } + }, + { + "Positive timestamp 1975-12-12 12:12:12.123 UTC", + DateTime64(187618332123), + 3, + { + 187618332, + 123 + } + } + }) +); diff --git a/src/IO/ReadHelpers.h b/src/IO/ReadHelpers.h index 13228853ff3..c5ffa52c9b3 100644 --- a/src/IO/ReadHelpers.h +++ b/src/IO/ReadHelpers.h @@ -929,6 +929,15 @@ inline ReturnType readDateTimeTextImpl(DateTime64 & datetime64, UInt32 scale, Re /// Ignore digits that are out of precision. while (!buf.eof() && isNumericASCII(*buf.position())) ++buf.position(); + + /// Keep sign of fractional part the same with whole part if datetime64 is negative + /// 1965-12-12 12:12:12.123 => whole = -127914468, fraction = 123(sign>0) -> new whole = -127914467, new fraction = 877(sign<0) + if (components.whole < 0 && components.fractional != 0) + { + const auto scale_multiplier = DecimalUtils::scaleMultiplier(scale); + ++components.whole; + components.fractional = scale_multiplier - components.fractional; + } } /// 9908870400 is time_t value for 2184-01-01 UTC (a bit over the last year supported by DateTime64) else if (whole >= 9908870400LL) diff --git a/src/IO/WriteHelpers.h b/src/IO/WriteHelpers.h index f72213f0d11..8547a0af1cd 100644 --- a/src/IO/WriteHelpers.h +++ b/src/IO/WriteHelpers.h @@ -805,6 +805,13 @@ inline void writeDateTimeText(DateTime64 datetime64, UInt32 scale, WriteBuffer & scale = scale > MaxScale ? MaxScale : scale; auto components = DecimalUtils::split(datetime64, scale); + /// -127914467.877 => whole = -127914467, fraction = 877 => new whole = -127914468(1965-12-12 12:12:12), new fraction = 123(.123) => 1965-12-12 12:12:12.123 + if (components.whole < 0 && components.fractional != 0) + { + --components.whole; + components.fractional = DecimalUtils::scaleMultiplier(scale) - components.fractional; + } + writeDateTimeText(LocalDateTime(components.whole, time_zone), buf); if (scale > 0) diff --git a/tests/queries/0_stateless/01277_fromUnixTimestamp64.reference b/tests/queries/0_stateless/01277_fromUnixTimestamp64.reference index 8c951058ea6..a9ffd259af0 100644 --- a/tests/queries/0_stateless/01277_fromUnixTimestamp64.reference +++ b/tests/queries/0_stateless/01277_fromUnixTimestamp64.reference @@ -4,6 +4,6 @@ Asia/Makassar 1234567891011 2009-02-14 07:31:31.011 1970-01-15 14:56:07.891011 1 non-const column 1234567891011 2009-02-13 23:31:31.011 1970-01-15 06:56:07.891011 1970-01-01 00:20:34.567891011 upper range bound -9904447342 2283-11-10 19:22:22.123 2283-11-10 19:22:22.123456 1925-01-01 00:00:00.586094827 +9904447342 2283-11-10 19:22:22.123 2283-11-10 19:22:22.123456 1925-01-01 00:00:00.413905173 lower range bound --1420066799 1925-01-01 01:00:01.123 1925-01-01 01:00:01.123456 1925-01-01 01:00:01.123456789 +-1420066799 1925-01-01 01:00:00.877 1925-01-01 01:00:00.876544 1925-01-01 01:00:00.876543211 diff --git a/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.reference b/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.reference index c5e86963f22..14123414873 100644 --- a/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.reference +++ b/tests/queries/0_stateless/01702_toDateTime_from_string_clamping.reference @@ -1,4 +1,4 @@ 1940-10-09 22:13:17.6 2283-11-11 23:46:43.6 2283-11-11 23:46:40.1 -1925-01-01 00:00:00.1 +1925-01-01 00:00:00.9 diff --git a/tests/queries/0_stateless/02207_subseconds_intervals.reference b/tests/queries/0_stateless/02207_subseconds_intervals.reference index f7b91ff48b8..f2e40137851 100644 --- a/tests/queries/0_stateless/02207_subseconds_intervals.reference +++ b/tests/queries/0_stateless/02207_subseconds_intervals.reference @@ -14,7 +14,7 @@ test intervals 1980-12-12 12:12:12.123456 1930-12-12 12:12:12.123456 1930-12-12 12:12:12.123400 -1930-12-12 12:12:12.123457 +1930-12-12 12:12:12.123456 2220-12-12 12:12:12.123456 2220-12-12 12:12:12.123400 2220-12-12 12:12:12.123456 @@ -25,7 +25,7 @@ test intervals 1980-12-12 12:12:12.123 1930-12-12 12:12:12.123 1930-12-12 12:12:12.120 -1930-12-12 12:12:12.124 +1930-12-12 12:12:12.123 2220-12-12 12:12:12.123 2220-12-12 12:12:12.120 2220-12-12 12:12:12.123 @@ -34,8 +34,8 @@ test add[...]seconds() 1980-12-12 12:12:12.123456790 1980-12-12 12:12:12.123456701 1980-12-12 12:12:12.123456790 -1930-12-12 12:12:12.123456788 -1930-12-12 12:12:12.123456699 +1930-12-12 12:12:12.123456790 +1930-12-12 12:12:12.123456701 2220-12-12 12:12:12.123456790 2220-12-12 12:12:12.123456701 - test microseconds @@ -43,9 +43,9 @@ test add[...]seconds() 1980-12-12 12:12:12.123401 1980-12-12 12:12:12.12345778 1980-12-12 12:12:12.123457 -1930-12-12 12:12:12.123455 -1930-12-12 12:12:12.123399 -1930-12-12 12:12:12.12345578 +1930-12-12 12:12:12.123457 +1930-12-12 12:12:12.123401 +1930-12-12 12:12:12.12345778 2220-12-12 12:12:12.123457 2220-12-12 12:12:12.123401 2220-12-12 12:12:12.12345778 @@ -54,9 +54,9 @@ test add[...]seconds() 1980-12-12 12:12:12.121 1980-12-12 12:12:12.124456 1980-12-12 12:12:12.124 -1930-12-12 12:12:12.122 -1930-12-12 12:12:12.119 -1930-12-12 12:12:12.122456 +1930-12-12 12:12:12.124 +1930-12-12 12:12:12.121 +1930-12-12 12:12:12.124456 2220-12-12 12:12:12.124 2220-12-12 12:12:12.121 2220-12-12 12:12:12.124456 diff --git a/tests/queries/0_stateless/02242_negative_datetime64.reference b/tests/queries/0_stateless/02242_negative_datetime64.reference new file mode 100644 index 00000000000..7f14679ac56 --- /dev/null +++ b/tests/queries/0_stateless/02242_negative_datetime64.reference @@ -0,0 +1,2 @@ +-127914467.877 +187618332.123 diff --git a/tests/queries/0_stateless/02242_negative_datetime64.sql b/tests/queries/0_stateless/02242_negative_datetime64.sql new file mode 100644 index 00000000000..32086188608 --- /dev/null +++ b/tests/queries/0_stateless/02242_negative_datetime64.sql @@ -0,0 +1,2 @@ +SELECT cast(toDateTime64('1965-12-12 12:12:12.123', 3, 'UTC') as Decimal64(3)); +SELECT cast(toDateTime64('1975-12-12 12:12:12.123', 3, 'UTC') as Decimal64(3));