#[non_exhaustive]pub struct UTCDate(/* private fields */);Expand description
RFC 3339 UTC timestamp string (RFC 8620 §1.4).
Format: YYYY-MM-DDTHH:MM:SSZ — time-offset MUST be Z, letters uppercase,
fractional seconds omitted if zero. Example: "2014-10-30T06:12:00Z".
§Deserialization is NOT validated
UTCDate is #[serde(transparent)] over String. Any string that
deserializes into a String deserializes into a UTCDate — including
"not-a-date", "2024-01-19T18:00:00" (no Z suffix), or any other
shape that violates RFC 8620 §1.4. The newtype carries the
type-level intent of “RFC 8620 UTC timestamp” but does NOT enforce
it at the wire boundary.
Use UTCDate::new_validated when constructing from untrusted input,
or call UTCDate::to_epoch_seconds when consuming a deserialized
value: the latter re-validates structural format AND semantic ranges
(month, day, hour, minute, second) and returns ValidationError on
any deviation. Treat any field of type Option<UTCDate> arriving
from a peer as “RFC 8620 timestamp by convention, not by contract”.
Implementations§
Source§impl UTCDate
impl UTCDate
Sourcepub fn into_inner(self) -> String
pub fn into_inner(self) -> String
Consumes the value and returns the inner String.
Source§impl UTCDate
impl UTCDate
Sourcepub fn new_validated(s: impl Into<String>) -> Result<Self, ValidationError>
pub fn new_validated(s: impl Into<String>) -> Result<Self, ValidationError>
Construct a UTCDate with RFC 8620 §1.4 validation.
Validation has two layers:
- Shape: exactly 20 characters in the
YYYY-MM-DDTHH:MM:SSZlayout withZsuffix and ASCII digits in every numeric position. - Values: month
1..=12; day1..=days_in_month(year, month)with proleptic Gregorian leap-year rules so e.g.2024-02-29is accepted but2023-02-29and2024-02-30are rejected; hour0..=23; minute0..=59; second0..=59(RFC 8620 §1.4 does not permit leap seconds even though RFC 3339 §5.6 does).
§Errors
Returns ValidationError when the input does not satisfy
RFC 8620 §1.4. The error description contains a substring that
callers can match on if they need finer-grained handling:
"exactly 20 characters"— wrong length;"wrong structure"— separators orZsuffix missing / misplaced;"is not a digit"— non-digit at a numeric position;"month must be"— month outside1..=12;"day must be"— day outside1..=days_in_month(year, month)(catches both out-of-range day numbers like32and non-existent dates like Feb 30 or Feb 29 in a non-leap year);"hour must be","minute must be","second must be"— the corresponding time component is out of range.
Use UTCDate::from when the value is known to be valid.
Sourcepub fn to_epoch_seconds(&self) -> Result<i64, ValidationError>
pub fn to_epoch_seconds(&self) -> Result<i64, ValidationError>
Convert this UTCDate to seconds since the Unix epoch
(1970-01-01T00:00:00Z).
Re-validates the structural RFC 8620 §1.4 format (the value may have
been constructed via UTCDate::from without validation) and also
validates semantic ranges: month 1..=12, day 1..=days_in_month,
hour 0..=23, minute 0..=59, second 0..=59 (no leap seconds).
Returns ValidationError on any validation failure.
Negative values are returned for dates before 1970-01-01T00:00:00Z.
Uses the proleptic Gregorian calendar via Hinnant’s days_from_civil
algorithm, which is exact-integer and handles leap years and century
rules correctly. No external dependencies.
§Examples
use jmap_types::UTCDate;
let d = UTCDate::new_validated("1970-01-01T00:00:00Z").unwrap();
assert_eq!(d.to_epoch_seconds().unwrap(), 0);
let rfc = UTCDate::new_validated("2014-10-30T06:12:00Z").unwrap();
assert_eq!(rfc.to_epoch_seconds().unwrap(), 1_414_649_520);