use core::{
convert::TryFrom,
fmt,
ops::{self, Range, RangeTo},
str,
};
use crate::{parse::parse_digits2, str::write_digit2};
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
use crate::common::TimeOffsetSign;
use crate::error::{ComponentKind, Error, ErrorKind};
const NUM_OFFSET_LEN: usize = 6;
const HOUR_ABS_RANGE: Range<usize> = 1..3;
const HOUR_SIGNED_RANGE: RangeTo<usize> = ..3;
const MINUTE_RANGE: Range<usize> = 4..6;
const HOUR_MAX: u8 = 23;
const MINUTE_MAX: u8 = 59;
fn validate_bytes(s: &[u8]) -> Result<(), Error> {
let s: &[u8; NUM_OFFSET_LEN] = TryFrom::try_from(s).map_err(|_| {
if s.len() < NUM_OFFSET_LEN {
ErrorKind::TooShort
} else {
ErrorKind::TooLong
}
})?;
if ((s[0] != b'+') && (s[0] != b'-')) || (s[3] != b':') {
return Err(ErrorKind::InvalidSeparator.into());
}
let hour_s: [u8; 2] = [s[1], s[2]];
let minute_s: [u8; 2] = [s[4], s[5]];
if !hour_s.iter().all(u8::is_ascii_digit) {
return Err(ErrorKind::InvalidComponentType(ComponentKind::OffsetHour).into());
}
if !minute_s.iter().all(u8::is_ascii_digit) {
return Err(ErrorKind::InvalidComponentType(ComponentKind::OffsetMinute).into());
}
let hour = parse_digits2(hour_s);
if hour > HOUR_MAX {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::OffsetHour).into());
}
let minute = parse_digits2(minute_s);
if minute > MINUTE_MAX {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::OffsetMinute).into());
}
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 TimeNumOffsetColonStr([u8]);
impl TimeNumOffsetColonStr {
#[inline]
#[must_use]
pub(crate) 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]
pub(crate) 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]
pub fn assign(&mut self, v: &Self) {
debug_assert_eq!(self.0.len(), v.0.len());
self.0.copy_from_slice(&v.0);
}
#[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 as_bytes_fixed_len(&self) -> &[u8; 6] {
debug_assert_eq!(
self.len(),
NUM_OFFSET_LEN,
"TimeNumOffsetColonStr must always be 6 bytes"
);
debug_assert_safe_version_ok!(<&[u8; NUM_OFFSET_LEN]>::try_from(&self.0));
let ptr = self.0.as_ptr() as *const [u8; NUM_OFFSET_LEN];
unsafe { &*ptr }
}
#[inline]
#[must_use]
pub fn sign(&self) -> TimeOffsetSign {
if self.as_bytes()[0] == b'+' {
TimeOffsetSign::Positive
} else {
debug_assert_eq!(self.0[0], b'-');
TimeOffsetSign::Negative
}
}
#[inline]
#[must_use]
unsafe fn sign_byte_mut(&mut self) -> &mut u8 {
debug_assert_safe_version_some!(self.0.get(0));
self.0.get_unchecked_mut(0)
}
#[inline]
pub fn set_sign(&mut self, sign: TimeOffsetSign) {
let byte = match sign {
TimeOffsetSign::Positive => b'+',
TimeOffsetSign::Negative => b'-',
};
unsafe {
*self.sign_byte_mut() = byte;
}
debug_assert_ok!(validate_bytes(&self.0));
debug_assert_eq!(self.sign(), sign);
}
#[inline]
#[must_use]
pub fn hour_abs_str(&self) -> &str {
unsafe {
debug_assert_safe_version_ok!(str::from_utf8(&self.0[HOUR_ABS_RANGE]));
str::from_utf8_unchecked(self.0.get_unchecked(HOUR_ABS_RANGE))
}
}
#[inline]
#[must_use]
pub fn hour_abs_bytes_fixed_len(&self) -> &[u8; 2] {
unsafe {
debug_assert_safe_version_ok!(<&[u8; 2]>::try_from(&self.0[HOUR_ABS_RANGE]));
let ptr = self.0.as_ptr().add(HOUR_ABS_RANGE.start) as *const [u8; 2];
&*ptr
}
}
#[inline]
#[must_use]
unsafe fn hour_abs_bytes_mut_fixed_len(&mut self) -> &mut [u8; 2] {
debug_assert_ok!(<&[u8; 2]>::try_from(&self.0[HOUR_ABS_RANGE]));
let ptr = self.0.as_mut_ptr().add(HOUR_ABS_RANGE.start) as *mut [u8; 2];
&mut *ptr
}
#[inline]
#[must_use]
pub fn hour_abs(&self) -> u8 {
parse_digits2(*self.hour_abs_bytes_fixed_len())
}
#[inline]
pub fn set_hour_abs(&mut self, hour_abs: u8) -> Result<(), Error> {
if hour_abs > HOUR_MAX {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::OffsetHour).into());
}
unsafe {
write_digit2(self.hour_abs_bytes_mut_fixed_len(), hour_abs);
}
debug_assert_ok!(validate_bytes(&self.0));
Ok(())
}
#[inline]
#[must_use]
pub fn hour_signed_str(&self) -> &str {
unsafe {
debug_assert_safe_version_ok!(str::from_utf8(&self.0[HOUR_SIGNED_RANGE]));
str::from_utf8_unchecked(self.0.get_unchecked(HOUR_SIGNED_RANGE))
}
}
#[inline]
#[must_use]
pub fn hour_signed_bytes_fixed_len(&self) -> &[u8; 3] {
debug_assert_safe_version_ok!(<&[u8; 3]>::try_from(&self.0[HOUR_SIGNED_RANGE]));
let ptr = self.0[HOUR_SIGNED_RANGE].as_ptr() as *const [u8; 3];
unsafe { &*ptr }
}
#[inline]
#[must_use]
pub fn hour_signed(&self) -> i8 {
let abs = self.hour_abs() as i8;
match self.sign() {
TimeOffsetSign::Positive => abs,
TimeOffsetSign::Negative => -abs,
}
}
#[inline]
pub fn set_sign_and_hour_abs(
&mut self,
sign: TimeOffsetSign,
hour_abs: u8,
) -> Result<(), Error> {
self.set_hour_abs(hour_abs)?;
self.set_sign(sign);
debug_assert_ok!(validate_bytes(&self.0));
Ok(())
}
#[inline]
pub fn set_hour_signed(&mut self, hour: i8) -> Result<(), Error> {
let sign = if hour >= 0 {
TimeOffsetSign::Positive
} else {
TimeOffsetSign::Negative
};
let hour_abs = hour.wrapping_abs() as u8;
self.set_sign_and_hour_abs(sign, hour_abs)
}
#[inline]
#[must_use]
pub fn minute_str(&self) -> &str {
unsafe {
debug_assert_safe_version_ok!(str::from_utf8(&self.0[MINUTE_RANGE]));
str::from_utf8_unchecked(self.0.get_unchecked(MINUTE_RANGE))
}
}
#[inline]
#[must_use]
pub fn minute_bytes_fixed_len(&self) -> &[u8; 2] {
unsafe {
debug_assert_safe_version_ok!(<&[u8; 2]>::try_from(&self.0[MINUTE_RANGE]));
let ptr = self.0.as_ptr().add(MINUTE_RANGE.start) as *const [u8; 2];
&*ptr
}
}
#[inline]
#[must_use]
unsafe fn minute_bytes_mut_fixed_len(&mut self) -> &mut [u8; 2] {
debug_assert_ok!(<&[u8; 2]>::try_from(&self.0[MINUTE_RANGE]));
let ptr = self.0.as_mut_ptr().add(MINUTE_RANGE.start) as *mut [u8; 2];
&mut *ptr
}
#[inline]
#[must_use]
pub fn minute(&self) -> u8 {
parse_digits2(*self.minute_bytes_fixed_len())
}
#[inline]
pub fn set_minute(&mut self, minute: u8) -> Result<(), Error> {
if minute > MINUTE_MAX {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::OffsetMinute).into());
}
unsafe {
write_digit2(self.minute_bytes_mut_fixed_len(), minute);
}
debug_assert_ok!(validate_bytes(&self.0));
Ok(())
}
#[inline]
pub fn set_time_signed(&mut self, hour: i8, minute: u8) -> Result<(), Error> {
let hour_abs = hour.wrapping_abs() as u8;
let sign = if hour >= 0 {
TimeOffsetSign::Positive
} else {
TimeOffsetSign::Negative
};
self.set_sign_and_time(sign, hour_abs, minute)
}
pub fn set_sign_and_time(
&mut self,
sign: TimeOffsetSign,
hour_abs: u8,
minute: u8,
) -> Result<(), Error> {
if hour_abs > HOUR_MAX {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::OffsetHour).into());
}
if minute > MINUTE_MAX {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::OffsetMinute).into());
}
self.set_sign(sign);
unsafe {
write_digit2(self.hour_abs_bytes_mut_fixed_len(), hour_abs);
write_digit2(self.minute_bytes_mut_fixed_len(), minute);
}
debug_assert_ok!(validate_bytes(&self.0));
Ok(())
}
#[must_use]
pub fn in_minutes(&self) -> i16 {
let abs_min = self.hour_abs() as i16 * 60 + self.minute() as i16;
match self.sign() {
TimeOffsetSign::Positive => abs_min,
TimeOffsetSign::Negative => -abs_min,
}
}
pub fn set_in_minutes(&mut self, v: i16) -> Result<(), Error> {
const IN_MINUTES_MAX: i16 = HOUR_MAX as i16 * 60 + MINUTE_MAX as i16;
if (v < -IN_MINUTES_MAX) || (v > IN_MINUTES_MAX) {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Offset).into());
}
let sign = if v < 0 {
TimeOffsetSign::Negative
} else {
TimeOffsetSign::Positive
};
let v = v.abs();
let hour_abs = (v / 60) as u8;
let minute = (v % 60) as u8;
self.set_sign_and_time(sign, hour_abs, minute)
}
#[inline]
#[must_use]
pub fn is_unknown_local_offset(&self) -> bool {
&self.0 == b"-00:00"
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl alloc::borrow::ToOwned for TimeNumOffsetColonStr {
type Owned = TimeNumOffsetColonString;
#[inline]
fn to_owned(&self) -> Self::Owned {
self.into()
}
}
impl AsRef<[u8]> for TimeNumOffsetColonStr {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsRef<str> for TimeNumOffsetColonStr {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<TimeNumOffsetColonStr> for TimeNumOffsetColonStr {
#[inline]
fn as_ref(&self) -> &TimeNumOffsetColonStr {
self
}
}
impl AsMut<TimeNumOffsetColonStr> for TimeNumOffsetColonStr {
#[inline]
fn as_mut(&mut self) -> &mut TimeNumOffsetColonStr {
self
}
}
impl<'a> From<&'a TimeNumOffsetColonStr> for &'a str {
#[inline]
fn from(v: &'a TimeNumOffsetColonStr) -> Self {
v.as_str()
}
}
#[cfg(feature = "chrono04")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
impl From<&TimeNumOffsetColonStr> for chrono04::FixedOffset {
#[inline]
fn from(v: &TimeNumOffsetColonStr) -> Self {
Self::east(i32::from(v.in_minutes()) * 60)
}
}
#[cfg(feature = "time03")]
#[cfg_attr(docsrs, doc(cfg(feature = "time03")))]
impl From<&TimeNumOffsetColonStr> for time03::UtcOffset {
#[inline]
fn from(v: &TimeNumOffsetColonStr) -> Self {
Self::from_whole_seconds(i32::from(v.in_minutes()) * 60)
.expect("[validity] `time03::UtcOffset` can represent `-23:59:59` to `+23:59:59`")
}
}
impl<'a> TryFrom<&'a [u8]> for &'a TimeNumOffsetColonStr {
type Error = Error;
#[inline]
fn try_from(v: &'a [u8]) -> Result<Self, Self::Error> {
validate_bytes(v)?;
Ok(unsafe {
TimeNumOffsetColonStr::from_bytes_maybe_unchecked(v)
})
}
}
impl<'a> TryFrom<&'a mut [u8]> for &'a mut TimeNumOffsetColonStr {
type Error = Error;
#[inline]
fn try_from(v: &'a mut [u8]) -> Result<Self, Self::Error> {
validate_bytes(v)?;
Ok(unsafe {
TimeNumOffsetColonStr::from_bytes_maybe_unchecked_mut(v)
})
}
}
impl<'a> TryFrom<&'a str> for &'a TimeNumOffsetColonStr {
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 TimeNumOffsetColonStr {
type Error = Error;
#[inline]
fn try_from(v: &'a mut str) -> Result<Self, Self::Error> {
validate_bytes(v.as_bytes())?;
Ok(unsafe {
TimeNumOffsetColonStr::from_str_maybe_unchecked_mut(v)
})
}
}
impl fmt::Display for TimeNumOffsetColonStr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl ops::Deref for TimeNumOffsetColonStr {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl_cmp_symmetric!(str, TimeNumOffsetColonStr, &TimeNumOffsetColonStr);
impl_cmp_symmetric!([u8], TimeNumOffsetColonStr, [u8]);
impl_cmp_symmetric!([u8], TimeNumOffsetColonStr, &[u8]);
impl_cmp_symmetric!([u8], &TimeNumOffsetColonStr, [u8]);
impl_cmp_symmetric!(str, TimeNumOffsetColonStr, str);
impl_cmp_symmetric!(str, TimeNumOffsetColonStr, &str);
impl_cmp_symmetric!(str, &TimeNumOffsetColonStr, str);
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl serde::Serialize for TimeNumOffsetColonStr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[allow(clippy::derive_hash_xor_eq)]
#[allow(unknown_lints, clippy::derive_ord_xor_partial_ord)]
pub struct TimeNumOffsetColonString([u8; NUM_OFFSET_LEN]);
impl TimeNumOffsetColonString {
#[inline]
#[must_use]
unsafe fn new_maybe_unchecked(s: [u8; 6]) -> Self {
debug_assert_ok!(validate_bytes(&s));
Self(s)
}
#[inline]
#[must_use]
pub fn utc() -> Self {
unsafe {
debug_assert_safe_version_ok!(Self::try_from(*b"+00:00"));
Self::new_maybe_unchecked(*b"+00:00")
}
}
#[inline]
#[must_use]
pub fn unknown_local_offset() -> Self {
unsafe {
debug_assert_safe_version_ok!(Self::try_from(*b"-00:00"));
Self::new_maybe_unchecked(*b"-00:00")
}
}
pub fn from_minutes(minutes: i16) -> Result<Self, Error> {
let mut v = Self::utc();
v.set_in_minutes(minutes)?;
Ok(v)
}
pub fn from_sign_and_hm(sign: TimeOffsetSign, hour_abs: u8, minute: u8) -> Result<Self, Error> {
let mut v = Self::utc();
v.set_sign_and_time(sign, hour_abs, minute)?;
Ok(v)
}
pub fn from_hm_signed(hour: i8, minute: u8) -> Result<Self, Error> {
let mut v = Self::utc();
v.set_time_signed(hour, minute)?;
Ok(v)
}
#[inline]
#[must_use]
pub fn as_deref(&self) -> &TimeNumOffsetColonStr {
unsafe {
debug_assert_ok!(TimeNumOffsetColonStr::from_bytes(&self.0));
TimeNumOffsetColonStr::from_bytes_maybe_unchecked(&self.0)
}
}
#[inline]
#[must_use]
pub fn as_deref_mut(&mut self) -> &mut TimeNumOffsetColonStr {
unsafe {
debug_assert_ok!(TimeNumOffsetColonStr::from_bytes(&self.0));
TimeNumOffsetColonStr::from_bytes_maybe_unchecked_mut(&mut self.0)
}
}
}
impl core::borrow::Borrow<TimeNumOffsetColonStr> for TimeNumOffsetColonString {
#[inline]
fn borrow(&self) -> &TimeNumOffsetColonStr {
self.as_deref()
}
}
impl core::borrow::BorrowMut<TimeNumOffsetColonStr> for TimeNumOffsetColonString {
#[inline]
fn borrow_mut(&mut self) -> &mut TimeNumOffsetColonStr {
self.as_deref_mut()
}
}
impl AsRef<[u8]> for TimeNumOffsetColonString {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsRef<str> for TimeNumOffsetColonString {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<TimeNumOffsetColonStr> for TimeNumOffsetColonString {
#[inline]
fn as_ref(&self) -> &TimeNumOffsetColonStr {
self
}
}
impl AsMut<TimeNumOffsetColonStr> for TimeNumOffsetColonString {
#[inline]
fn as_mut(&mut self) -> &mut TimeNumOffsetColonStr {
self
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl From<TimeNumOffsetColonString> for Vec<u8> {
#[inline]
fn from(v: TimeNumOffsetColonString) -> Vec<u8> {
(*v.as_bytes_fixed_len()).into()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl From<TimeNumOffsetColonString> for String {
#[inline]
fn from(v: TimeNumOffsetColonString) -> String {
let vec: Vec<u8> = (*v.as_bytes_fixed_len()).into();
unsafe {
String::from_utf8_unchecked(vec)
}
}
}
impl From<&TimeNumOffsetColonStr> for TimeNumOffsetColonString {
fn from(v: &TimeNumOffsetColonStr) -> Self {
unsafe {
Self::new_maybe_unchecked(*v.as_bytes_fixed_len())
}
}
}
impl TryFrom<&[u8]> for TimeNumOffsetColonString {
type Error = Error;
#[inline]
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
TimeNumOffsetColonStr::from_bytes(v).map(Into::into)
}
}
impl TryFrom<&str> for TimeNumOffsetColonString {
type Error = Error;
#[inline]
fn try_from(v: &str) -> Result<Self, Self::Error> {
TimeNumOffsetColonStr::from_str(v).map(Into::into)
}
}
impl TryFrom<[u8; 6]> for TimeNumOffsetColonString {
type Error = Error;
#[inline]
fn try_from(v: [u8; 6]) -> Result<Self, Self::Error> {
validate_bytes(&v)?;
Ok(unsafe {
Self::new_maybe_unchecked(v)
})
}
}
#[cfg(feature = "chrono04")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
impl TryFrom<&chrono04::FixedOffset> for TimeNumOffsetColonString {
type Error = Error;
fn try_from(v: &chrono04::FixedOffset) -> Result<Self, Self::Error> {
let seconds = v.local_minus_utc();
if seconds % 60 != 0 {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Offset).into());
}
debug_assert!(seconds < i32::from(i16::max_value()));
Ok(Self::from_minutes((seconds / 60) as i16)
.expect("`chrono04::FixedOffset` must always have a valid time"))
}
}
impl fmt::Display for TimeNumOffsetColonString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_deref().fmt(f)
}
}
impl ops::Deref for TimeNumOffsetColonString {
type Target = TimeNumOffsetColonStr;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_deref()
}
}
impl ops::DerefMut for TimeNumOffsetColonString {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_deref_mut()
}
}
impl str::FromStr for TimeNumOffsetColonString {
type Err = Error;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl_cmp_symmetric!(
TimeNumOffsetColonStr,
TimeNumOffsetColonString,
&TimeNumOffsetColonString
);
impl_cmp_symmetric!(
TimeNumOffsetColonStr,
TimeNumOffsetColonString,
TimeNumOffsetColonStr
);
impl_cmp_symmetric!(
TimeNumOffsetColonStr,
TimeNumOffsetColonString,
&TimeNumOffsetColonStr
);
impl_cmp_symmetric!(str, TimeNumOffsetColonString, str);
impl_cmp_symmetric!(str, TimeNumOffsetColonString, &str);
impl_cmp_symmetric!(str, &TimeNumOffsetColonString, str);
impl_cmp_symmetric!([u8], TimeNumOffsetColonString, [u8]);
impl_cmp_symmetric!([u8], TimeNumOffsetColonString, &[u8]);
impl_cmp_symmetric!([u8], &TimeNumOffsetColonString, [u8]);
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl serde::Serialize for TimeNumOffsetColonString {
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 TimeNumOffsetColonStr;
#[inline]
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("RFC 3339 time-numoffset 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 TimeNumOffsetColonStr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(StrVisitor)
}
}
struct StringVisitor;
impl<'de> Visitor<'de> for StringVisitor {
type Value = TimeNumOffsetColonString;
#[inline]
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("RFC 3339 time-numoffset time string")
}
#[inline]
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::try_from(v).map_err(E::custom)
}
#[inline]
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::try_from(v).map_err(E::custom)
}
}
impl<'de> Deserialize<'de> for TimeNumOffsetColonString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(StringVisitor)
}
}
}
#[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"+00:00").is_ok());
assert!(s_validate(b"+00:30").is_ok());
assert!(s_validate(b"+00:59").is_ok());
assert!(s_validate(b"+12:00").is_ok());
assert!(s_validate(b"+12:30").is_ok());
assert!(s_validate(b"+12:59").is_ok());
assert!(s_validate(b"+23:00").is_ok());
assert!(s_validate(b"+23:30").is_ok());
assert!(s_validate(b"+23:59").is_ok());
assert!(s_validate(b"-00:00").is_ok());
assert!(s_validate(b"-00:30").is_ok());
assert!(s_validate(b"-00:59").is_ok());
assert!(s_validate(b"-12:00").is_ok());
assert!(s_validate(b"-12:30").is_ok());
assert!(s_validate(b"-12:59").is_ok());
assert!(s_validate(b"-23:00").is_ok());
assert!(s_validate(b"-23:30").is_ok());
assert!(s_validate(b"-23:59").is_ok());
assert!(s_validate(b"Z").is_err());
assert!(s_validate(b"+24:00").is_err());
assert!(s_validate(b"-24:00").is_err());
assert!(s_validate(b"+23:60").is_err());
assert!(s_validate(b"-23:60").is_err());
assert!(s_validate(b"00:00").is_err());
}
#[cfg(feature = "serde")]
#[test]
fn ser_de_str() {
let raw: &'static str = "-12:34";
assert_tokens(
&TimeNumOffsetColonStr::from_str(raw).unwrap(),
&[Token::BorrowedStr(raw)],
);
}
#[cfg(feature = "serde")]
#[test]
fn ser_de_string() {
let raw: &'static str = "-12:34";
assert_tokens(
&TimeNumOffsetColonString::try_from(raw).unwrap(),
&[Token::Str(raw)],
);
}
#[cfg(feature = "serde")]
#[test]
fn de_bytes_slice() {
let raw: &'static [u8; 6] = b"-12:34";
assert_de_tokens(
&TimeNumOffsetColonStr::from_bytes(raw).unwrap(),
&[Token::BorrowedBytes(raw)],
);
}
#[cfg(feature = "serde")]
#[test]
fn de_bytes() {
let raw: &'static [u8; 6] = b"-12:34";
assert_de_tokens(
&TimeNumOffsetColonString::try_from(&raw[..]).unwrap(),
&[Token::Bytes(raw)],
);
}
}