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 4c7f2b86b feat(rust): add duration serializer support (#2878)
4c7f2b86b is described below
commit 4c7f2b86bd25a418396a75c6a5ef1382899fd2cb
Author: Shawn Yang <[email protected]>
AuthorDate: Mon Nov 3 12:03:29 2025 +0800
feat(rust): add duration serializer support (#2878)
## Why?
<!-- Describe the purpose of this PR. -->
## What does this PR do?
add duration serializer support
## Related issues
<!--
Is there any related issue? If this PR closes them you say say
fix/closes:
- #xxxx0
- #xxxx1
- Fixes #xxxx2
-->
## Does this PR introduce any user-facing change?
<!--
If any user-facing interface changes, please [open an
issue](https://github.com/apache/fory/issues/new/choose) describing the
need to do so and update the document if necessary.
Delete section if not applicable.
-->
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
<!--
When the PR has an impact on performance (if you don't know whether the
PR will have an impact on performance, you can submit the PR first, and
if it will have impact on performance, the code reviewer will explain
it), be sure to attach a benchmark data here.
Delete section if not applicable.
-->
---
docs/specification/xlang_serialization_spec.md | 20 ++++
.../apache/fory/serializer/TimeSerializers.java | 13 +++
rust/fory-core/src/serializer/datetime.rs | 113 +++++++++++++++++++++
rust/fory-core/src/serializer/skip.rs | 4 +
4 files changed, 150 insertions(+)
diff --git a/docs/specification/xlang_serialization_spec.md
b/docs/specification/xlang_serialization_spec.md
index 9ab038c6a..297f9b627 100644
--- a/docs/specification/xlang_serialization_spec.md
+++ b/docs/specification/xlang_serialization_spec.md
@@ -597,6 +597,26 @@ Which encoding to choose:
- If the string is encoded by `utf-8`, then fory will use `utf-8` to decode
the data. Cross-language string
serialization of fory uses `utf-8` by default.
+### duration
+
+Duration is an absolute length of time, independent of any calendar/timezone,
as a count of seconds and nanoseconds.
+
+Format:
+
+```
+| signed varint64: seconds | signed int32: nanoseconds |
+```
+
+- `seconds`: Number of seconds in the duration, encoded as a signed varint64.
Can be positive or negative.
+- `nanoseconds`: Nanosecond adjustment to the duration, encoded as a signed
int32. Value range is [0, 999,999,999] for positive durations, and
[-999,999,999, 0] for negative durations.
+
+Notes:
+
+- The duration is stored as two separate fields to maintain precision and
avoid overflow issues.
+- Seconds are encoded using varint64 for compact representation of common
duration values.
+- Nanoseconds are stored as a fixed int32 since the range is limited.
+- The sign of the duration is determined by the seconds field. When seconds is
0, the sign is determined by nanoseconds.
+
### collection/list
Format:
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/TimeSerializers.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/TimeSerializers.java
index f911b51fb..f0b4d99ae 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/TimeSerializers.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/TimeSerializers.java
@@ -301,6 +301,19 @@ public class TimeSerializers {
int nanos = buffer.readInt32();
return Duration.ofSeconds(seconds, nanos);
}
+
+ @Override
+ public void xwrite(MemoryBuffer buffer, Duration value) {
+ buffer.writeVarInt64(value.getSeconds());
+ buffer.writeInt32(value.getNano());
+ }
+
+ @Override
+ public Duration xread(MemoryBuffer buffer) {
+ long seconds = buffer.readVarInt64();
+ int nanos = buffer.readInt32();
+ return Duration.ofSeconds(seconds, nanos);
+ }
}
public static class LocalDateTimeSerializer extends
ImmutableTimeSerializer<LocalDateTime> {
diff --git a/rust/fory-core/src/serializer/datetime.rs
b/rust/fory-core/src/serializer/datetime.rs
index c5b65476f..a0c0f6ae8 100644
--- a/rust/fory-core/src/serializer/datetime.rs
+++ b/rust/fory-core/src/serializer/datetime.rs
@@ -26,8 +26,10 @@ use crate::types::TypeId;
use crate::util::EPOCH;
use chrono::{NaiveDate, NaiveDateTime};
use std::mem;
+use std::time::Duration;
impl Serializer for NaiveDateTime {
+ #[inline(always)]
fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), Error>
{
let dt = self.and_utc();
let micros = dt.timestamp() * 1_000_000 + dt.timestamp_subsec_micros()
as i64;
@@ -35,6 +37,7 @@ impl Serializer for NaiveDateTime {
Ok(())
}
+ #[inline(always)]
fn fory_read_data(context: &mut ReadContext) -> Result<Self, Error> {
let micros = context.reader.read_i64()?;
use chrono::TimeDelta;
@@ -45,43 +48,52 @@ impl Serializer for NaiveDateTime {
Ok(result)
}
+ #[inline(always)]
fn fory_reserved_space() -> usize {
mem::size_of::<u64>()
}
+ #[inline(always)]
fn fory_get_type_id(_: &TypeResolver) -> Result<u32, Error> {
Ok(TypeId::TIMESTAMP as u32)
}
+ #[inline(always)]
fn fory_type_id_dyn(&self, _: &TypeResolver) -> Result<u32, Error> {
Ok(TypeId::TIMESTAMP as u32)
}
+ #[inline(always)]
fn fory_static_type_id() -> TypeId {
TypeId::TIMESTAMP
}
+ #[inline(always)]
fn as_any(&self) -> &dyn std::any::Any {
self
}
+ #[inline(always)]
fn fory_write_type_info(context: &mut WriteContext) -> Result<(), Error> {
context.writer.write_varuint32(TypeId::TIMESTAMP as u32);
Ok(())
}
+ #[inline(always)]
fn fory_read_type_info(context: &mut ReadContext) -> Result<(), Error> {
read_basic_type_info::<Self>(context)
}
}
impl Serializer for NaiveDate {
+ #[inline(always)]
fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), Error>
{
let days_since_epoch = self.signed_duration_since(EPOCH).num_days();
context.writer.write_i32(days_since_epoch as i32);
Ok(())
}
+ #[inline(always)]
fn fory_read_data(context: &mut ReadContext) -> Result<Self, Error> {
let days = context.reader.read_i32()?;
use chrono::TimeDelta;
@@ -90,44 +102,145 @@ impl Serializer for NaiveDate {
Ok(result)
}
+ #[inline(always)]
fn fory_reserved_space() -> usize {
mem::size_of::<i32>()
}
+ #[inline(always)]
fn fory_get_type_id(_: &TypeResolver) -> Result<u32, Error> {
Ok(TypeId::LOCAL_DATE as u32)
}
+ #[inline(always)]
fn fory_type_id_dyn(&self, _: &TypeResolver) -> Result<u32, Error> {
Ok(TypeId::LOCAL_DATE as u32)
}
+ #[inline(always)]
fn fory_static_type_id() -> TypeId {
TypeId::LOCAL_DATE
}
+ #[inline(always)]
fn as_any(&self) -> &dyn std::any::Any {
self
}
+ #[inline(always)]
fn fory_write_type_info(context: &mut WriteContext) -> Result<(), Error> {
context.writer.write_varuint32(TypeId::LOCAL_DATE as u32);
Ok(())
}
+ #[inline(always)]
fn fory_read_type_info(context: &mut ReadContext) -> Result<(), Error> {
read_basic_type_info::<Self>(context)
}
}
impl ForyDefault for NaiveDateTime {
+ #[inline(always)]
fn fory_default() -> Self {
NaiveDateTime::default()
}
}
impl ForyDefault for NaiveDate {
+ #[inline(always)]
fn fory_default() -> Self {
NaiveDate::default()
}
}
+
+impl Serializer for Duration {
+ #[inline(always)]
+ fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), Error>
{
+ let secs = self.as_secs() as i64;
+ let nanos = self.subsec_nanos() as i32;
+ context.writer.write_varint64(secs);
+ context.writer.write_i32(nanos);
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn fory_read_data(context: &mut ReadContext) -> Result<Self, Error> {
+ let secs = context.reader.read_varint64()? as u64;
+ let nanos = context.reader.read_i32()? as u32;
+ Ok(Duration::new(secs, nanos))
+ }
+
+ #[inline(always)]
+ fn fory_reserved_space() -> usize {
+ 9 + mem::size_of::<i32>() // max varint64 is 9 bytes + 4 bytes for i32
+ }
+
+ #[inline(always)]
+ fn fory_get_type_id(_: &TypeResolver) -> Result<u32, Error> {
+ Ok(TypeId::DURATION as u32)
+ }
+
+ #[inline(always)]
+ fn fory_type_id_dyn(&self, _: &TypeResolver) -> Result<u32, Error> {
+ Ok(TypeId::DURATION as u32)
+ }
+
+ #[inline(always)]
+ fn fory_static_type_id() -> TypeId {
+ TypeId::DURATION
+ }
+
+ #[inline(always)]
+ fn as_any(&self) -> &dyn std::any::Any {
+ self
+ }
+
+ #[inline(always)]
+ fn fory_write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+ context.writer.write_varuint32(TypeId::DURATION as u32);
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn fory_read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+ read_basic_type_info::<Self>(context)
+ }
+}
+
+impl ForyDefault for Duration {
+ #[inline(always)]
+ fn fory_default() -> Self {
+ Duration::ZERO
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::fory::Fory;
+
+ #[test]
+ fn test_duration_serialization() {
+ let fory = Fory::default();
+
+ // Test various durations
+ let test_cases = vec![
+ Duration::ZERO,
+ Duration::new(0, 0),
+ Duration::new(1, 0),
+ Duration::new(0, 1),
+ Duration::new(123, 456789),
+ Duration::new(u64::MAX, 999_999_999),
+ ];
+
+ for duration in test_cases {
+ let bytes = fory.serialize(&duration).unwrap();
+ let deserialized: Duration = fory.deserialize(&bytes).unwrap();
+ assert_eq!(
+ duration, deserialized,
+ "Failed for duration: {:?}",
+ duration
+ );
+ }
+ }
+}
diff --git a/rust/fory-core/src/serializer/skip.rs
b/rust/fory-core/src/serializer/skip.rs
index 89f013264..ea103bd37 100644
--- a/rust/fory-core/src/serializer/skip.rs
+++ b/rust/fory-core/src/serializer/skip.rs
@@ -26,6 +26,7 @@ use crate::types;
use crate::types::{is_user_type, RefFlag};
use chrono::{NaiveDate, NaiveDateTime};
use std::rc::Rc;
+use std::time::Duration;
#[allow(unreachable_code)]
pub fn skip_field_value(
@@ -381,6 +382,9 @@ fn skip_value(
types::TIMESTAMP => {
<NaiveDateTime as Serializer>::fory_read_data(context)?;
}
+ types::DURATION => {
+ <Duration as Serializer>::fory_read_data(context)?;
+ }
types::BINARY => {
<Vec<u8> as Serializer>::fory_read_data(context)?;
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]