#[cfg(feature = "alloc")]
mod owned;
use core::{
convert::TryFrom,
fmt,
ops::{self, RangeFrom, RangeTo},
str,
};
#[cfg(feature = "serde")]
use serde::Serialize;
use crate::error::{Error, ErrorKind};
use super::{FullDateStr, FullTimeStr};
#[cfg(feature = "alloc")]
pub use self::owned::DateTimeString;
const DATETIME_LEN_MIN: usize = 20;
const T_POS: usize = 10;
const DATE_RANGE: RangeTo<usize> = ..T_POS;
const TIME_RANGE: RangeFrom<usize> = (T_POS + 1)..;
fn validate_bytes(s: &[u8]) -> Result<(), Error> {
if s.len() < DATETIME_LEN_MIN {
return Err(ErrorKind::TooShort.into());
}
if s[T_POS] != b'T' {
return Err(ErrorKind::InvalidSeparator.into());
}
FullDateStr::from_bytes(&s[DATE_RANGE])?;
FullTimeStr::from_bytes(&s[TIME_RANGE])?;
Ok(())
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[allow(clippy::derive_hash_xor_eq)]
#[allow(unknown_lints, clippy::derive_ord_xor_partial_ord)]
pub struct DateTimeStr([u8]);
impl DateTimeStr {
#[inline]
#[must_use]
unsafe fn from_bytes_maybe_unchecked(s: &[u8]) -> &Self {
debug_assert_ok!(validate_bytes(s));
&*(s as *const [u8] as *const Self)
}
#[inline]
#[must_use]
unsafe fn from_bytes_maybe_unchecked_mut(s: &mut [u8]) -> &mut Self {
debug_assert_ok!(validate_bytes(s));
&mut *(s as *mut [u8] as *mut Self)
}
#[inline]
#[must_use]
unsafe fn from_str_maybe_unchecked_mut(s: &mut str) -> &mut Self {
Self::from_bytes_maybe_unchecked_mut(s.as_bytes_mut())
}
#[inline]
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Result<&Self, Error> {
TryFrom::try_from(s)
}
#[inline]
pub fn from_mut_str(s: &mut str) -> Result<&mut Self, Error> {
TryFrom::try_from(s)
}
#[inline]
pub fn from_bytes(s: &[u8]) -> Result<&Self, Error> {
TryFrom::try_from(s)
}
#[inline]
pub fn from_bytes_mut(s: &mut [u8]) -> Result<&mut Self, Error> {
TryFrom::try_from(s)
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
unsafe {
debug_assert_safe_version_ok!(str::from_utf8(&self.0));
str::from_utf8_unchecked(&self.0)
}
}
#[inline]
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
#[inline]
#[must_use]
pub fn decompose(&self) -> (&FullDateStr, &FullTimeStr) {
let date = unsafe {
debug_assert_safe_version_ok!(<FullDateStr>::from_bytes(&self.0[DATE_RANGE]));
FullDateStr::from_bytes_maybe_unchecked(self.0.get_unchecked(DATE_RANGE))
};
let time = unsafe {
debug_assert_safe_version_ok!(<FullTimeStr>::from_bytes(&self.0[TIME_RANGE]));
FullTimeStr::from_bytes_maybe_unchecked(self.0.get_unchecked(TIME_RANGE))
};
(date, time)
}
#[inline]
#[must_use]
pub fn decompose_mut(&mut self) -> (&mut FullDateStr, &mut FullTimeStr) {
debug_assert_ok!(<FullDateStr>::from_bytes(&self.0[..T_POS]));
debug_assert_ok!(<FullTimeStr>::from_bytes(&self.0[(T_POS + 1)..]));
unsafe {
let (date, t_time) = self.0.split_at_mut(T_POS);
let time = t_time.get_unchecked_mut(1..);
let date = FullDateStr::from_bytes_maybe_unchecked_mut(date);
let time = FullTimeStr::from_bytes_maybe_unchecked_mut(time);
(date, time)
}
}
#[inline]
#[must_use]
pub fn date(&self) -> &FullDateStr {
unsafe {
debug_assert_safe_version_ok!(FullDateStr::from_bytes(&self.0[DATE_RANGE]));
let s = self.0.get_unchecked(DATE_RANGE);
FullDateStr::from_bytes_maybe_unchecked(s)
}
}
#[inline]
#[must_use]
pub fn date_mut(&mut self) -> &mut FullDateStr {
unsafe {
debug_assert_ok!(FullDateStr::from_bytes(&self.0[DATE_RANGE]));
let s = self.0.get_unchecked_mut(DATE_RANGE);
FullDateStr::from_bytes_maybe_unchecked_mut(s)
}
}
#[inline]
#[must_use]
pub fn time(&self) -> &FullTimeStr {
unsafe {
debug_assert_safe_version_ok!(FullTimeStr::from_bytes(&self.0[TIME_RANGE]));
let s = self.0.get_unchecked(TIME_RANGE);
FullTimeStr::from_bytes_maybe_unchecked(s)
}
}
#[inline]
#[must_use]
pub fn time_mut(&mut self) -> &mut FullTimeStr {
unsafe {
debug_assert_ok!(FullTimeStr::from_bytes(&self.0[TIME_RANGE]));
let s = self.0.get_unchecked_mut(TIME_RANGE);
FullTimeStr::from_bytes_maybe_unchecked_mut(s)
}
}
#[cfg(feature = "chrono04")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
#[deprecated(since = "0.2.2", note = "renamed to `to_chrono04_date_time`")]
#[inline]
#[must_use]
pub fn to_chrono_date_time(&self) -> chrono04::DateTime<chrono04::FixedOffset> {
self.to_chrono04_date_time()
}
#[cfg(feature = "chrono04")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
pub fn to_chrono04_date_time(&self) -> chrono04::DateTime<chrono04::FixedOffset> {
use chrono04::TimeZone;
let (date_s, time_s) = self.decompose();
let date: chrono04::NaiveDate = date_s.into();
let time: chrono04::NaiveTime = time_s.partial_time().to_chrono04_naive_time();
let offset: chrono04::FixedOffset = time_s.offset().into();
offset
.from_local_datetime(&date.and_time(time))
.single()
.expect("Should never fail: fixed time offset is not affected by summer time")
}
#[cfg(feature = "time03")]
#[cfg_attr(docsrs, doc(cfg(feature = "time03")))]
pub fn to_time03_date_time(&self) -> time03::OffsetDateTime {
let (date_s, time_s) = self.decompose();
let date: time03::Date = date_s.into();
let time: time03::Time = time_s.partial_time().to_time03_time();
let offset: time03::UtcOffset = time_s.offset().into();
date.with_time(time).assume_offset(offset)
}
}
impl AsRef<[u8]> for DateTimeStr {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl AsRef<str> for DateTimeStr {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<DateTimeStr> for DateTimeStr {
#[inline]
fn as_ref(&self) -> &DateTimeStr {
self
}
}
impl AsMut<DateTimeStr> for DateTimeStr {
#[inline]
fn as_mut(&mut self) -> &mut DateTimeStr {
self
}
}
impl<'a> From<&'a DateTimeStr> for &'a str {
#[inline]
fn from(v: &'a DateTimeStr) -> Self {
v.as_str()
}
}
impl<'a> TryFrom<&'a [u8]> for &'a DateTimeStr {
type Error = Error;
#[inline]
fn try_from(v: &'a [u8]) -> Result<Self, Self::Error> {
validate_bytes(v)?;
Ok(unsafe {
DateTimeStr::from_bytes_maybe_unchecked(v)
})
}
}
impl<'a> TryFrom<&'a mut [u8]> for &'a mut DateTimeStr {
type Error = Error;
#[inline]
fn try_from(v: &'a mut [u8]) -> Result<Self, Self::Error> {
validate_bytes(v)?;
Ok(unsafe {
DateTimeStr::from_bytes_maybe_unchecked_mut(v)
})
}
}
impl<'a> TryFrom<&'a str> for &'a DateTimeStr {
type Error = Error;
#[inline]
fn try_from(v: &'a str) -> Result<Self, Self::Error> {
Self::try_from(v.as_bytes())
}
}
impl<'a> TryFrom<&'a mut str> for &'a mut DateTimeStr {
type Error = Error;
#[inline]
fn try_from(v: &'a mut str) -> Result<Self, Self::Error> {
validate_bytes(v.as_bytes())?;
Ok(unsafe {
DateTimeStr::from_str_maybe_unchecked_mut(v)
})
}
}
impl fmt::Display for DateTimeStr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl ops::Deref for DateTimeStr {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl_cmp_symmetric!(str, DateTimeStr, &DateTimeStr);
impl_cmp_symmetric!([u8], DateTimeStr, [u8]);
impl_cmp_symmetric!([u8], DateTimeStr, &[u8]);
impl_cmp_symmetric!([u8], &DateTimeStr, [u8]);
impl_cmp_symmetric!(str, DateTimeStr, str);
impl_cmp_symmetric!(str, DateTimeStr, &str);
impl_cmp_symmetric!(str, &DateTimeStr, str);
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl Serialize for DateTimeStr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
#[cfg(feature = "serde")]
mod serde_ {
use super::*;
use serde::de::{Deserialize, Deserializer, Visitor};
struct StrVisitor;
impl<'de> Visitor<'de> for StrVisitor {
type Value = &'de DateTimeStr;
#[inline]
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("RFC 3339 date-time string")
}
#[inline]
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::try_from(v).map_err(E::custom)
}
#[inline]
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::try_from(v).map_err(E::custom)
}
}
impl<'de> Deserialize<'de> for &'de DateTimeStr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(StrVisitor)
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "serde")]
use super::*;
use super::validate_bytes as s_validate;
#[cfg(feature = "serde")]
use serde_test::{assert_de_tokens, assert_tokens, Token};
#[test]
fn validate_bytes() {
assert!(s_validate(b"2001-06-17T12:34:56Z").is_ok());
assert!(s_validate(b"2001-06-17T12:34:56-00:00").is_ok());
assert!(s_validate(b"2001-06-17T12:34:56-12:30").is_ok());
assert!(s_validate(b"2001-06-17T12:34:56-23:59").is_ok());
assert!(s_validate(b"2001-06-17T12:34:56+00:00").is_ok());
assert!(s_validate(b"2001-06-17T12:34:56+12:30").is_ok());
assert!(s_validate(b"2001-06-17T12:34:56+23:59").is_ok());
assert!(s_validate(b"2001-06-17T00:00:00-00:00").is_ok());
assert!(s_validate(b"2001-06-17T23:59:59-00:00").is_ok());
assert!(s_validate(b"2001-06-17T12:34:56.7890Z").is_ok());
assert!(s_validate(b"2001-06-17T12:34:56.7890-00:00").is_ok());
assert!(s_validate(b"2001-06-17T12:34:56.7890+00:00").is_ok());
}
#[cfg(feature = "serde")]
#[test]
fn ser_de_str() {
let raw: &'static str = "2001-06-17T12:34:56.7890-23:12";
assert_tokens(
&DateTimeStr::from_str(raw).unwrap(),
&[Token::BorrowedStr(raw)],
);
}
#[cfg(feature = "serde")]
#[test]
fn de_bytes_slice() {
let raw: &'static [u8] = b"2001-06-17T12:34:56.7890-23:12";
assert_de_tokens(
&DateTimeStr::from_bytes(raw).unwrap(),
&[Token::BorrowedBytes(raw)],
);
}
}