1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
//! Serialize/deserialize Timestamp type from and into string:
use core::fmt;
use serde::{de::Error as _, ser::Error, Deserialize, Deserializer, Serialize, Serializer};
use time::{
format_description::well_known::Rfc3339 as Rfc3339Format, macros::offset, OffsetDateTime,
};
use crate::{google::protobuf::Timestamp, prelude::*};
/// Helper struct to serialize and deserialize Timestamp into an RFC3339-compatible string
/// This is required because the serde `with` attribute is only available to fields of a struct but
/// not the whole struct.
#[derive(Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Rfc3339(#[serde(with = "crate::serializers::timestamp")] Timestamp);
impl From<Timestamp> for Rfc3339 {
fn from(value: Timestamp) -> Self {
Rfc3339(value)
}
}
impl From<Rfc3339> for Timestamp {
fn from(value: Rfc3339) -> Self {
value.0
}
}
/// Deserialize string into Timestamp
pub fn deserialize<'de, D>(deserializer: D) -> Result<Timestamp, D::Error>
where
D: Deserializer<'de>,
{
let value_string = String::deserialize(deserializer)?;
let t = OffsetDateTime::parse(&value_string, &Rfc3339Format).map_err(D::Error::custom)?;
let t = t.to_offset(offset!(UTC));
if !matches!(t.year(), 1..=9999) {
return Err(D::Error::custom("date is out of range"));
}
let seconds = t.unix_timestamp();
// Safe to convert to i32 because .nanosecond()
// is guaranteed to return a value in 0..1_000_000_000 range.
let nanos = t.nanosecond() as i32;
Ok(Timestamp { seconds, nanos })
}
/// Serialize from Timestamp into string
pub fn serialize<S>(value: &Timestamp, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if value.nanos < 0 || value.nanos > 999_999_999 {
return Err(S::Error::custom("invalid nanoseconds in time"));
}
let total_nanos = value.seconds as i128 * 1_000_000_000 + value.nanos as i128;
let datetime = OffsetDateTime::from_unix_timestamp_nanos(total_nanos)
.map_err(|_| S::Error::custom("invalid time"))?;
to_rfc3339_nanos(datetime).serialize(serializer)
}
/// Serialization helper for converting an [`OffsetDateTime`] object to a string.
///
/// This reproduces the behavior of Go's `time.RFC3339Nano` format,
/// ie. a RFC3339 date-time with left-padded subsecond digits without
/// trailing zeros and no trailing dot.
pub fn to_rfc3339_nanos(t: OffsetDateTime) -> String {
// Can't use OffsetDateTime::format because the feature enabling it
// currently requires std (https://github.com/time-rs/time/issues/400)
// Preallocate enough string capacity to fit the shortest possible form,
// yyyy-mm-ddThh:mm:ssZ
let mut buf = String::with_capacity(20);
fmt_as_rfc3339_nanos(t, &mut buf).unwrap();
buf
}
/// Helper for formatting an [`OffsetDateTime`] value.
///
/// This function can be used to efficiently format date-time values
/// in [`Display`] or [`Debug`] implementations.
///
/// The format reproduces Go's `time.RFC3339Nano` format,
/// ie. a RFC3339 date-time with left-padded subsecond digits without
/// trailing zeros and no trailing dot.
///
/// [`Display`]: core::fmt::Display
/// [`Debug`]: core::fmt::Debug
pub fn fmt_as_rfc3339_nanos(t: OffsetDateTime, f: &mut impl fmt::Write) -> fmt::Result {
let t = t.to_offset(offset!(UTC));
let nanos = t.nanosecond();
if nanos == 0 {
write!(
f,
"{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z",
year = t.year(),
month = t.month() as u8,
day = t.day(),
hour = t.hour(),
minute = t.minute(),
second = t.second(),
)
} else {
let mut secfrac = nanos;
let mut secfrac_width = 9;
while secfrac % 10 == 0 {
secfrac /= 10;
secfrac_width -= 1;
}
write!(
f,
"{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{secfrac:0sfw$}Z",
year = t.year(),
month = t.month() as u8,
day = t.day(),
hour = t.hour(),
minute = t.minute(),
second = t.second(),
secfrac = secfrac,
sfw = secfrac_width,
)
}
}
#[allow(warnings)]
#[cfg(test)]
mod test {
use serde::{Deserialize, Serialize};
use super::*;
use crate::google::protobuf::Timestamp;
// The Go code with which the following timestamps
// were tested is as follows:
//
// ```go
// package main
//
// import (
// "fmt"
// "time"
// )
//
// func main() {
// timestamps := []string{
// "1970-01-01T00:00:00Z",
// "0001-01-01T00:00:00Z",
// "2020-09-14T16:33:00Z",
// "2020-09-14T16:33:00.1Z",
// "2020-09-14T16:33:00.211914212Z",
// "2020-09-14T16:33:54.21191421Z",
// "2021-01-07T20:25:56.045576Z",
// "2021-01-07T20:25:57.039219Z",
// "2021-01-07T20:26:05.00509Z",
// "2021-01-07T20:26:05.005096Z",
// "2021-01-07T20:26:05.0005096Z",
// }
// for _, timestamp := range timestamps {
// ts, err := time.Parse(time.RFC3339Nano, timestamp)
// if err != nil {
// panic(err)
// }
// tss := ts.Format(time.RFC3339Nano)
// if timestamp != tss {
// panic(fmt.Sprintf("\nExpected : %s\nActual : %s", timestamp, tss))
// }
// }
// fmt.Println("All good!")
// }
// ```
#[test]
fn json_timestamp_precision() {
let test_timestamps = vec![
"1970-01-01T00:00:00Z",
"0001-01-01T00:00:00Z",
"2020-09-14T16:33:00Z",
"2020-09-14T16:33:00.1Z",
"2020-09-14T16:33:00.211914212Z",
"2020-09-14T16:33:54.21191421Z",
"2021-01-07T20:25:56.045576Z",
"2021-01-07T20:25:57.039219Z",
"2021-01-07T20:26:05.00509Z",
"2021-01-07T20:26:05.005096Z",
"2021-01-07T20:26:05.0005096Z",
];
for timestamp in test_timestamps {
let json = format!("\"{}\"", timestamp);
let rfc = serde_json::from_str::<Rfc3339>(&json).unwrap();
assert_eq!(json, serde_json::to_string(&rfc).unwrap());
}
}
}