This is an automated email from the ASF dual-hosted git repository.

gangwu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new 033fa2d  test: construct temporal values from structural inputs (#267)
033fa2d is described below

commit 033fa2d2abd2b85c7f266bb9c4ee17fe980bb06d
Author: Junwang Zhao <[email protected]>
AuthorDate: Tue Oct 21 23:41:57 2025 +0800

    test: construct temporal values from structural inputs (#267)
---
 src/iceberg/test/bucket_util_test.cc    |  59 +++++++++++----
 src/iceberg/test/temporal_test_helper.h | 128 ++++++++++++++++++++++++++++++++
 src/iceberg/test/transform_test.cc      | 121 +++++++++++++++++++++---------
 src/iceberg/util/temporal_util.cc       |   1 +
 4 files changed, 258 insertions(+), 51 deletions(-)

diff --git a/src/iceberg/test/bucket_util_test.cc 
b/src/iceberg/test/bucket_util_test.cc
index 69a04ef..8c80f04 100644
--- a/src/iceberg/test/bucket_util_test.cc
+++ b/src/iceberg/test/bucket_util_test.cc
@@ -25,6 +25,7 @@
 
 #include "iceberg/util/decimal.h"
 #include "iceberg/util/uuid.h"
+#include "temporal_test_helper.h"
 
 namespace iceberg {
 
@@ -41,27 +42,55 @@ TEST(BucketUtilsTest, HashHelper) {
   EXPECT_EQ(BucketUtils::HashBytes(decimal->ToBigEndian()), -500754589);
 
   // date hash
-  std::chrono::sys_days sd = std::chrono::year{2017} / 11 / 16;
-  std::chrono::sys_days epoch{std::chrono::year{1970} / 1 / 1};
-  int32_t days = (sd - epoch).count();
-  EXPECT_EQ(BucketUtils::HashInt(days), -653330422);
+  EXPECT_EQ(BucketUtils::HashInt(
+                TemporalTestHelper::CreateDate({.year = 2017, .month = 11, 
.day = 16})),
+            -653330422);
 
   // time
-  // 22:31:08 in microseconds
-  int64_t time_micros = (22 * 3600 + 31 * 60 + 8) * 1000000LL;
-  EXPECT_EQ(BucketUtils::HashLong(time_micros), -662762989);
+  EXPECT_EQ(BucketUtils::HashLong(
+                TemporalTestHelper::CreateTime({.hour = 22, .minute = 31, 
.second = 8})),
+            -662762989);
 
   // timestamp
   // 2017-11-16T22:31:08 in microseconds
-  std::chrono::system_clock::time_point tp =
-      std::chrono::sys_days{std::chrono::year{2017} / 11 / 16} + 
std::chrono::hours{22} +
-      std::chrono::minutes{31} + std::chrono::seconds{8};
-  int64_t timestamp_micros =
-      
std::chrono::duration_cast<std::chrono::microseconds>(tp.time_since_epoch())
-          .count();
-  EXPECT_EQ(BucketUtils::HashLong(timestamp_micros), -2047944441);
+  EXPECT_EQ(
+      BucketUtils::HashLong(TemporalTestHelper::CreateTimestamp(
+          {.year = 2017, .month = 11, .day = 16, .hour = 22, .minute = 31, 
.second = 8})),
+      -2047944441);
+
   // 2017-11-16T22:31:08.000001 in microseconds
-  EXPECT_EQ(BucketUtils::HashLong(timestamp_micros + 1), -1207196810);
+  EXPECT_EQ(
+      BucketUtils::HashLong(TemporalTestHelper::CreateTimestamp({.year = 2017,
+                                                                 .month = 11,
+                                                                 .day = 16,
+                                                                 .hour = 22,
+                                                                 .minute = 31,
+                                                                 .second = 8,
+                                                                 .microsecond 
= 1})),
+      -1207196810);
+
+  // 2017-11-16T14:31:08-08:00 in microseconds
+  EXPECT_EQ(BucketUtils::HashLong(
+                TemporalTestHelper::CreateTimestampTz({.year = 2017,
+                                                       .month = 11,
+                                                       .day = 16,
+                                                       .hour = 14,
+                                                       .minute = 31,
+                                                       .second = 8,
+                                                       .tz_offset_minutes = 
-480})),
+            -2047944441);
+
+  // 2017-11-16T14:31:08.000001-08:00 in microseconds
+  EXPECT_EQ(BucketUtils::HashLong(
+                TemporalTestHelper::CreateTimestampTz({.year = 2017,
+                                                       .month = 11,
+                                                       .day = 16,
+                                                       .hour = 14,
+                                                       .minute = 31,
+                                                       .second = 8,
+                                                       .microsecond = 1,
+                                                       .tz_offset_minutes = 
-480})),
+            -1207196810);
 
   // string
   std::string str = "iceberg";
diff --git a/src/iceberg/test/temporal_test_helper.h 
b/src/iceberg/test/temporal_test_helper.h
new file mode 100644
index 0000000..0f29048
--- /dev/null
+++ b/src/iceberg/test/temporal_test_helper.h
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <cstdint>
+
+namespace iceberg {
+
+using namespace std::chrono;  // NOLINT
+
+struct DateParts {
+  int32_t year{0};
+  uint8_t month{0};
+  uint8_t day{0};
+};
+
+struct TimeParts {
+  int32_t hour{0};
+  int32_t minute{0};
+  int32_t second{0};
+  int32_t microsecond{0};
+};
+
+struct TimestampParts {
+  int32_t year{0};
+  uint8_t month{0};
+  uint8_t day{0};
+  int32_t hour{0};
+  int32_t minute{0};
+  int32_t second{0};
+  int32_t microsecond{0};
+  // e.g. -480 for PST (UTC-8:00), +480 for Asia/Shanghai (UTC+8:00)
+  int32_t tz_offset_minutes{0};
+};
+
+struct TimestampNanosParts {
+  int32_t year{0};
+  uint8_t month{0};
+  uint8_t day{0};
+  int32_t hour{0};
+  int32_t minute{0};
+  int32_t second{0};
+  int32_t nanosecond{0};
+  // e.g. -480 for PST (UTC-8:00), +480 for Asia/Shanghai (UTC+8:00)
+  int32_t tz_offset_minutes{0};
+};
+
+class TemporalTestHelper {
+  static constexpr auto kEpochDays = sys_days(year{1970} / January / 1);
+
+ public:
+  /// \brief Construct a Calendar date without timezone or time
+  static int32_t CreateDate(const DateParts& parts) {
+    return static_cast<int32_t>(
+        (sys_days(year{parts.year} / month{parts.month} / day{parts.day}) - 
kEpochDays)
+            .count());
+  }
+
+  /// \brief Construct a time-of-day, microsecond precision, without date, 
timezone
+  static int64_t CreateTime(const TimeParts& parts) {
+    return duration_cast<microseconds>(hours(parts.hour) + 
minutes(parts.minute) +
+                                       seconds(parts.second) +
+                                       microseconds(parts.microsecond))
+        .count();
+  }
+
+  /// \brief Construct a timestamp, microsecond precision, without timezone
+  static int64_t CreateTimestamp(const TimestampParts& parts) {
+    year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
+    auto tp = sys_time<microseconds>{(sys_days(ymd) + hours{parts.hour} +
+                                      minutes{parts.minute} + 
seconds{parts.second} +
+                                      microseconds{parts.microsecond})
+                                         .time_since_epoch()};
+    return tp.time_since_epoch().count();
+  }
+
+  /// \brief Construct a timestamp, microsecond precision, with timezone
+  static int64_t CreateTimestampTz(const TimestampParts& parts) {
+    year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
+    auto tp = sys_time<microseconds>{(sys_days(ymd) + hours{parts.hour} +
+                                      minutes{parts.minute} + 
seconds{parts.second} +
+                                      microseconds{parts.microsecond} -
+                                      minutes{parts.tz_offset_minutes})
+                                         .time_since_epoch()};
+    return tp.time_since_epoch().count();
+  }
+
+  /// \brief Construct a timestamp, nanosecond precision, without timezone
+  static int64_t CreateTimestampNanos(const TimestampNanosParts& parts) {
+    year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
+    auto tp =
+        sys_time<nanoseconds>{(sys_days(ymd) + hours{parts.hour} + 
minutes{parts.minute} +
+                               seconds{parts.second} + 
nanoseconds{parts.nanosecond})
+                                  .time_since_epoch()};
+    return tp.time_since_epoch().count();
+  }
+
+  /// \brief Construct a timestamp, nanosecond precision, with timezone
+  static int64_t CreateTimestampTzNanos(const TimestampNanosParts& parts) {
+    year_month_day ymd{year{parts.year}, month{parts.month}, day{parts.day}};
+    auto tp =
+        sys_time<nanoseconds>{(sys_days(ymd) + hours{parts.hour} + 
minutes{parts.minute} +
+                               seconds{parts.second} + 
nanoseconds{parts.nanosecond} -
+                               minutes{parts.tz_offset_minutes})
+                                  .time_since_epoch()};
+    return tp.time_since_epoch().count();
+  }
+};
+
+}  // namespace iceberg
diff --git a/src/iceberg/test/transform_test.cc 
b/src/iceberg/test/transform_test.cc
index 1003b95..6d72bdc 100644
--- a/src/iceberg/test/transform_test.cc
+++ b/src/iceberg/test/transform_test.cc
@@ -27,10 +27,10 @@
 #include <gtest/gtest.h>
 
 #include "iceberg/expression/literal.h"
-#include "iceberg/transform_function.h"
 #include "iceberg/type.h"
 #include "iceberg/util/formatter.h"  // IWYU pragma: keep
 #include "matchers.h"
+#include "temporal_test_helper.h"
 
 namespace iceberg {
 
@@ -315,25 +315,40 @@ INSTANTIATE_TEST_SUITE_P(
                        .source = Literal::Decimal(1420, 4, 2),
                        .expected = Literal::Int(3)},
         TransformParam{.str = "Date",
-                       // 2017-11-16
                        .source_type = iceberg::date(),
-                       .source = Literal::Date(17486),
+                       .source = Literal::Date(TemporalTestHelper::CreateDate(
+                           {.year = 2017, .month = 11, .day = 16})),
                        .expected = Literal::Int(2)},
         TransformParam{.str = "Time",
-                       // 22:31:08 in microseconds
                        .source_type = iceberg::time(),
-                       .source = Literal::Time(81068000000),
+                       .source = Literal::Time(TemporalTestHelper::CreateTime(
+                           {.hour = 22, .minute = 31, .second = 8})),
                        .expected = Literal::Int(3)},
         TransformParam{.str = "Timestamp",
                        // 2017-11-16T22:31:08 in microseconds
                        .source_type = iceberg::timestamp(),
-                       .source = Literal::Timestamp(1510871468000000),
+                       .source = Literal::Timestamp(
+                           TemporalTestHelper::CreateTimestamp({.year = 2017,
+                                                                .month = 11,
+                                                                .day = 16,
+                                                                .hour = 22,
+                                                                .minute = 31,
+                                                                .second = 8})),
                        .expected = Literal::Int(3)},
-        TransformParam{.str = "TimestampTz",
-                       // 2017-11-16T22:31:08.000001 in microseconds
-                       .source_type = iceberg::timestamp_tz(),
-                       .source = Literal::TimestampTz(1510871468000001),
-                       .expected = Literal::Int(2)},
+        TransformParam{
+            .str = "TimestampTz",
+            // 2017-11-16T14:31:08.000001-08:00 in microseconds
+            .source_type = iceberg::timestamp_tz(),
+            .source = Literal::TimestampTz(
+                TemporalTestHelper::CreateTimestampTz({.year = 2017,
+                                                       .month = 11,
+                                                       .day = 16,
+                                                       .hour = 14,
+                                                       .minute = 31,
+                                                       .second = 8,
+                                                       .microsecond = 1,
+                                                       .tz_offset_minutes = 
-480})),
+            .expected = Literal::Int(2)},
         TransformParam{.str = "String",
                        .source_type = iceberg::string(),
                        .source = Literal::String("iceberg"),
@@ -428,19 +443,36 @@ TEST_P(YearTransformTest, YearTransform) {
 
 INSTANTIATE_TEST_SUITE_P(
     YearTransformTests, YearTransformTest,
-    ::testing::Values(TransformParam{.str = "Timestamp",
-                                     // 2021-06-01T11:43:20Z
-                                     .source_type = iceberg::timestamp(),
-                                     .source = 
Literal::Timestamp(1622547800000000),
-                                     .expected = Literal::Int(2021)},
-                      TransformParam{.str = "TimestampTz",
-                                     .source_type = iceberg::timestamp_tz(),
-                                     .source = 
Literal::TimestampTz(1622547800000000),
-                                     .expected = Literal::Int(2021)},
-                      TransformParam{.str = "Date",
-                                     .source_type = iceberg::date(),
-                                     .source = Literal::Date(30000),
-                                     .expected = Literal::Int(2052)}),
+    ::testing::Values(
+        TransformParam{.str = "Timestamp",
+                       // 2021-06-01T11:43:20Z
+                       .source_type = iceberg::timestamp(),
+                       .source = Literal::Timestamp(
+                           TemporalTestHelper::CreateTimestamp({.year = 2021,
+                                                                .month = 6,
+                                                                .day = 1,
+                                                                .hour = 11,
+                                                                .minute = 43,
+                                                                .second = 
20})),
+                       .expected = Literal::Int(2021)},
+        TransformParam{
+            .str = "TimestampTz",
+            // 2021-01-01T07:43:20+08:00, which is 2020-12-31T23:43:20Z
+            .source_type = iceberg::timestamp_tz(),
+            .source = Literal::TimestampTz(
+                TemporalTestHelper::CreateTimestampTz({.year = 2021,
+                                                       .month = 1,
+                                                       .day = 1,
+                                                       .hour = 7,
+                                                       .minute = 43,
+                                                       .second = 20,
+                                                       .tz_offset_minutes = 
480})),
+            .expected = Literal::Int(2020)},
+        TransformParam{.str = "Date",
+                       .source_type = iceberg::date(),
+                       .source = Literal::Date(TemporalTestHelper::CreateDate(
+                           {.year = 2052, .month = 2, .day = 20})),
+                       .expected = Literal::Int(2052)}),
     [](const ::testing::TestParamInfo<TransformParam>& info) { return 
info.param.str; });
 
 class MonthTransformTest : public ::testing::TestWithParam<TransformParam> {};
@@ -495,18 +527,35 @@ TEST_P(DayTransformTest, DayTransform) {
 
 INSTANTIATE_TEST_SUITE_P(
     DayTransformTests, DayTransformTest,
-    ::testing::Values(TransformParam{.str = "Timestamp",
-                                     .source_type = iceberg::timestamp(),
-                                     .source = 
Literal::Timestamp(1622547800000000),
-                                     .expected = Literal::Int(18779)},
-                      TransformParam{.str = "TimestampTz",
-                                     .source_type = iceberg::timestamp_tz(),
-                                     .source = 
Literal::TimestampTz(1622547800000000),
-                                     .expected = Literal::Int(18779)},
-                      TransformParam{.str = "Date",
-                                     .source_type = iceberg::date(),
-                                     .source = Literal::Date(30000),
-                                     .expected = Literal::Int(30000)}),
+    ::testing::Values(
+        TransformParam{.str = "Timestamp",
+                       .source_type = iceberg::timestamp(),
+                       .source = Literal::Timestamp(
+                           TemporalTestHelper::CreateTimestamp({.year = 2021,
+                                                                .month = 6,
+                                                                .day = 1,
+                                                                .hour = 11,
+                                                                .minute = 43,
+                                                                .second = 
20})),
+                       .expected = Literal::Int(TemporalTestHelper::CreateDate(
+                           {.year = 2021, .month = 6, .day = 1}))},
+        TransformParam{
+            .str = "TimestampTz",
+            .source_type = iceberg::timestamp_tz(),
+            .source = Literal::TimestampTz(
+                TemporalTestHelper::CreateTimestampTz({.year = 2021,
+                                                       .month = 1,
+                                                       .day = 1,
+                                                       .hour = 7,
+                                                       .minute = 43,
+                                                       .second = 20,
+                                                       .tz_offset_minutes = 
480})),
+            .expected = Literal::Int(
+                TemporalTestHelper::CreateDate({.year = 2020, .month = 12, 
.day = 31}))},
+        TransformParam{.str = "Date",
+                       .source_type = iceberg::date(),
+                       .source = Literal::Date(30000),
+                       .expected = Literal::Int(30000)}),
     [](const ::testing::TestParamInfo<TransformParam>& info) { return 
info.param.str; });
 
 class HourTransformTest : public ::testing::TestWithParam<TransformParam> {};
diff --git a/src/iceberg/util/temporal_util.cc 
b/src/iceberg/util/temporal_util.cc
index 41748c9..0112e49 100644
--- a/src/iceberg/util/temporal_util.cc
+++ b/src/iceberg/util/temporal_util.cc
@@ -20,6 +20,7 @@
 #include "iceberg/util/temporal_util.h"
 
 #include <chrono>
+#include <cstdint>
 #include <utility>
 
 #include "iceberg/expression/literal.h"

Reply via email to