Skip to content

Commit b291b1d

Browse files
committed
Use RFC 3339 date formats for serde.
1 parent d8e43e5 commit b291b1d

File tree

10 files changed

+165
-61
lines changed

10 files changed

+165
-61
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ serde = [
2828
"dep:serde",
2929
"dep:serde_with",
3030
"dep:cfg_eval",
31-
"time/serde-human-readable",
31+
"time/serde",
3232
"language-tags?/serde",
3333
]
3434
zeroize = ["dep:zeroize"]

src/builder.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
//!
33
use crate::{
44
property::{DeliveryAddress, Gender, Kind, TextListProperty},
5-
Uri, Vcard,
5+
Date, DateTime, Uri, Vcard,
66
};
7-
use time::{Date, OffsetDateTime};
87

98
#[cfg(feature = "language-tags")]
109
use language_tags::LanguageTag;
@@ -226,7 +225,7 @@ impl VcardBuilder {
226225
}
227226

228227
/// Set the revision of the vCard.
229-
pub fn rev(mut self, value: OffsetDateTime) -> Self {
228+
pub fn rev(mut self, value: DateTime) -> Self {
230229
self.card.rev = Some(value.into());
231230
self
232231
}
@@ -315,10 +314,14 @@ mod tests {
315314
.nickname("JC".to_owned())
316315
.photo("file:///images/jdoe.jpeg".parse().unwrap())
317316
.birthday(
318-
Date::from_calendar_date(1986, Month::February, 7).unwrap(),
317+
Date::from_calendar_date(1986, Month::February, 7)
318+
.unwrap()
319+
.into(),
319320
)
320321
.anniversary(
321-
Date::from_calendar_date(2002, Month::March, 18).unwrap(),
322+
Date::from_calendar_date(2002, Month::March, 18)
323+
.unwrap()
324+
.into(),
322325
)
323326
.gender("F")
324327
.address(DeliveryAddress {
@@ -347,7 +350,7 @@ mod tests {
347350
.categories(vec!["Medical".to_owned(), "Health".to_owned()])
348351
.note("Saved my life!".to_owned())
349352
.prod_id("Contact App v1".to_owned())
350-
.rev(rev)
353+
.rev(rev.into())
351354
.sound("https://example.com/janedoe.wav".parse().unwrap())
352355
.uid(
353356
"urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6"

src/date_time.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use crate::Error;
2+
use std::{fmt, str::FromStr};
3+
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
4+
5+
#[cfg(feature = "serde")]
6+
use serde_with::{serde_as, DeserializeFromStr, SerializeDisplay};
7+
8+
/// Date and time that serializes to and from RFC3339.
9+
#[derive(Debug, Clone, PartialEq, Eq)]
10+
#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)]
11+
#[cfg_attr(feature = "serde", derive(DeserializeFromStr, SerializeDisplay))]
12+
pub struct DateTime(OffsetDateTime);
13+
14+
impl DateTime {
15+
/// Create UTC date and time.
16+
pub fn now_utc() -> Self {
17+
Self(OffsetDateTime::now_utc())
18+
}
19+
}
20+
21+
impl From<OffsetDateTime> for DateTime {
22+
fn from(value: OffsetDateTime) -> Self {
23+
Self(value)
24+
}
25+
}
26+
27+
impl From<DateTime> for OffsetDateTime {
28+
fn from(value: DateTime) -> Self {
29+
value.0
30+
}
31+
}
32+
33+
impl AsRef<OffsetDateTime> for DateTime {
34+
fn as_ref(&self) -> &OffsetDateTime {
35+
&self.0
36+
}
37+
}
38+
39+
impl fmt::Display for DateTime {
40+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41+
write!(
42+
f,
43+
"{}",
44+
self.0.format(&Rfc3339).map_err(|_| fmt::Error::default())?
45+
)
46+
}
47+
}
48+
49+
impl FromStr for DateTime {
50+
type Err = Error;
51+
52+
fn from_str(s: &str) -> Result<Self, Self::Err> {
53+
Ok(Self(OffsetDateTime::parse(s, &Rfc3339)?))
54+
}
55+
}
56+
57+
/// Date that serializes to and from RFC3339.
58+
#[derive(Debug, Clone, PartialEq, Eq)]
59+
#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)]
60+
#[cfg_attr(feature = "serde", derive(DeserializeFromStr, SerializeDisplay))]
61+
pub struct Date(time::Date);
62+
63+
impl From<time::Date> for Date {
64+
fn from(value: time::Date) -> Self {
65+
Self(value)
66+
}
67+
}
68+
69+
impl From<Date> for time::Date {
70+
fn from(value: Date) -> Self {
71+
value.0
72+
}
73+
}
74+
75+
impl AsRef<time::Date> for Date {
76+
fn as_ref(&self) -> &time::Date {
77+
&self.0
78+
}
79+
}
80+
81+
impl fmt::Display for Date {
82+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83+
write!(f, "{}", self.0.to_string(),)
84+
}
85+
}
86+
87+
impl FromStr for Date {
88+
type Err = Error;
89+
90+
fn from_str(s: &str) -> Result<Self, Self::Err> {
91+
Ok(Self(OffsetDateTime::parse(s, &Rfc3339)?.date()))
92+
}
93+
}

src/helper.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use time::{
55
Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset,
66
};
77

8-
use crate::{property::DateAndOrTime, Error, Result};
8+
use crate::{property::DateAndOrTime, DateTime, Error, Result};
99

1010
// UTC OFFSET
1111

@@ -196,14 +196,14 @@ fn do_parse_date(s: &str) -> Result<Date> {
196196
}
197197
}
198198

199-
pub(crate) fn format_date(value: &Date) -> Result<String> {
199+
pub(crate) fn format_date(value: &crate::Date) -> Result<String> {
200200
let date = format_description::parse("[year][month][day]")?;
201-
Ok(value.format(&date)?)
201+
Ok(value.as_ref().format(&date)?)
202202
}
203203

204204
pub(crate) fn format_date_list(
205205
f: &mut fmt::Formatter<'_>,
206-
val: &[Date],
206+
val: &[crate::Date],
207207
) -> fmt::Result {
208208
for (index, item) in val.iter().enumerate() {
209209
write!(f, "{}", &format_date(item).map_err(|_| fmt::Error)?)?;
@@ -217,7 +217,7 @@ pub(crate) fn format_date_list(
217217
// DATETIME
218218

219219
/// Parse a list of date times separated by a comma.
220-
pub fn parse_date_time_list(value: &str) -> Result<Vec<OffsetDateTime>> {
220+
pub fn parse_date_time_list(value: &str) -> Result<Vec<DateTime>> {
221221
let mut values = Vec::new();
222222
for value in value.split(',') {
223223
values.push(parse_date_time(value)?);
@@ -226,7 +226,7 @@ pub fn parse_date_time_list(value: &str) -> Result<Vec<OffsetDateTime>> {
226226
}
227227

228228
/// Parse a date time.
229-
pub fn parse_date_time(value: &str) -> Result<OffsetDateTime> {
229+
pub fn parse_date_time(value: &str) -> Result<DateTime> {
230230
let mut it = value.splitn(2, 'T');
231231
let date = it
232232
.next()
@@ -242,10 +242,11 @@ pub fn parse_date_time(value: &str) -> Result<OffsetDateTime> {
242242
.replace_date(date)
243243
.replace_time(time)
244244
.replace_offset(offset);
245-
Ok(utc)
245+
Ok(utc.into())
246246
}
247247

248-
pub(crate) fn format_date_time(d: &OffsetDateTime) -> Result<String> {
248+
pub(crate) fn format_date_time(d: &DateTime) -> Result<String> {
249+
let d = d.as_ref();
249250
let offset = (*d).offset();
250251

251252
let format = if offset == UtcOffset::UTC {
@@ -263,7 +264,7 @@ pub(crate) fn format_date_time(d: &OffsetDateTime) -> Result<String> {
263264

264265
pub(crate) fn format_date_time_list(
265266
f: &mut fmt::Formatter<'_>,
266-
val: &[OffsetDateTime],
267+
val: &[DateTime],
267268
) -> fmt::Result {
268269
for (index, item) in val.iter().enumerate() {
269270
write!(f, "{}", &format_date_time(item).map_err(|_| fmt::Error)?)?;
@@ -277,7 +278,7 @@ pub(crate) fn format_date_time_list(
277278
// TIMESTAMP
278279

279280
/// Parse a timestamp.
280-
pub fn parse_timestamp(value: &str) -> Result<OffsetDateTime> {
281+
pub fn parse_timestamp(value: &str) -> Result<DateTime> {
281282
let offset_format = format_description::parse(
282283
"[year][month][day]T[hour][minute][second][offset_hour sign:mandatory][offset_minute]",
283284
)?;
@@ -292,24 +293,24 @@ pub fn parse_timestamp(value: &str) -> Result<OffsetDateTime> {
292293
)?;
293294

294295
if let Ok(result) = OffsetDateTime::parse(value, &offset_format) {
295-
Ok(result)
296+
Ok(result.into())
296297
} else if let Ok(result) =
297-
OffsetDateTime::parse(value, &offset_format_hours)
298+
OffsetDateTime::parse(value, &offset_format_hours).into()
298299
{
299-
Ok(result)
300+
Ok(result.into())
300301
} else if let Ok(result) = PrimitiveDateTime::parse(value, &utc_format) {
301302
let result = OffsetDateTime::now_utc().replace_date_time(result);
302-
Ok(result)
303+
Ok(result.into())
303304
} else {
304305
let result = PrimitiveDateTime::parse(value, &implicit_utc_format)?;
305306
let result = OffsetDateTime::now_utc().replace_date_time(result);
306-
Ok(result)
307+
Ok(result.into())
307308
}
308309
}
309310

310311
pub(crate) fn format_timestamp_list(
311312
f: &mut fmt::Formatter<'_>,
312-
val: &[OffsetDateTime],
313+
val: &[DateTime],
313314
) -> fmt::Result {
314315
for (index, item) in val.iter().enumerate() {
315316
write!(f, "{}", &format_date_time(item).map_err(|_| fmt::Error)?)?;
@@ -321,7 +322,7 @@ pub(crate) fn format_timestamp_list(
321322
}
322323

323324
/// Parse a list of date and or time types possibly separated by a comma.
324-
pub fn parse_timestamp_list(value: &str) -> Result<Vec<OffsetDateTime>> {
325+
pub fn parse_timestamp_list(value: &str) -> Result<Vec<DateTime>> {
325326
let mut values = Vec::new();
326327
for value in value.split(',') {
327328
values.push(parse_timestamp(value)?);

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
//!
9696
9797
mod builder;
98+
mod date_time;
9899
mod error;
99100
pub mod helper;
100101
mod iter;
@@ -112,6 +113,7 @@ pub use error::Error;
112113
pub use iter::VcardIterator;
113114
pub use vcard::Vcard;
114115

116+
pub use date_time::{Date, DateTime};
115117
pub use time;
116118
pub use uri::Uri;
117119

src/parser.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -938,9 +938,12 @@ impl<'s> VcardParser<'s> {
938938
ValueType::Boolean => {
939939
AnyProperty::Boolean(parse_boolean(value.as_ref())?)
940940
}
941-
ValueType::Date => {
942-
AnyProperty::Date(parse_date_list(value.as_ref())?)
943-
}
941+
ValueType::Date => AnyProperty::Date(
942+
parse_date_list(value.as_ref())?
943+
.into_iter()
944+
.map(crate::Date::from)
945+
.collect(),
946+
),
944947
ValueType::DateTime => AnyProperty::DateTime(
945948
parse_date_time_list(value.as_ref())?,
946949
),

0 commit comments

Comments
 (0)