Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
804163f
add support for naive_date naive_date_time naive_time date_time_utc d…
l-7-l Oct 6, 2025
b23a415
feat(chrono): refactor chrono support and fuzz testing
l-7-l Dec 21, 2025
1954e56
Appease clippy and fix CI using a better-supported big endian arch. (…
finnbear Oct 17, 2025
7f6d6c8
Fix thread local access error. (#93)
finnbear Dec 17, 2025
d7df262
Version 0.6.8 (#96)
finnbear Dec 17, 2025
946257c
Version 0.6.9 - remove deprecated doc_auto_cfg feature. (#97)
finnbear Dec 18, 2025
7d5981f
add support for naive_date naive_date_time naive_time date_time_utc d…
l-7-l Oct 6, 2025
437ec56
Merge branch 'SoftbearStudios:main' into support_for_chrono#39
l-7-l Dec 21, 2025
bd26bd4
resolve conflicts
l-7-l Dec 21, 2025
e68f05d
chore: add chrono to fuzz
l-7-l Dec 21, 2025
be7a642
add support for naive_date naive_date_time naive_time date_time_utc d…
l-7-l Oct 6, 2025
9427f9f
feat(chrono): refactor chrono support and fuzz testing
l-7-l Dec 21, 2025
ad2a731
add support for naive_date naive_date_time naive_time date_time_utc d…
l-7-l Oct 6, 2025
843f389
resolve conflicts
l-7-l Dec 21, 2025
232a3a7
add try_convert_from
l-7-l Mar 28, 2026
fe60bb0
feat: use try_convert_from for chrono
l-7-l Mar 28, 2026
53823bb
add jiff support
l-7-l Mar 28, 2026
a0d7e2c
feat: add jiff support
l-7-l Mar 28, 2026
ada3fd1
chore: 1. Bump crate version from 0.6.9 to 0.7.0 2. change jiff depe…
l-7-l Mar 28, 2026
6d583dc
chore: bump lz4_flex from 0.11.2(yanked) to 0.13.0
l-7-l Mar 28, 2026
cbfecb7
fix(chrono): correct NaiveTime conversion assertions 23:59:59 with 1…
l-7-l Mar 28, 2026
60c51df
chore: remove chrono in default features
l-7-l Mar 28, 2026
37b09f2
test(jiff): remove jiff::Timestamp::now() for Miri compatibility
l-7-l Mar 28, 2026
41954b1
fix(try_convert): use next_unchecked_as_ptr for decode
l-7-l Apr 5, 2026
9bcb09e
refactor(chrono): restructure module and use ConvertFrom for infallib…
l-7-l Apr 5, 2026
a30071f
refactor(jiff): refine Zoned implementation, add tests and fix comment
l-7-l Apr 5, 2026
7239369
chore(chrono): remove src/ext/chrono.rs
l-7-l Apr 5, 2026
4885071
Merge branch 'main' into support_for_chrono#39
l-7-l Apr 5, 2026
277b6eb
chore(jiff/zoned/test): remove Zoned::now
l-7-l Apr 5, 2026
f5e222d
opt(jiff/chorno): add #[inline(always)] for Convert/TryConvert From
l-7-l Apr 5, 2026
25b0983
fix(try_convert): add Copy bound to Decode implementation for memory …
l-7-l Apr 5, 2026
dee35ad
chore(Cargo.toml): change 0.7.0 to 0.6.9
l-7-l Apr 5, 2026
7fe44f0
chore(cargo.toml/jiff): set default-features=false to jiff
l-7-l Apr 5, 2026
86a1736
chore(cargo.toml): remove jiff from the default enabled features
l-7-l Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ exclude = ["fuzz/"]
arrayvec = { version = "0.7", default-features = false, optional = true }
bitcode_derive = { version = "=0.6.9", path = "./bitcode_derive", optional = true }
bytemuck = { version = "1.14", features = [ "min_const_generics", "must_cast" ] }
chrono = { version = ">=0.4", default-features = false, optional = true }
glam = { version = ">=0.21", default-features = false, optional = true }
jiff = { version = ">=0.2.0", default-features = false, optional = true }
rust_decimal = { version = "1.36", default-features = false, optional = true }
serde = { version = "1.0", default-features = false, features = [ "alloc" ], optional = true }
time = { version = "0.3", default-features = false, optional = true }
Expand Down
7 changes: 5 additions & 2 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ cargo-fuzz = true

[dependencies]
arrayvec = { version = "0.7", features = ["serde"] }
bitcode = { path = "..", features = [ "arrayvec", "rust_decimal", "serde", "time" ] }
bitcode = { path = "..", features = [ "arrayvec", "rust_decimal", "serde", "time", "chrono", "jiff" ] }
libfuzzer-sys = "0.4"
rust_decimal = "1.36.0"
serde = { version ="1.0", features = [ "derive" ] }
time = { version = "0.3", features = ["serde"]}
chrono = { version = "0.4.42", features = ["serde"] }
jiff = {version = "0.2.23", features = ["serde"]}


# Prevent this from interfering with workspaces
[workspace]
Expand All @@ -24,4 +27,4 @@ members = ["."]
name = "fuzz"
path = "fuzz_targets/fuzz.rs"
test = false
doc = false
doc = false
Binary file not shown.
26 changes: 22 additions & 4 deletions fuzz/fuzz_targets/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use libfuzzer_sys::fuzz_target;
extern crate bitcode;
use arrayvec::{ArrayString, ArrayVec};
use bitcode::{Decode, DecodeOwned, Encode};
use rust_decimal::Decimal;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::fmt::Debug;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::num::NonZeroU32;
use std::time::Duration;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use rust_decimal::Decimal;

#[inline(never)]
fn test_derive<T: Debug + PartialEq + Encode + DecodeOwned>(data: &[u8]) {
Expand Down Expand Up @@ -148,10 +148,20 @@ fuzz_target!(|data: &[u8]| {
A,
B,
C(u16),
D { a: u8, b: u8, #[serde(skip)] #[bitcode(skip)] c: u8 },
D {
a: u8,
b: u8,
#[serde(skip)]
#[bitcode(skip)]
c: u8,
},
E(String),
F,
G(#[bitcode(skip)] #[serde(skip)] i16),
G(
#[bitcode(skip)]
#[serde(skip)]
i16,
),
P(BTreeMap<u16, u8>),
}

Expand Down Expand Up @@ -233,5 +243,13 @@ fuzz_target!(|data: &[u8]| {
SocketAddrV6,
SocketAddr,
time::Time,
chrono::NaiveTime,
chrono::NaiveDate,
chrono::NaiveDateTime,
chrono::DateTime<chrono::Utc>,
jiff::civil::Date,
jiff::civil::Time,
jiff::Timestamp,
jiff::Zoned,
);
});
2 changes: 2 additions & 0 deletions src/derive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub(crate) mod convert;
mod duration;
mod empty;
mod impls;
#[cfg(any(feature = "chrono", feature = "jiff"))]
pub(crate) mod try_convert;
// TODO: When ip_in_core has been stable (https://github.com/rust-lang/rust/issues/108443)
// for long enough, remove feature check.
#[cfg(feature = "std")]
Expand Down
74 changes: 74 additions & 0 deletions src/derive/try_convert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::{
coder::{Decoder, View},
derive::Decode,
fast::{CowSlice, NextUnchecked, PushUnchecked, SliceImpl, Unaligned},
};

#[allow(unused)]
macro_rules! impl_try_convert {
($want: path, $have: ty) => {
impl_try_convert!($want, $have, $have);
};
($want: path, $have_encode: ty, $have_decode: ty) => {
impl crate::derive::Encode for $want {
type Encoder = crate::derive::convert::ConvertIntoEncoder<$have_encode>;
}
impl<'a> crate::derive::Decode<'a> for $want {
type Decoder =
crate::derive::try_convert::TryConvertFromDecoder<'a, $have_decode, $want>;
}
};
}

#[allow(unused)]
pub(crate) use impl_try_convert;

// Like [`TryFrom`] but we can implement it ourselves.
pub trait TryConvertFrom<T>: Sized {
fn try_convert_from(value: T) -> Result<Self, crate::Error>;
}
/// Decodes a `T` and then converts it with [`TryConvertFrom`].
pub struct TryConvertFromDecoder<'a, T: Decode<'a>, F: TryConvertFrom<T>> {
data: CowSlice<'a, F>,
decoder: T::Decoder,
}

// Can't derive since it would bound T: Default.
impl<'a, T: Decode<'a>, F: TryConvertFrom<T>> Default for TryConvertFromDecoder<'a, T, F> {
fn default() -> Self {
Self {
data: CowSlice::with_allocation(Vec::new()),
decoder: Default::default(),
}
}
}

impl<'a, T: Decode<'a>, F: TryConvertFrom<T>> View<'a> for TryConvertFromDecoder<'a, T, F> {
fn populate(&mut self, input: &mut &'a [u8], length: usize) -> Result<(), crate::Error> {
self.decoder.populate(input, length)?;

let out: &mut Vec<F> = &mut self.data.set_owned();
out.reserve(length);

for _ in 0..length {
let value = F::try_convert_from(self.decoder.decode())?;
unsafe { out.push_unchecked(value) };
}

Ok(())
}
}

impl<'a, T: Decode<'a>, F: TryConvertFrom<T> + Send + Sync + Copy> Decoder<'a, F>
for TryConvertFromDecoder<'a, T, F>
{
#[inline(always)]
fn as_primitive(&mut self) -> Option<&mut SliceImpl<'_, Unaligned<F>>> {
None
}

#[inline(always)]
fn decode(&mut self) -> F {
unsafe { self.data.mut_slice().next_unchecked() }
}
}
67 changes: 67 additions & 0 deletions src/ext/chrono/date_time_utc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use chrono::{DateTime, NaiveDateTime, Utc};

use crate::convert::{impl_convert, ConvertFrom};

impl_convert!(DateTime<Utc>, NaiveDateTime, NaiveDateTime);

impl ConvertFrom<&DateTime<Utc>> for NaiveDateTime {
#[inline(always)]
fn convert_from(x: &DateTime<Utc>) -> Self {
x.naive_utc()
}
}

impl ConvertFrom<NaiveDateTime> for DateTime<Utc> {
#[inline(always)]
fn convert_from(enc: NaiveDateTime) -> Self {
DateTime::from_naive_utc_and_offset(enc, Utc)
}
}

#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use chrono::{DateTime, NaiveDate, Utc};

#[test]
fn test_chrono_datetime_utc() {
let ymds = [
(1970, 1, 1), // epoch
(2025, 10, 6),
(1, 1, 1),
(-44, 3, 15), // BCE
(9999, 12, 31),
];

for &(y, m, d) in ymds.iter() {
let naive = NaiveDate::from_ymd_opt(y, m, d)
.unwrap()
.and_hms_opt(12, 34, 56)
.unwrap();
let dt_utc = DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc);

let enc = crate::encode(&dt_utc);
let decoded: DateTime<Utc> = crate::decode(&enc).unwrap();

assert_eq!(dt_utc, decoded, "failed for datetime {:?}", dt_utc);
}
}

fn bench_data() -> Vec<DateTime<Utc>> {
crate::random_data(1000)
.into_iter()
.map(
|(y, m, d, h, mi, s, n, _offset_sec): (i32, u32, u32, u32, u32, u32, u32, i32)| {
let naive =
NaiveDate::from_ymd_opt((y % 9999).max(1), (m % 12).max(1), (d % 28) + 1)
.unwrap()
.and_hms_nano_opt(h % 24, mi % 60, s % 60, n % 1_000_000_000)
.unwrap();
DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc)
},
)
.collect()
}

crate::bench_encode_decode!(utc_vec: Vec<DateTime<Utc>>);
}
4 changes: 4 additions & 0 deletions src/ext/chrono/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod date_time_utc;
mod naive_date;
mod naive_date_time;
mod naive_time;
64 changes: 64 additions & 0 deletions src/ext/chrono/naive_date.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use chrono::{Datelike, NaiveDate};

use crate::{
convert::ConvertFrom,
try_convert::{impl_try_convert, TryConvertFrom},
};

// Number of days since the CE epoch(0001-01-01).
type NaiveDateCoder = i32;

impl_try_convert!(NaiveDate, NaiveDateCoder, NaiveDateCoder);

impl ConvertFrom<&NaiveDate> for NaiveDateCoder {
#[inline(always)]
fn convert_from(days: &NaiveDate) -> Self {
days.num_days_from_ce()
}
}

impl TryConvertFrom<NaiveDateCoder> for NaiveDate {
#[inline(always)]
fn try_convert_from(days: NaiveDateCoder) -> Result<Self, crate::Error> {
NaiveDate::from_num_days_from_ce_opt(days)
.ok_or_else(|| crate::error::error("Failed to convert DateDecode to chrono::NaiveDate"))
}
}

#[cfg(test)]
mod tests {
#[test]
fn test_chrono_naive_date() {
let dates = [
NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(), // epoch
NaiveDate::from_ymd_opt(2025, 10, 6).unwrap(),
NaiveDate::from_ymd_opt(1, 1, 1).unwrap(),
NaiveDate::from_ymd_opt(-44, 3, 15).unwrap(), // BCE
NaiveDate::from_ymd_opt(-44, 3, 15).unwrap(), // BCE
NaiveDate::from_ymd_opt(9999, 12, 31).unwrap(),
];

for x in dates {
let enc = crate::encode(&x);
let date: NaiveDate = crate::decode(&enc).unwrap();

assert_eq!(x, date, "failed for date {:?}", x);
}
}

use alloc::vec::Vec;
use chrono::NaiveDate;

fn bench_data() -> Vec<NaiveDate> {
crate::random_data(1000)
.into_iter()
.map(|(y, m, d): (i32, u32, u32)| {
let year = (y % 9999).max(1); // 1 ~ 9998
let month = (m % 12).max(1); // 1 ~ 12
let day = (d % 28) + 1; // 1 ~ 28
NaiveDate::from_ymd_opt(year, month, day).unwrap()
})
.collect()
}
crate::bench_encode_decode!(data: Vec<_>);
}
Loading
Loading