chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,46 @@
use serde::{Deserialize, Serialize, Serializer};
use serde_test::{assert_ser_tokens_error, Token};
use time::macros::{datetime, format_description};
use time::{error, OffsetDateTime};
/// Trigger `time::error::Format::StdIo` errors.
///
/// `StdIo` won't be reached during normal serde operation: it's instantiated
/// only during calls to `format_into()`, but most `Serializable`
/// implementations will only call `format()` because serde `Serializer`
/// interface doesn't expose the underlying `io::Write`.
///
/// Therefore, we need a contrived serializer to trigger coverage.
fn serialize<S: Serializer>(datetime: &OffsetDateTime, _serializer: S) -> Result<S::Ok, S::Error> {
Err(datetime
.format_into(
&mut [0u8; 0].as_mut_slice(),
format_description!("nonempty format description"),
)
.map_err(error::Format::into_invalid_serde_value::<S>)
.expect_err("Writing to a zero-length buffer should always error."))
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct TestBadIo {
#[serde(serialize_with = "serialize")]
dt: OffsetDateTime,
}
#[test]
fn custom_serialize_io_error() {
let value = TestBadIo {
dt: datetime!(2000-01-01 00:00 -4:00),
};
assert_ser_tokens_error::<TestBadIo>(
&value,
&[
Token::Struct {
name: "TestBadIo",
len: 1,
},
Token::Str("dt"),
],
"failed to write whole buffer",
);
}

View File

@@ -0,0 +1,128 @@
use serde::{Deserialize, Serialize};
use serde_test::{
assert_de_tokens_error, assert_ser_tokens_error, assert_tokens, Configure, Token,
};
use time::macros::datetime;
use time::serde::iso8601;
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct Test {
#[serde(with = "iso8601")]
dt: OffsetDateTime,
#[serde(with = "iso8601::option")]
option_dt: Option<OffsetDateTime>,
}
#[test]
fn serialize() {
let value = Test {
dt: datetime!(2000-01-01 00:00:00 UTC),
option_dt: Some(datetime!(2000-01-01 00:00:00 UTC)),
};
assert_tokens(
&value.compact(),
&[
Token::Struct {
name: "Test",
len: 2,
},
Token::Str("dt"),
Token::BorrowedStr("+002000-01-01T00:00:00.000000000Z"),
Token::Str("option_dt"),
Token::Some,
Token::BorrowedStr("+002000-01-01T00:00:00.000000000Z"),
Token::StructEnd,
],
);
let value = Test {
dt: datetime!(2000-01-01 00:00:00 UTC),
option_dt: None,
};
assert_tokens(
&value.compact(),
&[
Token::Struct {
name: "Test",
len: 2,
},
Token::Str("dt"),
Token::BorrowedStr("+002000-01-01T00:00:00.000000000Z"),
Token::Str("option_dt"),
Token::None,
Token::StructEnd,
],
);
}
#[test]
fn serialize_error() {
let value = Test {
dt: datetime!(2000-01-01 00:00:00 +00:00:01),
option_dt: None,
};
assert_ser_tokens_error::<Test>(
&value,
&[
Token::Struct {
name: "Test",
len: 2,
},
Token::Str("dt"),
],
"The offset_second component cannot be formatted into the requested format.",
);
}
#[test]
fn deserialize_error() {
assert_de_tokens_error::<Test>(
&[
Token::Struct {
name: "Test",
len: 2,
},
Token::Str("dt"),
Token::BorrowedStr("bad"),
Token::StructEnd,
],
"the 'year' component could not be parsed",
);
}
#[test]
fn issue_674_leap_second_support() {
serde_test::assert_de_tokens::<Test>(
&Test {
dt: datetime!(2016-12-31 23:59:59.999999999 UTC),
option_dt: Some(datetime!(2016-12-31 23:59:59.999999999 UTC)),
},
&[
Token::Struct {
name: "Test",
len: 2,
},
Token::Str("dt"),
Token::BorrowedStr("2016-12-31T23:59:60.000Z"),
Token::Str("option_dt"),
Token::Some,
Token::BorrowedStr("2016-12-31T23:59:60.000Z"),
Token::StructEnd,
],
);
}
#[test]
fn issue_724_truncation() {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct Demo(#[serde(with = "time::serde::iso8601")] OffsetDateTime);
let value = datetime!(2025-01-10 23:01:16.000081999 UTC);
let info = Demo(value);
let serialized = serde_json::to_string(&info).expect("serialization failed");
assert_eq!(serialized, r#""+002025-01-10T23:01:16.000081999Z""#);
let deserialized: Demo = serde_json::from_str(&serialized).expect("deserialization failed");
assert_eq!(info, deserialized);
}

View File

@@ -0,0 +1,148 @@
use std::error::Error;
use serde::{Deserialize, Serialize};
use serde_test::Configure;
use time::macros::{date, datetime, time};
use time::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, Weekday};
enum Format {
Compact,
Readable,
}
use Format::*;
fn serialize<T: Serialize>(val: T) -> Result<String, Box<dyn Error>> {
let mut buf: Vec<u8> = Vec::new();
let mut ser = serde_json::Serializer::new(&mut buf);
val.serialize(&mut ser)?;
let str = String::from_utf8(buf)?;
Ok(str)
}
fn deserialize<'a, T: Deserialize<'a>>(from: &'a str, fmt: Format) -> serde_json::Result<T> {
let mut de = serde_json::Deserializer::from_str(from);
match fmt {
Compact => T::deserialize((&mut de).compact()),
Readable => T::deserialize((&mut de).readable()),
}
}
#[test]
fn weekday_json() -> Result<(), Box<dyn Error>> {
use Weekday::*;
assert_eq!(serialize(Monday.compact())?, "1");
assert_eq!(deserialize::<Weekday>("1", Compact)?, Monday);
assert_eq!(serialize(Monday.readable())?, "\"Monday\"");
assert_eq!(deserialize::<Weekday>("\"Monday\"", Readable)?, Monday);
assert_eq!(deserialize::<Weekday>("1", Readable)?, Monday);
Ok(())
}
#[test]
fn month_json() -> Result<(), Box<dyn Error>> {
use Month::*;
assert_eq!(serialize(March.compact())?, "3");
assert_eq!(deserialize::<Month>("3", Compact)?, March);
assert_eq!(serialize(March.readable())?, "\"March\"");
assert_eq!(deserialize::<Month>("\"March\"", Readable)?, March);
assert_eq!(deserialize::<Month>("3", Readable)?, March);
Ok(())
}
#[test]
fn time_json() -> Result<(), Box<dyn Error>> {
let time = time!(12:40:20);
assert_eq!(serialize(time.compact())?, "[12,40,20,0]");
assert_eq!(deserialize::<Time>("[12,40,20,0]", Compact)?, time);
assert_eq!(serialize(time.readable())?, "\"12:40:20.0\"");
assert_eq!(deserialize::<Time>("\"12:40:20.0\"", Readable)?, time);
assert_eq!(deserialize::<Time>("[12,40,20,0]", Readable)?, time);
Ok(())
}
#[test]
fn primitive_datetime_json() -> Result<(), Box<dyn Error>> {
let dt = datetime!(2022-05-20 12:40:20);
assert_eq!(serialize(dt.compact())?, "[2022,140,12,40,20,0]");
assert_eq!(
deserialize::<PrimitiveDateTime>("[2022,140,12,40,20,0]", Compact)?,
dt
);
assert_eq!(serialize(dt.readable())?, "\"2022-05-20 12:40:20.0\"");
assert_eq!(
deserialize::<PrimitiveDateTime>("\"2022-05-20 12:40:20.0\"", Readable)?,
dt
);
assert_eq!(
deserialize::<PrimitiveDateTime>("[2022,140,12,40,20,0]", Readable)?,
dt
);
Ok(())
}
#[test]
fn offset_datetime_json() -> Result<(), Box<dyn Error>> {
let dt = datetime!(2022-05-20 12:40:20).assume_utc();
assert_eq!(serialize(dt.compact())?, "[2022,140,12,40,20,0,0,0,0]");
assert_eq!(
deserialize::<OffsetDateTime>("[2022,140,12,40,20,0,0,0,0]", Compact)?,
dt
);
assert_eq!(
serialize(dt.readable())?,
"\"2022-05-20 12:40:20.0 +00:00:00\""
);
assert_eq!(
deserialize::<OffsetDateTime>("\"2022-05-20 12:40:20.0 +00:00:00\"", Readable)?,
dt
);
assert_eq!(
deserialize::<OffsetDateTime>("[2022,140,12,40,20,0,0,0,0]", Readable)?,
dt
);
Ok(())
}
#[test]
fn duration_json() -> Result<(), Box<dyn Error>> {
let dur = Duration::new(50, 0);
assert_eq!(serialize(dur.compact())?, "[50,0]");
assert_eq!(deserialize::<Duration>("[50,0]", Compact)?, dur);
assert_eq!(serialize(dur.readable())?, "\"50.000000000\"");
assert_eq!(deserialize::<Duration>("\"50.000000000\"", Readable)?, dur);
assert_eq!(deserialize::<Duration>("[50,0]", Readable)?, dur);
Ok(())
}
#[test]
fn date_json() -> Result<(), Box<dyn Error>> {
let date = date!(2022-04-05);
assert_eq!(serialize(date.compact())?, "[2022,95]");
assert_eq!(deserialize::<Date>("[2022,95]", Compact)?, date);
assert_eq!(serialize(date.readable())?, "\"2022-04-05\"");
assert_eq!(deserialize::<Date>("\"2022-04-05\"", Readable)?, date);
assert_eq!(deserialize::<Date>("[2022,95]", Readable)?, date);
Ok(())
}

View File

@@ -0,0 +1,305 @@
#[rustfmt::skip] // Tries to remove the leading `::`, which breaks compilation.
use ::serde::{Deserialize, Serialize};
use serde_test::{
assert_de_tokens, assert_de_tokens_error, assert_ser_tokens_error, assert_tokens, Configure,
Token,
};
use time::format_description::well_known::{iso8601, Iso8601};
use time::format_description::BorrowedFormatItem;
use time::macros::{date, datetime, offset, time};
use time::{serde, Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
// Not used in the tests, but ensures that the macro compiles.
#[expect(dead_code)]
const ISO_FORMAT: Iso8601<{ iso8601::Config::DEFAULT.encode() }> =
Iso8601::<{ iso8601::Config::DEFAULT.encode() }>;
time::serde::format_description!(my_format, OffsetDateTime, ISO_FORMAT);
time::serde::format_description!(
my_format2,
OffsetDateTime,
Iso8601::<{ iso8601::Config::DEFAULT.encode() }>
);
mod nested {
time::serde::format_description!(
pub(super) offset_dt_format,
OffsetDateTime,
"custom format: [year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
sign:mandatory]:[offset_minute]"
);
time::serde::format_description!(
pub primitive_dt_format,
PrimitiveDateTime,
"custom format: [year]-[month]-[day] [hour]:[minute]:[second]"
);
time::serde::format_description!(
pub(in crate::serde::macros) time_format,
Time,
"custom format: [minute]:[second]"
);
}
serde::format_description!(date_format, Date, "custom format: [year]-[month]-[day]");
serde::format_description!(
offset_format,
UtcOffset,
"custom format: [offset_hour sign:mandatory]:[offset_minute]"
);
const TIME_FORMAT_ALT: &[BorrowedFormatItem<'_>] =
time::macros::format_description!("[hour]:[minute]");
serde::format_description!(time_format_alt, Time, TIME_FORMAT_ALT);
serde::format_description!(
time_format_alt2,
Time,
time::macros::format_description!("[hour]:[minute]")
);
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct TestCustomFormat {
#[serde(with = "nested::offset_dt_format")]
offset_dt: OffsetDateTime,
#[serde(with = "nested::primitive_dt_format::option")]
primitive_dt: Option<PrimitiveDateTime>,
#[serde(with = "date_format")]
date: Date,
#[serde(with = "nested::time_format::option")]
time: Option<Time>,
#[serde(with = "offset_format")]
offset: UtcOffset,
#[serde(with = "time_format_alt")]
time_alt: Time,
}
#[test]
fn custom_serialize() {
let value = TestCustomFormat {
offset_dt: datetime!(2000-01-01 00:00 -4:00),
primitive_dt: Some(datetime!(2000-01-01 00:00)),
date: date!(2000-01-01),
time: None,
offset: offset!(-4),
time_alt: time!(12:34),
};
assert_tokens(
&value.compact(),
&[
Token::Struct {
name: "TestCustomFormat",
len: 6,
},
Token::Str("offset_dt"),
Token::BorrowedStr("custom format: 2000-01-01 00:00:00 -04:00"),
Token::Str("primitive_dt"),
Token::Some,
Token::BorrowedStr("custom format: 2000-01-01 00:00:00"),
Token::Str("date"),
Token::BorrowedStr("custom format: 2000-01-01"),
Token::Str("time"),
Token::None,
Token::Str("offset"),
Token::BorrowedStr("custom format: -04:00"),
Token::Str("time_alt"),
Token::BorrowedStr("12:34"),
Token::StructEnd,
],
);
}
#[test]
fn custom_serialize_error() {
// Deserialization error due to parse problem.
assert_de_tokens_error::<TestCustomFormat>(
&[
Token::Struct {
name: "TestCustomFormat",
len: 5,
},
Token::Str("offset_dt"),
Token::BorrowedStr("custom format: 2000-01-01 0:00:00 -04:00"),
],
"the 'hour' component could not be parsed",
);
// Parse problem in optional field.
assert_de_tokens_error::<TestCustomFormat>(
&[
Token::Struct {
name: "TestCustomFormat",
len: 5,
},
Token::Str("offset_dt"),
Token::BorrowedStr("custom format: 2000-01-01 00:00:00 -04:00"),
Token::Str("primitive_dt"),
Token::Some,
Token::BorrowedStr("custom format: 2000-01-01 0:00:00 -04:00"),
],
"the 'hour' component could not be parsed",
);
// Type error
assert_de_tokens_error::<TestCustomFormat>(
&[
Token::Struct {
name: "TestCustomFormat",
len: 5,
},
Token::Str("offset_dt"),
Token::Bool(false),
],
"invalid type: boolean `false`, expected a(n) `OffsetDateTime` in the format \"custom \
format: [year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
sign:mandatory]:[offset_minute]\"",
);
assert_de_tokens_error::<TestCustomFormat>(
&[
Token::Struct {
name: "TestCustomFormat",
len: 5,
},
Token::Str("offset_dt"),
Token::BorrowedStr("custom format: 2000-01-01 00:00:00 -04:00"),
Token::Str("primitive_dt"),
Token::Bool(false),
],
"invalid type: boolean `false`, expected an `Option<PrimitiveDateTime>` in the format \
\"custom format: [year]-[month]-[day] [hour]:[minute]:[second]\"",
);
}
// This format string has offset_hour and offset_minute, but is for formatting PrimitiveDateTime.
serde::format_description!(
primitive_date_time_format_bad,
PrimitiveDateTime,
"[offset_hour]:[offset_minute]"
);
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct TestCustomFormatPrimitiveDateTimeBad {
#[serde(with = "primitive_date_time_format_bad")]
dt: PrimitiveDateTime,
}
#[test]
fn custom_serialize_bad_type_error() {
let value = TestCustomFormatPrimitiveDateTimeBad {
dt: datetime!(2000-01-01 00:00),
};
assert_ser_tokens_error::<TestCustomFormatPrimitiveDateTimeBad>(
&value,
&[
Token::Struct {
name: "TestCustomFormatPrimitiveDateTimeBad",
len: 1,
},
Token::Str("dt"),
],
"The type being formatted does not contain sufficient information to format a component.",
);
}
// Test the behavior of versioning.
serde::format_description!(version = 1, version_test_1, Time, "[[ [hour]:[minute]");
serde::format_description!(version = 1, version_test_2, Time, r"\\ [hour]:[minute]");
serde::format_description!(version = 2, version_test_3, Time, r"\\ [hour]:[minute]");
serde::format_description!(version, Time, "[hour]:[minute]");
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct TestVersioning {
#[serde(with = "version_test_1")]
time_1: Time,
#[serde(with = "version_test_2")]
time_2: Time,
#[serde(with = "version_test_3")]
time_3: Time,
#[serde(with = "version")]
time_4: Time,
}
#[test]
fn versioning() {
let value = TestVersioning {
time_1: Time::MIDNIGHT,
time_2: Time::MIDNIGHT,
time_3: Time::MIDNIGHT,
time_4: Time::MIDNIGHT,
};
assert_tokens(
&value,
&[
Token::Struct {
name: "TestVersioning",
len: 4,
},
Token::Str("time_1"),
Token::Str("[ 00:00"),
Token::Str("time_2"),
Token::Str(r"\\ 00:00"),
Token::Str("time_3"),
Token::Str(r"\ 00:00"),
Token::Str("time_4"),
Token::Str("00:00"),
Token::StructEnd,
],
);
}
serde::format_description!(
version = 1,
nested_v1,
Time,
"[hour]:[minute][optional [:[second]]]"
);
serde::format_description!(
version = 2,
nested_v2,
Time,
"[hour]:[minute][optional [:[second]]]"
);
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct TestNested {
#[serde(with = "nested_v1")]
time_1: Time,
#[serde(with = "nested_v2")]
time_2: Time,
}
#[test]
fn nested() {
let value = TestNested {
time_1: time!(12:34:56),
time_2: time!(12:34:56),
};
assert_tokens(
&value,
&[
Token::Struct {
name: "TestNested",
len: 2,
},
Token::Str("time_1"),
Token::Str("12:34:56"),
Token::Str("time_2"),
Token::Str("12:34:56"),
Token::StructEnd,
],
);
let expected = TestNested {
time_1: time!(12:34),
time_2: time!(12:34),
};
assert_de_tokens(
&expected,
&[
Token::Struct {
name: "TestNested",
len: 2,
},
Token::Str("time_1"),
Token::Str("12:34"),
Token::Str("time_2"),
Token::Str("12:34"),
Token::StructEnd,
],
);
}

View File

@@ -0,0 +1,996 @@
use serde_test::{
Compact, Configure, Readable, Token, assert_de_tokens, assert_de_tokens_error, assert_tokens,
};
use time::macros::{date, datetime, offset, time};
use time::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
mod error_conditions;
mod iso8601;
mod json;
mod macros;
mod rfc2822;
mod rfc3339;
mod timestamps;
#[test]
fn time() {
assert_tokens(
&Time::MIDNIGHT.compact(),
&[
Token::Tuple { len: 4 },
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(0),
Token::TupleEnd,
],
);
assert_tokens(
&time!(23:58:59.123_456_789).compact(),
&[
Token::Tuple { len: 4 },
Token::U8(23),
Token::U8(58),
Token::U8(59),
Token::U32(123_456_789),
Token::TupleEnd,
],
);
assert_tokens(
&Time::MIDNIGHT.readable(),
&[Token::BorrowedStr("00:00:00.0")],
);
assert_tokens(
&time!(23:58:59.123_456_789).readable(),
&[Token::BorrowedStr("23:58:59.123456789")],
);
}
#[test]
fn time_error() {
assert_de_tokens_error::<Compact<Time>>(
&[
Token::Tuple { len: 4 },
Token::U8(24),
Token::U8(0),
Token::U8(0),
Token::U32(0),
Token::TupleEnd,
],
"invalid hour, expected an in-range value",
);
assert_de_tokens_error::<Readable<Time>>(
&[Token::BorrowedStr("24:00:00.0")],
"the 'hour' component could not be parsed",
);
assert_de_tokens_error::<Readable<Time>>(
&[Token::BorrowedStr("23-00:00.0")],
"a character literal was not valid",
);
assert_de_tokens_error::<Readable<Time>>(
&[Token::BorrowedStr("0:00:00.0")],
"the 'hour' component could not be parsed",
);
assert_de_tokens_error::<Readable<Time>>(
&[Token::BorrowedStr("00:00:00.0x")],
"unexpected trailing characters; the end of input was expected",
);
assert_de_tokens_error::<Readable<Time>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `Time`",
);
assert_de_tokens_error::<Compact<Time>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `Time`",
);
}
#[test]
fn time_partial() {
assert_de_tokens_error::<Compact<Time>>(
&[Token::Tuple { len: 4 }, Token::TupleEnd],
"expected hour",
);
assert_de_tokens_error::<Compact<Time>>(
&[Token::Tuple { len: 4 }, Token::U8(0), Token::TupleEnd],
"expected minute",
);
assert_de_tokens_error::<Compact<Time>>(
&[
Token::Tuple { len: 4 },
Token::U8(0),
Token::U8(0),
Token::TupleEnd,
],
"expected second",
);
assert_de_tokens_error::<Compact<Time>>(
&[
Token::Tuple { len: 4 },
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::TupleEnd,
],
"expected nanosecond",
);
assert_de_tokens_error::<Readable<Time>>(
&[Token::Tuple { len: 4 }, Token::TupleEnd],
"expected hour",
);
assert_de_tokens_error::<Readable<Time>>(
&[Token::Tuple { len: 4 }, Token::U8(0), Token::TupleEnd],
"expected minute",
);
assert_de_tokens_error::<Readable<Time>>(
&[
Token::Tuple { len: 4 },
Token::U8(0),
Token::U8(0),
Token::TupleEnd,
],
"expected second",
);
assert_de_tokens_error::<Readable<Time>>(
&[
Token::Tuple { len: 4 },
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::TupleEnd,
],
"expected nanosecond",
);
}
#[test]
fn date() {
assert_tokens(
&date!(-9999-001).compact(),
&[
Token::Tuple { len: 2 },
Token::I32(-9999),
Token::U16(1),
Token::TupleEnd,
],
);
assert_tokens(
&date!(+9999-365).compact(),
&[
Token::Tuple { len: 2 },
Token::I32(9999),
Token::U16(365),
Token::TupleEnd,
],
);
assert_tokens(
&date!(-9999-001).readable(),
&[Token::BorrowedStr("-9999-01-01")],
);
assert_tokens(
&date!(+9999-365).readable(),
&[Token::BorrowedStr("9999-12-31")],
);
}
#[test]
fn date_error() {
assert_de_tokens_error::<Readable<Date>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `Date`",
);
assert_de_tokens_error::<Compact<Date>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `Date`",
);
}
#[test]
fn date_partial() {
assert_de_tokens_error::<Compact<Date>>(
&[Token::Tuple { len: 2 }, Token::TupleEnd],
"expected year",
);
assert_de_tokens_error::<Compact<Date>>(
&[Token::Tuple { len: 2 }, Token::I32(9999), Token::TupleEnd],
"expected day of year",
);
assert_de_tokens_error::<Readable<Date>>(
&[Token::Tuple { len: 2 }, Token::TupleEnd],
"expected year",
);
assert_de_tokens_error::<Readable<Date>>(
&[Token::Tuple { len: 2 }, Token::I32(9999), Token::TupleEnd],
"expected day of year",
);
}
#[test]
fn primitive_date_time() {
assert_tokens(
&datetime!(-9999-001 0:00).compact(),
&[
Token::Tuple { len: 6 },
Token::I32(-9999),
Token::U16(1),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(0),
Token::TupleEnd,
],
);
assert_tokens(
&datetime!(+9999-365 23:58:59.123_456_789).compact(),
&[
Token::Tuple { len: 6 },
Token::I32(9999),
Token::U16(365),
Token::U8(23),
Token::U8(58),
Token::U8(59),
Token::U32(123_456_789),
Token::TupleEnd,
],
);
assert_tokens(
&datetime!(-9999-001 0:00).readable(),
&[Token::BorrowedStr("-9999-01-01 00:00:00.0")],
);
assert_tokens(
&datetime!(+9999-365 23:58:59.123_456_789).readable(),
&[Token::BorrowedStr("9999-12-31 23:58:59.123456789")],
);
}
#[test]
fn primitive_date_time_error() {
assert_de_tokens_error::<Readable<PrimitiveDateTime>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `PrimitiveDateTime`",
);
assert_de_tokens_error::<Compact<PrimitiveDateTime>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `PrimitiveDateTime`",
);
assert_de_tokens_error::<Compact<PrimitiveDateTime>>(
&[
Token::Tuple { len: 6 },
Token::I32(2021),
Token::U16(366),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(0),
Token::TupleEnd,
],
"invalid ordinal, expected an in-range value",
);
assert_de_tokens_error::<Compact<PrimitiveDateTime>>(
&[
Token::Tuple { len: 6 },
Token::I32(2021),
Token::U16(1),
Token::U8(24),
Token::U8(0),
Token::U8(0),
Token::U32(0),
Token::TupleEnd,
],
"invalid hour, expected an in-range value",
);
}
#[test]
fn primitive_date_time_partial() {
assert_de_tokens_error::<Compact<PrimitiveDateTime>>(
&[Token::Tuple { len: 6 }, Token::TupleEnd],
"expected year",
);
assert_de_tokens_error::<Compact<PrimitiveDateTime>>(
&[Token::Tuple { len: 6 }, Token::I32(9999), Token::TupleEnd],
"expected day of year",
);
assert_de_tokens_error::<Compact<PrimitiveDateTime>>(
&[
Token::Tuple { len: 6 },
Token::I32(9999),
Token::U16(365),
Token::TupleEnd,
],
"expected hour",
);
assert_de_tokens_error::<Compact<PrimitiveDateTime>>(
&[
Token::Tuple { len: 6 },
Token::I32(9999),
Token::U16(365),
Token::U8(23),
Token::TupleEnd,
],
"expected minute",
);
assert_de_tokens_error::<Compact<PrimitiveDateTime>>(
&[
Token::Tuple { len: 6 },
Token::I32(9999),
Token::U16(365),
Token::U8(23),
Token::U8(58),
Token::TupleEnd,
],
"expected second",
);
assert_de_tokens_error::<Compact<PrimitiveDateTime>>(
&[
Token::Tuple { len: 6 },
Token::I32(9999),
Token::U16(365),
Token::U8(23),
Token::U8(58),
Token::U8(59),
Token::TupleEnd,
],
"expected nanosecond",
);
assert_de_tokens_error::<Readable<PrimitiveDateTime>>(
&[Token::Tuple { len: 6 }, Token::TupleEnd],
"expected year",
);
assert_de_tokens_error::<Readable<PrimitiveDateTime>>(
&[Token::Tuple { len: 6 }, Token::I32(9999), Token::TupleEnd],
"expected day of year",
);
assert_de_tokens_error::<Readable<PrimitiveDateTime>>(
&[
Token::Tuple { len: 6 },
Token::I32(9999),
Token::U16(365),
Token::TupleEnd,
],
"expected hour",
);
assert_de_tokens_error::<Readable<PrimitiveDateTime>>(
&[
Token::Tuple { len: 6 },
Token::I32(9999),
Token::U16(365),
Token::U8(23),
Token::TupleEnd,
],
"expected minute",
);
assert_de_tokens_error::<Readable<PrimitiveDateTime>>(
&[
Token::Tuple { len: 6 },
Token::I32(9999),
Token::U16(365),
Token::U8(23),
Token::U8(58),
Token::TupleEnd,
],
"expected second",
);
assert_de_tokens_error::<Readable<PrimitiveDateTime>>(
&[
Token::Tuple { len: 6 },
Token::I32(9999),
Token::U16(365),
Token::U8(23),
Token::U8(58),
Token::U8(59),
Token::TupleEnd,
],
"expected nanosecond",
);
}
#[test]
fn offset_date_time() {
assert_tokens(
&datetime!(-9999-001 0:00 UTC)
.to_offset(offset!(+23:58:59))
.compact(),
&[
Token::Tuple { len: 9 },
Token::I32(-9999),
Token::U16(1),
Token::U8(23),
Token::U8(58),
Token::U8(59),
Token::U32(0),
Token::I8(23),
Token::I8(58),
Token::I8(59),
Token::TupleEnd,
],
);
assert_tokens(
&datetime!(+9999-365 23:58:59.123_456_789 UTC)
.to_offset(offset!(-23:58:59))
.compact(),
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(123_456_789),
Token::I8(-23),
Token::I8(-58),
Token::I8(-59),
Token::TupleEnd,
],
);
assert_tokens(
&datetime!(-9999-001 0:00 UTC)
.to_offset(offset!(+23:58:59))
.readable(),
&[Token::BorrowedStr("-9999-01-01 23:58:59.0 +23:58:59")],
);
assert_tokens(
&datetime!(+9999-365 23:58:59.123_456_789 UTC)
.to_offset(offset!(-23:58:59))
.readable(),
&[Token::BorrowedStr(
"9999-12-31 00:00:00.123456789 -23:58:59",
)],
);
}
#[test]
fn offset_date_time_error() {
assert_de_tokens_error::<Readable<OffsetDateTime>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected an `OffsetDateTime`",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected an `OffsetDateTime`",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(2021),
Token::U16(366),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(0),
Token::I8(0),
Token::I8(0),
Token::I8(0),
Token::TupleEnd,
],
"invalid ordinal, expected an in-range value",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(2021),
Token::U16(1),
Token::U8(24),
Token::U8(0),
Token::U8(0),
Token::U32(0),
Token::I8(0),
Token::I8(0),
Token::I8(0),
Token::TupleEnd,
],
"invalid hour, expected an in-range value",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(2021),
Token::U16(1),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(0),
Token::I8(26),
Token::I8(0),
Token::I8(0),
Token::TupleEnd,
],
"invalid offset hour, expected an in-range value",
);
// the Deserialize impl does not recognize leap second times as valid
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(2021),
Token::U16(365),
Token::U8(23),
Token::U8(59),
Token::U8(60),
Token::U32(0),
Token::I8(0),
Token::I8(0),
Token::I8(0),
Token::TupleEnd,
],
"invalid second, expected an in-range value",
);
assert_de_tokens_error::<Readable<OffsetDateTime>>(
&[Token::BorrowedStr("2021-12-31 23:59:60.0 +00:00:00")],
"second was not in range",
);
}
#[test]
fn offset_date_time_partial() {
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[Token::Tuple { len: 9 }, Token::TupleEnd],
"expected year",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[Token::Tuple { len: 9 }, Token::I32(9999), Token::TupleEnd],
"expected day of year",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::TupleEnd,
],
"expected hour",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::TupleEnd,
],
"expected minute",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::U8(0),
Token::TupleEnd,
],
"expected second",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::TupleEnd,
],
"expected nanosecond",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(123_456_789),
Token::TupleEnd,
],
"expected offset hours",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(123_456_789),
Token::I8(-23),
Token::TupleEnd,
],
"expected offset minutes",
);
assert_de_tokens_error::<Compact<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(123_456_789),
Token::I8(-23),
Token::I8(-58),
Token::TupleEnd,
],
"expected offset seconds",
);
assert_de_tokens_error::<Readable<OffsetDateTime>>(
&[Token::Tuple { len: 9 }, Token::TupleEnd],
"expected year",
);
assert_de_tokens_error::<Readable<OffsetDateTime>>(
&[Token::Tuple { len: 9 }, Token::I32(9999), Token::TupleEnd],
"expected day of year",
);
assert_de_tokens_error::<Readable<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::TupleEnd,
],
"expected hour",
);
assert_de_tokens_error::<Readable<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::TupleEnd,
],
"expected minute",
);
assert_de_tokens_error::<Readable<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::U8(0),
Token::TupleEnd,
],
"expected second",
);
assert_de_tokens_error::<Readable<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::TupleEnd,
],
"expected nanosecond",
);
assert_de_tokens_error::<Readable<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(123_456_789),
Token::TupleEnd,
],
"expected offset hours",
);
assert_de_tokens_error::<Readable<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(123_456_789),
Token::I8(-23),
Token::TupleEnd,
],
"expected offset minutes",
);
assert_de_tokens_error::<Readable<OffsetDateTime>>(
&[
Token::Tuple { len: 9 },
Token::I32(9999),
Token::U16(365),
Token::U8(0),
Token::U8(0),
Token::U8(0),
Token::U32(123_456_789),
Token::I8(-23),
Token::I8(-58),
Token::TupleEnd,
],
"expected offset seconds",
);
}
#[test]
fn utc_offset() {
assert_tokens(
&offset!(-23:58:59).compact(),
&[
Token::Tuple { len: 3 },
Token::I8(-23),
Token::I8(-58),
Token::I8(-59),
Token::TupleEnd,
],
);
assert_tokens(
&offset!(+23:58:59).compact(),
&[
Token::Tuple { len: 3 },
Token::I8(23),
Token::I8(58),
Token::I8(59),
Token::TupleEnd,
],
);
assert_tokens(
&offset!(-23:58:59).readable(),
&[Token::BorrowedStr("-23:58:59")],
);
assert_tokens(
&offset!(+23:58:59).readable(),
&[Token::BorrowedStr("+23:58:59")],
);
}
#[test]
fn utc_offset_error() {
assert_de_tokens_error::<Readable<UtcOffset>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `UtcOffset`",
);
assert_de_tokens_error::<Compact<UtcOffset>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `UtcOffset`",
);
assert_de_tokens_error::<Compact<UtcOffset>>(
&[
Token::Tuple { len: 3 },
Token::I8(26),
Token::I8(0),
Token::I8(0),
Token::TupleEnd,
],
"invalid offset hour, expected an in-range value",
);
}
#[test]
fn utc_offset_partial() {
assert_de_tokens_error::<Compact<UtcOffset>>(
&[Token::Tuple { len: 0 }, Token::TupleEnd],
"expected offset hours",
);
assert_de_tokens_error::<Readable<UtcOffset>>(
&[Token::Tuple { len: 0 }, Token::TupleEnd],
"expected offset hours",
);
let value = offset!(+23);
assert_de_tokens::<Compact<UtcOffset>>(
&value.compact(),
&[Token::Tuple { len: 1 }, Token::I8(23), Token::TupleEnd],
);
let value = offset!(+23);
assert_de_tokens::<Readable<UtcOffset>>(
&value.readable(),
&[Token::Tuple { len: 1 }, Token::I8(23), Token::TupleEnd],
);
let value = offset!(+23:58);
assert_de_tokens::<Compact<UtcOffset>>(
&value.compact(),
&[
Token::Tuple { len: 2 },
Token::I8(23),
Token::I8(58),
Token::TupleEnd,
],
);
let value = offset!(+23:58);
assert_de_tokens::<Readable<UtcOffset>>(
&value.readable(),
&[
Token::Tuple { len: 2 },
Token::I8(23),
Token::I8(58),
Token::TupleEnd,
],
);
}
#[test]
fn duration() {
assert_tokens(
&Duration::MIN.compact(),
&[
Token::Tuple { len: 2 },
Token::I64(i64::MIN),
Token::I32(-999_999_999),
Token::TupleEnd,
],
);
assert_tokens(
&Duration::MAX.compact(),
&[
Token::Tuple { len: 2 },
Token::I64(i64::MAX),
Token::I32(999_999_999),
Token::TupleEnd,
],
);
assert_tokens(
&Duration::MIN.readable(),
&[Token::BorrowedStr("-9223372036854775808.999999999")],
);
assert_tokens(
&Duration::MAX.readable(),
&[Token::BorrowedStr("9223372036854775807.999999999")],
);
assert_tokens(
&Duration::ZERO.readable(),
&[Token::BorrowedStr("0.000000000")],
);
assert_tokens(
&Duration::nanoseconds(123).readable(),
&[Token::BorrowedStr("0.000000123")],
);
assert_tokens(
&Duration::nanoseconds(-123).readable(),
&[Token::BorrowedStr("-0.000000123")],
);
}
#[test]
fn duration_error() {
assert_de_tokens_error::<Readable<Duration>>(
&[Token::BorrowedStr("x")],
r#"invalid value: string "x", expected a decimal point"#,
);
assert_de_tokens_error::<Readable<Duration>>(
&[Token::BorrowedStr("x.0")],
r#"invalid value: string "x", expected seconds"#,
);
assert_de_tokens_error::<Readable<Duration>>(
&[Token::BorrowedStr("0.x")],
r#"invalid value: string "x", expected nanoseconds"#,
);
assert_de_tokens_error::<Readable<Duration>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `Duration`",
);
assert_de_tokens_error::<Compact<Duration>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `Duration`",
);
}
#[test]
fn duration_partial() {
assert_de_tokens_error::<Compact<Duration>>(
&[Token::Tuple { len: 2 }, Token::TupleEnd],
"expected seconds",
);
assert_de_tokens_error::<Compact<Duration>>(
&[
Token::Tuple { len: 2 },
Token::I64(i64::MAX),
Token::TupleEnd,
],
"expected nanoseconds",
);
assert_de_tokens_error::<Readable<Duration>>(
&[Token::Tuple { len: 2 }, Token::TupleEnd],
"expected seconds",
);
assert_de_tokens_error::<Readable<Duration>>(
&[
Token::Tuple { len: 2 },
Token::I64(i64::MAX),
Token::TupleEnd,
],
"expected nanoseconds",
);
}
#[test]
fn weekday() {
use Weekday::*;
assert_tokens(&Monday.compact(), &[Token::U8(1)]);
assert_tokens(&Tuesday.compact(), &[Token::U8(2)]);
assert_tokens(&Wednesday.compact(), &[Token::U8(3)]);
assert_tokens(&Thursday.compact(), &[Token::U8(4)]);
assert_tokens(&Friday.compact(), &[Token::U8(5)]);
assert_tokens(&Saturday.compact(), &[Token::U8(6)]);
assert_tokens(&Sunday.compact(), &[Token::U8(7)]);
assert_tokens(&Monday.readable(), &[Token::BorrowedStr("Monday")]);
assert_tokens(&Tuesday.readable(), &[Token::BorrowedStr("Tuesday")]);
assert_tokens(&Wednesday.readable(), &[Token::BorrowedStr("Wednesday")]);
assert_tokens(&Thursday.readable(), &[Token::BorrowedStr("Thursday")]);
assert_tokens(&Friday.readable(), &[Token::BorrowedStr("Friday")]);
assert_tokens(&Saturday.readable(), &[Token::BorrowedStr("Saturday")]);
assert_tokens(&Sunday.readable(), &[Token::BorrowedStr("Sunday")]);
}
#[test]
fn weekday_error() {
assert_de_tokens_error::<Compact<Weekday>>(
&[Token::U8(0)],
"invalid value: integer `0`, expected a value in the range 1..=7",
);
assert_de_tokens_error::<Readable<Weekday>>(
&[Token::BorrowedStr("NotADay")],
r#"invalid value: string "NotADay", expected a `Weekday`"#,
);
assert_de_tokens_error::<Readable<Weekday>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `Weekday`",
);
assert_de_tokens_error::<Compact<Weekday>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `Weekday`",
);
}
#[test]
fn month() {
use Month::*;
assert_tokens(&January.compact(), &[Token::U8(1)]);
assert_tokens(&February.compact(), &[Token::U8(2)]);
assert_tokens(&March.compact(), &[Token::U8(3)]);
assert_tokens(&April.compact(), &[Token::U8(4)]);
assert_tokens(&May.compact(), &[Token::U8(5)]);
assert_tokens(&June.compact(), &[Token::U8(6)]);
assert_tokens(&July.compact(), &[Token::U8(7)]);
assert_tokens(&August.compact(), &[Token::U8(8)]);
assert_tokens(&September.compact(), &[Token::U8(9)]);
assert_tokens(&October.compact(), &[Token::U8(10)]);
assert_tokens(&November.compact(), &[Token::U8(11)]);
assert_tokens(&December.compact(), &[Token::U8(12)]);
assert_tokens(&January.readable(), &[Token::BorrowedStr("January")]);
assert_tokens(&February.readable(), &[Token::BorrowedStr("February")]);
assert_tokens(&March.readable(), &[Token::BorrowedStr("March")]);
assert_tokens(&April.readable(), &[Token::BorrowedStr("April")]);
assert_tokens(&May.readable(), &[Token::BorrowedStr("May")]);
assert_tokens(&June.readable(), &[Token::BorrowedStr("June")]);
assert_tokens(&July.readable(), &[Token::BorrowedStr("July")]);
assert_tokens(&August.readable(), &[Token::BorrowedStr("August")]);
assert_tokens(&September.readable(), &[Token::BorrowedStr("September")]);
assert_tokens(&October.readable(), &[Token::BorrowedStr("October")]);
assert_tokens(&November.readable(), &[Token::BorrowedStr("November")]);
assert_tokens(&December.readable(), &[Token::BorrowedStr("December")]);
}
#[test]
fn month_error() {
assert_de_tokens_error::<Compact<Month>>(
&[Token::U8(0)],
"invalid value: integer `0`, expected a value in the range 1..=12",
);
assert_de_tokens_error::<Readable<Month>>(
&[Token::BorrowedStr("NotAMonth")],
r#"invalid value: string "NotAMonth", expected a `Month`"#,
);
assert_de_tokens_error::<Readable<Month>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `Month`",
);
assert_de_tokens_error::<Compact<Month>>(
&[Token::Bool(false)],
"invalid type: boolean `false`, expected a `Month`",
);
}

View File

@@ -0,0 +1,56 @@
use serde::{Deserialize, Serialize};
use serde_test::{assert_tokens, Configure, Token};
use time::serde::rfc2822;
use time::OffsetDateTime;
use time_macros::datetime;
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct Test {
#[serde(with = "rfc2822")]
dt: OffsetDateTime,
#[serde(with = "rfc2822::option")]
option_dt: Option<OffsetDateTime>,
}
#[test]
fn serialize_deserialize() {
let value = Test {
dt: datetime!(2000-01-01 00:00:00 UTC),
option_dt: Some(datetime!(2000-01-01 00:00:00 UTC)),
};
assert_tokens(
&value.compact(),
&[
Token::Struct {
name: "Test",
len: 2,
},
Token::Str("dt"),
Token::BorrowedStr("Sat, 01 Jan 2000 00:00:00 +0000"),
Token::Str("option_dt"),
Token::Some,
Token::BorrowedStr("Sat, 01 Jan 2000 00:00:00 +0000"),
Token::StructEnd,
],
);
}
#[test]
fn parse_json() -> serde_json::Result<()> {
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
#[serde(untagged)]
enum Wrapper {
A(Test),
}
assert_eq!(
serde_json::from_str::<Wrapper>(
r#"{"dt": "Sat, 01 Jan 2000 00:00:00 +0000", "option_dt": null}"#
)?,
Wrapper::A(Test {
dt: datetime!(2000-01-01 00:00:00 UTC),
option_dt: None,
})
);
Ok(())
}

View File

@@ -0,0 +1,141 @@
use serde::{Deserialize, Serialize};
use serde_test::{
assert_de_tokens_error, assert_ser_tokens_error, assert_tokens, Configure, Token,
};
use time::macros::datetime;
use time::serde::rfc3339;
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct Test {
#[serde(with = "rfc3339")]
dt: OffsetDateTime,
#[serde(with = "rfc3339::option")]
option_dt: Option<OffsetDateTime>,
}
#[test]
fn serialize_deserialize() {
let value = Test {
dt: datetime!(2000-01-01 00:00:00 UTC),
option_dt: Some(datetime!(2000-01-01 00:00:00 UTC)),
};
assert_tokens(
&value.compact(),
&[
Token::Struct {
name: "Test",
len: 2,
},
Token::Str("dt"),
Token::BorrowedStr("2000-01-01T00:00:00Z"),
Token::Str("option_dt"),
Token::Some,
Token::BorrowedStr("2000-01-01T00:00:00Z"),
Token::StructEnd,
],
);
let value = Test {
dt: datetime!(2000-01-01 00:00:00 UTC),
option_dt: None,
};
assert_tokens(
&value.compact(),
&[
Token::Struct {
name: "Test",
len: 2,
},
Token::Str("dt"),
Token::BorrowedStr("2000-01-01T00:00:00Z"),
Token::Str("option_dt"),
Token::None,
Token::StructEnd,
],
);
assert_de_tokens_error::<Test>(
&[
Token::Struct {
name: "Test",
len: 2,
},
Token::Str("dt"),
Token::BorrowedStr("bad"),
Token::StructEnd,
],
"the 'year' component could not be parsed",
);
let value = Test {
dt: datetime!(2000-01-01 00:00:00 +00:00:01),
option_dt: None,
};
assert_ser_tokens_error::<Test>(
&value,
&[
Token::Struct {
name: "Test",
len: 2,
},
Token::Str("dt"),
],
"The offset_second component cannot be formatted into the requested format.",
);
}
#[test]
fn parse_json() -> serde_json::Result<()> {
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
#[serde(untagged)]
enum Wrapper {
A(Test),
}
assert_eq!(
serde_json::from_str::<Wrapper>("{\"dt\": \"2000-01-01T00:00:00Z\", \"option_dt\": null}")?,
Wrapper::A(Test {
dt: datetime!(2000-01-01 00:00:00 UTC),
option_dt: None,
})
);
Ok(())
}
#[test]
fn issue_479() -> serde_json::Result<()> {
const A: &str = r#"{
"date": "2022-05-01T10:20:42.123Z"
}"#;
const B: &str = r#"{
"date": "2022-05-01T10:20:42.123Z",
"maybe_date": "2022-05-01T10:20:42.123Z"
}"#;
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct S {
#[serde(with = "time::serde::rfc3339")]
date: OffsetDateTime,
#[serde(with = "time::serde::rfc3339::option", default)]
maybe_date: Option<OffsetDateTime>,
}
let a = serde_json::from_str::<S>(A)?;
let b = serde_json::from_str::<S>(B)?;
assert_eq!(
a,
S {
date: datetime!(2022-05-01 10:20:42.123 UTC),
maybe_date: None
}
);
assert_eq!(
b,
S {
date: datetime!(2022-05-01 10:20:42.123 UTC),
maybe_date: Some(datetime!(2022-05-01 10:20:42.123 UTC))
}
);
Ok(())
}

View File

@@ -0,0 +1,138 @@
use serde::{Deserialize, Serialize};
use serde_test::{assert_de_tokens_error, assert_tokens, Configure, Token};
use time::macros::datetime;
use time::serde::timestamp;
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Test {
#[serde(with = "timestamp")]
dt: OffsetDateTime,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct TestMilliseconds {
#[serde(with = "timestamp::milliseconds")]
dt: OffsetDateTime,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct TestMicroseconds {
#[serde(with = "timestamp::microseconds")]
dt: OffsetDateTime,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct TestNanoseconds {
#[serde(with = "timestamp::nanoseconds")]
dt: OffsetDateTime,
}
#[test]
fn serialize_timestamp() {
let value = Test {
dt: datetime!(2000-01-01 00:00:00 UTC),
};
assert_tokens(
&value.compact(),
&[
Token::Struct {
name: "Test",
len: 1,
},
Token::Str("dt"),
Token::I64(946684800),
Token::StructEnd,
],
);
assert_de_tokens_error::<Test>(
&[
Token::Struct {
name: "Test",
len: 1,
},
Token::Str("dt"),
Token::Str("bad"),
Token::StructEnd,
],
"invalid type: string \"bad\", expected i64",
);
}
#[test]
fn serialize_timestamp_milliseconds() -> serde_json::Result<()> {
let value_milliseconds = TestMilliseconds {
dt: datetime!(2000-01-01 00:00:00.999 UTC),
};
assert_de_tokens_error::<TestMilliseconds>(
&[
Token::Struct {
name: "TestMilliseconds",
len: 1,
},
Token::Str("dt"),
Token::Str("bad"),
Token::StructEnd,
],
"invalid type: string \"bad\", expected i128",
);
// serde_test does not support I128, see: https://github.com/serde-rs/test/issues/18
let milliseconds_str = r#"{"dt":946684800999}"#;
let deserialized_milliseconds: TestMilliseconds = serde_json::from_str(milliseconds_str)?;
let serialized_milliseconds = serde_json::to_string(&value_milliseconds)?;
assert_eq!(value_milliseconds.dt, deserialized_milliseconds.dt);
assert_eq!(serialized_milliseconds, milliseconds_str);
Ok(())
}
#[test]
fn serialize_timestamp_microseconds() -> serde_json::Result<()> {
let value_microseconds = TestMicroseconds {
dt: datetime!(2000-01-01 00:00:00.999_999 UTC),
};
assert_de_tokens_error::<TestMicroseconds>(
&[
Token::Struct {
name: "TestMicroseconds",
len: 1,
},
Token::Str("dt"),
Token::Str("bad"),
Token::StructEnd,
],
"invalid type: string \"bad\", expected i128",
);
// serde_test does not support I128, see: https://github.com/serde-rs/test/issues/18
let microseconds_str = r#"{"dt":946684800999999}"#;
let deserialized_microseconds: TestMicroseconds = serde_json::from_str(microseconds_str)?;
let serialized_microseconds = serde_json::to_string(&value_microseconds)?;
assert_eq!(value_microseconds.dt, deserialized_microseconds.dt);
assert_eq!(serialized_microseconds, microseconds_str);
Ok(())
}
#[test]
fn serialize_timestamp_nanoseconds() -> serde_json::Result<()> {
let value_nanoseconds = TestNanoseconds {
dt: datetime!(2000-01-01 00:00:00.999_999_999 UTC),
};
assert_de_tokens_error::<TestNanoseconds>(
&[
Token::Struct {
name: "TestNanoseconds",
len: 1,
},
Token::Str("dt"),
Token::Str("bad"),
Token::StructEnd,
],
"invalid type: string \"bad\", expected i128",
);
// serde_test does not support I128, see: https://github.com/serde-rs/test/issues/18
let nanoseconds_str = r#"{"dt":946684800999999999}"#;
let deserialized_nanoseconds: TestNanoseconds = serde_json::from_str(nanoseconds_str)?;
let serialized_nanoseconds = serde_json::to_string(&value_nanoseconds)?;
assert_eq!(value_nanoseconds.dt, deserialized_nanoseconds.dt);
assert_eq!(serialized_nanoseconds, nanoseconds_str);
Ok(())
}