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

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new 7b6be90dc fix(c++): fix c++ duration serialization (#3598)
7b6be90dc is described below

commit 7b6be90dc39119247589f8df53fe2e2d29590521
Author: Shawn Yang <[email protected]>
AuthorDate: Tue Apr 21 16:18:24 2026 +0800

    fix(c++): fix c++ duration serialization (#3598)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence of the final clean AI review results
    from both fresh reviewers on the current PR diff or current HEAD after
    the latest code changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 cpp/fory/serialization/serialization_test.cc  | 80 +++++++++++++++++++++++++++
 cpp/fory/serialization/skip.cc                |  9 ++-
 cpp/fory/serialization/temporal_serializers.h | 19 +++++--
 3 files changed, 100 insertions(+), 8 deletions(-)

diff --git a/cpp/fory/serialization/serialization_test.cc 
b/cpp/fory/serialization/serialization_test.cc
index d5ec5a86e..479fcd038 100644
--- a/cpp/fory/serialization/serialization_test.cc
+++ b/cpp/fory/serialization/serialization_test.cc
@@ -19,8 +19,10 @@
 
 #include "fory/serialization/fory.h"
 #include "fory/serialization/ref_resolver.h"
+#include "fory/serialization/skip.h"
 #include "gtest/gtest.h"
 #include <atomic>
+#include <chrono>
 #include <cstdint>
 #include <cstring>
 #include <map>
@@ -98,6 +100,11 @@ inline void register_test_types(Fory &fory) {
   fory.register_enum<OldStatus>(type_id++);
 }
 
+inline std::vector<uint8_t> buffer_bytes(Buffer &buffer) {
+  return std::vector<uint8_t>(buffer.data(),
+                              buffer.data() + buffer.writer_index());
+}
+
 template <typename T>
 void test_roundtrip(const T &original, bool should_equal = true) {
   auto fory = Fory::builder().xlang(true).track_ref(false).build();
@@ -182,6 +189,79 @@ TEST(SerializationTest, StringRoundtrip) {
   test_roundtrip(std::string("UTF-8: 你好世界"));
 }
 
+TEST(SerializationTest, DurationRoundtrip) {
+  auto fory = Fory::builder().xlang(true).track_ref(false).build();
+  std::vector<Duration> values = {
+      Duration(0),
+      std::chrono::seconds(12) + Duration(345678901),
+      -std::chrono::seconds(7) - std::chrono::milliseconds(45) - Duration(67),
+      Duration(-1),
+  };
+
+  for (const Duration &original : values) {
+    auto serialize_result = fory.serialize(original);
+    ASSERT_TRUE(serialize_result.ok())
+        << "Serialization failed: " << serialize_result.error().to_string();
+
+    std::vector<uint8_t> bytes = std::move(serialize_result).value();
+    auto deserialize_result =
+        fory.deserialize<Duration>(bytes.data(), bytes.size());
+    ASSERT_TRUE(deserialize_result.ok())
+        << "Deserialization failed: " << 
deserialize_result.error().to_string();
+    EXPECT_EQ(deserialize_result.value(), original);
+  }
+}
+
+TEST(SerializationTest, DurationUsesSecondsAndNanosecondsPayload) {
+  struct TestCase {
+    Duration value;
+    int64_t expected_seconds;
+    int32_t expected_nanos;
+  };
+
+  auto fory = Fory::builder().xlang(true).track_ref(false).build();
+  std::vector<TestCase> cases = {
+      {Duration(1234567890), 1, 234567890},
+      {Duration(-1234567890), -1, -234567890},
+      {Duration(-1), 0, -1},
+  };
+
+  for (const TestCase &test_case : cases) {
+    WriteContext write_ctx(fory.config(), fory.type_resolver().clone());
+    Serializer<Duration>::write_data(test_case.value, write_ctx);
+    ASSERT_FALSE(write_ctx.has_error()) << write_ctx.error().to_string();
+
+    Buffer expected;
+    expected.write_var_int64(test_case.expected_seconds);
+    expected.write_int32(test_case.expected_nanos);
+    EXPECT_EQ(buffer_bytes(write_ctx.buffer()), buffer_bytes(expected));
+
+    ReadContext read_ctx(fory.config(), fory.type_resolver().clone());
+    read_ctx.attach(write_ctx.buffer());
+    Duration decoded = Serializer<Duration>::read_data(read_ctx);
+    ASSERT_FALSE(read_ctx.has_error()) << read_ctx.error().to_string();
+    EXPECT_EQ(decoded, test_case.value);
+    EXPECT_EQ(read_ctx.buffer().reader_index(),
+              write_ctx.buffer().writer_index());
+  }
+}
+
+TEST(SerializationTest, DurationSkipConsumesSecondsAndNanosecondsPayload) {
+  auto fory = Fory::builder().xlang(true).track_ref(false).build();
+  WriteContext write_ctx(fory.config(), fory.type_resolver().clone());
+  Serializer<Duration>::write_data(Duration(-1), write_ctx);
+  ASSERT_FALSE(write_ctx.has_error()) << write_ctx.error().to_string();
+
+  ReadContext read_ctx(fory.config(), fory.type_resolver().clone());
+  read_ctx.attach(write_ctx.buffer());
+  skip_field_value(read_ctx,
+                   FieldType(static_cast<uint32_t>(TypeId::DURATION), false),
+                   RefMode::None);
+  ASSERT_FALSE(read_ctx.has_error()) << read_ctx.error().to_string();
+  EXPECT_EQ(read_ctx.buffer().reader_index(),
+            write_ctx.buffer().writer_index());
+}
+
 // ============================================================================
 // Character Type Tests (C++ native only)
 // ============================================================================
diff --git a/cpp/fory/serialization/skip.cc b/cpp/fory/serialization/skip.cc
index 15e8bbb94..99c0b1bee 100644
--- a/cpp/fory/serialization/skip.cc
+++ b/cpp/fory/serialization/skip.cc
@@ -549,8 +549,13 @@ void skip_field_value(ReadContext &ctx, const FieldType 
&field_type,
     return;
 
   case TypeId::DURATION: {
-    // Duration is stored as fixed 8-byte nanosecond count.
-    constexpr uint32_t k_bytes = static_cast<uint32_t>(sizeof(int64_t));
+    // Duration is stored as signed varint64 seconds + signed int32
+    // nanoseconds.
+    skip_varint(ctx);
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return;
+    }
+    constexpr uint32_t k_bytes = static_cast<uint32_t>(sizeof(int32_t));
     ctx.buffer().increase_reader_index(k_bytes, ctx.error());
     return;
   }
diff --git a/cpp/fory/serialization/temporal_serializers.h 
b/cpp/fory/serialization/temporal_serializers.h
index 046599ae8..ac167ff11 100644
--- a/cpp/fory/serialization/temporal_serializers.h
+++ b/cpp/fory/serialization/temporal_serializers.h
@@ -55,7 +55,8 @@ struct Date {
 // ============================================================================
 
 /// Serializer for Duration (std::chrono::nanoseconds)
-/// Per xlang spec: serialized as int64 nanosecond count
+/// Per xlang spec: serialized as signed varint64 seconds + signed int32
+/// nanoseconds
 template <> struct Serializer<Duration> {
   static constexpr TypeId type_id = TypeId::DURATION;
 
@@ -85,8 +86,10 @@ template <> struct Serializer<Duration> {
   }
 
   static inline void write_data(const Duration &duration, WriteContext &ctx) {
-    int64_t nanos = duration.count();
-    ctx.write_bytes(&nanos, sizeof(int64_t));
+    auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration);
+    auto remainder = duration - seconds;
+    ctx.write_var_int64(seconds.count());
+    ctx.buffer().write_int32(static_cast<int32_t>(remainder.count()));
   }
 
   static inline void write_data_generic(const Duration &duration,
@@ -115,9 +118,13 @@ template <> struct Serializer<Duration> {
   }
 
   static inline Duration read_data(ReadContext &ctx) {
-    int64_t nanos;
-    ctx.read_bytes(&nanos, sizeof(int64_t), ctx.error());
-    return Duration(nanos);
+    int64_t seconds = ctx.read_var_int64(ctx.error());
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return Duration(0);
+    }
+    int32_t nanos = ctx.read_int32(ctx.error());
+    return std::chrono::duration_cast<Duration>(std::chrono::seconds(seconds)) 
+
+           Duration(nanos);
   }
 
   static inline Duration read_with_type_info(ReadContext &ctx, RefMode 
ref_mode,


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to