Skip to content

Commit

Permalink
Optimize DateTime deserialization to w/o dyn alloc
Browse files Browse the repository at this point in the history
  • Loading branch information
Kijewski committed Mar 3, 2022
1 parent 2842dd6 commit 15ca18e
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 110 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
**0.1.3**

* Optimize DateTime deserialization to work without dynamic allocation
([tz-rs#22](https://github.com/x-hgg-x/tz-rs/pull/22))

**0.1.2**

* Include “backzone” data to include pre-1970 information for some more time zones
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion make-tzdb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ itertools = "0.10.3"
phf_codegen = "0.10.0"
ron = "0.7.0"
serde = { version = "1.0.136", features = ["derive"] }
tz-rs = "0.6.3"
tz-rs = "0.6.4"
6 changes: 3 additions & 3 deletions tzdb/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tzdb"
version = "0.1.2"
version = "0.1.3"
edition = "2021"
authors = ["René Kijewski <[email protected]>"]
repository = "https://github.com/Kijewski/tzdb"
Expand All @@ -12,7 +12,7 @@ readme = "README.md"
rust-version = "1.57"

[dependencies]
tz-rs = "0.6.3"
tz-rs = "0.6.4"

# optional dependencies
byte-slice-cast = { version = "1.2.1", optional = true }
Expand Down Expand Up @@ -44,7 +44,7 @@ serde-as = ["serde", "serde_with"]
docsrs = ["document-features", "testing"]

# Internal feature, used when testing.
testing = ["serde", "serde_with", "serde/derive", "serde_with/macros"]
testing = ["serde-as", "serde/derive", "serde_with/macros"]

[package.metadata.docs.rs]
features = ["docsrs"]
Expand Down
56 changes: 46 additions & 10 deletions tzdb/src/serde_as/common.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,66 @@
use serde::de::Error;
use std::fmt;
use std::marker::PhantomData;

use serde::de::{Error, SeqAccess, Visitor};
use serde::ser::SerializeTuple;
use serde::Serializer;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use tz::timezone::LocalTimeType;
use tz::{DateTime, TimeZone};
use tz::{DateTime, TimeZoneRef, UtcDateTime};

pub(super) type TzTuple<'de> = (i32, bool, &'de str);

pub(super) fn deserialize_tz<E: Error>(value: TzTuple<'_>) -> Result<TimeZone, E> {
pub(super) fn project_utc<E: Error>(utc: UtcDateTime, value: TzTuple<'_>) -> Result<DateTime, E> {
let (ut_offset, is_dst, tz) = value;
let tz = match tz {
"" => None,
tz => Some(tz.as_bytes()),
};
let tz = LocalTimeType::new(ut_offset, is_dst, tz).map_err(E::custom)?;
TimeZone::new(vec![], vec![tz], vec![], None).map_err(E::custom)
let tz = &[tz];
let tz = TimeZoneRef::new(&[], tz, &[], &None).map_err(E::custom)?;
utc.project(tz).map_err(E::custom)
}

pub(super) fn deserialize_date_time<'de, D: Deserializer<'de>, T: Deserialize<'de>>(
deserializer: D,
) -> Result<(T, TzTuple<'de>), D::Error> {
struct UnixTpl<T>(PhantomData<T>);

impl<'de, T: Deserialize<'de>> Visitor<'de> for UnixTpl<T> {
type Value = (T, TzTuple<'de>);

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("UnixTime tuple")
}

fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let ut = seq
.next_element()?
.ok_or_else(|| A::Error::custom("expected UnixTime"))?;
let tz = seq
.next_element()?
.ok_or_else(|| A::Error::custom("expected LocalTimeType"))?;
Ok((ut, tz))
}
}

deserializer.deserialize_tuple(2, UnixTpl(PhantomData))
}

pub(super) fn serialize_tz<S: Serializer>(
pub(super) fn serialize_date_time<S: Serializer, T: Serialize>(
serializer: S,
source: &DateTime,
mut seq: <S as Serializer>::SerializeTuple,
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> {
date_time: T,
) -> Result<S::Ok, S::Error> {
let local_time_type = source.local_time_type();
seq.serialize_element(&(
let local_time_type = (
local_time_type.ut_offset(),
local_time_type.is_dst(),
local_time_type.time_zone_designation(),
))?;
);

let mut seq = serializer.serialize_tuple(2)?;
seq.serialize_element(&date_time)?;
seq.serialize_element(&local_time_type)?;
seq.end()
}
40 changes: 10 additions & 30 deletions tzdb/src/serde_as/float.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use std::fmt;
use std::num::FpCategory;

use serde::de::{Error, SeqAccess, Visitor};
use serde::ser::SerializeTuple;
use serde::de::{Error, Visitor};
use serde::{Deserializer, Serializer};
use serde_with::{DeserializeAs, SerializeAs};
use tz::{DateTime, UtcDateTime};

use super::common::serialize_tz;
use crate::serde_as::common::{deserialize_tz, TzTuple};
use super::common::{deserialize_date_time, serialize_date_time};
use crate::serde_as::common::project_utc;

/// (De)serialize a (Utc)DateTime as an f64 with millisecond resolution
///
Expand Down Expand Up @@ -101,38 +100,19 @@ impl SerializeAs<UtcDateTime> for Float {

impl<'de> DeserializeAs<'de, DateTime> for Float {
fn deserialize_as<D: Deserializer<'de>>(deserializer: D) -> Result<DateTime, D::Error> {
struct UnixTpl;

impl<'de> Visitor<'de> for UnixTpl {
type Value = (f64, TzTuple<'de>);

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("UnixTime")
}

fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let ut = seq
.next_element()?
.ok_or_else(|| A::Error::custom("expected UnixTime"))?;
let tz = seq
.next_element()?
.ok_or_else(|| A::Error::custom("expected LocalTimeType"))?;
Ok((ut, tz))
}
}

let (value, tz) = deserializer.deserialize_tuple(2, UnixTpl)?;
let (value, tz) = deserialize_date_time(deserializer)?;
let utc = nanos_to_utc(value)?;
let tz = deserialize_tz(tz)?;
utc.project(tz.as_ref()).map_err(D::Error::custom)
project_utc(utc, tz)
}
}

impl SerializeAs<DateTime> for Float {
fn serialize_as<S: Serializer>(source: &DateTime, serializer: S) -> Result<S::Ok, S::Error> {
let mut seq = serializer.serialize_tuple(2)?;
seq.serialize_element(&serialize_nanos(source.total_nanoseconds()))?;
serialize_tz::<S>(source, seq)
serialize_date_time(
serializer,
source,
serialize_nanos(source.total_nanoseconds()),
)
}
}

Expand Down
41 changes: 11 additions & 30 deletions tzdb/src/serde_as/nanoseconds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use serde::{Deserializer, Serializer};
use serde_with::{DeserializeAs, SerializeAs};
use tz::{DateTime, UtcDateTime};

use super::common::serialize_tz;
use crate::serde_as::common::{deserialize_tz, TzTuple};
use super::common::{deserialize_date_time, serialize_date_time};
use crate::serde_as::common::project_utc;

/// (De)serialize a (Utc)DateTime as a tuple with nanosecond resolution
///
Expand Down Expand Up @@ -79,38 +79,19 @@ impl SerializeAs<UtcDateTime> for Nanoseconds {

impl<'de> DeserializeAs<'de, DateTime> for Nanoseconds {
fn deserialize_as<D: Deserializer<'de>>(deserializer: D) -> Result<DateTime, D::Error> {
struct UnixTpl;

impl<'de> Visitor<'de> for UnixTpl {
type Value = ((i64, u32), TzTuple<'de>);

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("UnixTime")
}

fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let ut = seq
.next_element()?
.ok_or_else(|| A::Error::custom("expected UnixTime"))?;
let tz = seq
.next_element()?
.ok_or_else(|| A::Error::custom("expected LocalTimeType"))?;
Ok((ut, tz))
}
}

let ((secs, nanos), tz) = deserializer.deserialize_tuple(2, UnixTpl)?;
let ((secs, nanos), tz) = deserialize_date_time(deserializer)?;
let utc = UtcDateTime::from_timespec(secs, nanos).map_err(D::Error::custom)?;
let tz = deserialize_tz(tz)?;
utc.project(tz.as_ref()).map_err(D::Error::custom)
project_utc(utc, tz)
}
}

impl SerializeAs<DateTime> for Nanoseconds {
fn serialize_as<S: Serializer>(source: &DateTime, serializer: S) -> Result<S::Ok, S::Error> {
let mut seq = serializer.serialize_tuple(2)?;
seq.serialize_element(&(source.unix_time(), source.nanoseconds()))?;
serialize_tz::<S>(source, seq)
serialize_date_time(
serializer,
source,
(source.unix_time(), source.nanoseconds()),
)
}
}

Expand Down Expand Up @@ -140,15 +121,15 @@ fn test_seconds_nanos_tuple() {
assert_eq!(dt.local_time_type().ut_offset(), 3600);

assert_eq!(
to_string(&UtcStruct(utc.clone())).unwrap(),
to_string(&UtcStruct(utc)).unwrap(),
"[1646148037,730296742]",
);
assert_eq!(
from_str::<UtcStruct>("[1646148037,730296742]").unwrap(),
UtcStruct(utc),
);
assert_eq!(
to_string(&DtStruct(dt.clone())).unwrap(),
to_string(&DtStruct(dt)).unwrap(),
r#"[[1646148037,730296742],[3600,false,"CET"]]"#,
);
assert_eq!(
Expand Down
42 changes: 9 additions & 33 deletions tzdb/src/serde_as/seconds.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use std::fmt;

use serde::de::{Error, SeqAccess, Visitor};
use serde::ser::SerializeTuple;
use serde::de::{Error, Visitor};
use serde::{Deserializer, Serializer};
use serde_with::{DeserializeAs, SerializeAs};
use tz::{DateTime, UtcDateTime};

use super::common::serialize_tz;
use crate::serde_as::common::{deserialize_tz, TzTuple};
use super::common::{deserialize_date_time, serialize_date_time};
use crate::serde_as::common::project_utc;

/// (De)serialize only the seconds of a (Utc)DateTime as an i64
///
Expand Down Expand Up @@ -69,38 +68,15 @@ impl SerializeAs<UtcDateTime> for Seconds {

impl<'de> DeserializeAs<'de, DateTime> for Seconds {
fn deserialize_as<D: Deserializer<'de>>(deserializer: D) -> Result<DateTime, D::Error> {
struct UnixTpl;

impl<'de> Visitor<'de> for UnixTpl {
type Value = (i64, TzTuple<'de>);

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("UnixTime")
}

fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let ut = seq
.next_element()?
.ok_or_else(|| A::Error::custom("expected UnixTime"))?;
let tz = seq
.next_element()?
.ok_or_else(|| A::Error::custom("expected LocalTimeType"))?;
Ok((ut, tz))
}
}

let (secs, tz) = deserializer.deserialize_tuple(2, UnixTpl)?;
let (secs, tz) = deserialize_date_time(deserializer)?;
let utc = UtcDateTime::from_timespec(secs, 0).map_err(D::Error::custom)?;
let tz = deserialize_tz(tz)?;
utc.project(tz.as_ref()).map_err(D::Error::custom)
project_utc(utc, tz)
}
}

impl SerializeAs<DateTime> for Seconds {
fn serialize_as<S: Serializer>(source: &DateTime, serializer: S) -> Result<S::Ok, S::Error> {
let mut seq = serializer.serialize_tuple(2)?;
seq.serialize_element(&source.unix_time())?;
serialize_tz::<S>(source, seq)
serialize_date_time(serializer, source, source.unix_time())
}
}

Expand Down Expand Up @@ -129,10 +105,10 @@ fn test_seconds_tuple() {
assert_eq!(utc.unix_time(), 1_646_148_037);
assert_eq!(dt.local_time_type().ut_offset(), 3600);

assert_eq!(to_string(&UtcStruct(utc.clone())).unwrap(), "1646148037",);
assert_eq!(from_str::<UtcStruct>("1646148037").unwrap(), UtcStruct(utc),);
assert_eq!(to_string(&UtcStruct(utc)).unwrap(), "1646148037");
assert_eq!(from_str::<UtcStruct>("1646148037").unwrap(), UtcStruct(utc));
assert_eq!(
to_string(&DtStruct(dt.clone())).unwrap(),
to_string(&DtStruct(dt)).unwrap(),
r#"[1646148037,[3600,false,"CET"]]"#,
);
assert_eq!(
Expand Down

0 comments on commit 15ca18e

Please sign in to comment.