1#![allow(clippy::missing_safety_doc)]
2
3#[derive(Clone, Hash, PartialEq, Eq)]
4#[repr(transparent)]
5pub struct Timestamp(String);
7
8impl Timestamp {
9 #[inline]
11 pub fn new(raw: String) -> Result<Self, TimestampParseError> {
12 TimestampRef::validate(raw.as_ref())?;
13 Ok(Self(raw))
14 }
15
16 #[allow(unsafe_code)]
22 #[inline]
23 pub const unsafe fn new_unchecked(raw: String) -> Self { Self(raw) }
24
25 #[inline]
26 #[track_caller]
32 pub fn from_static(raw: &'static str) -> Self {
33 ::std::borrow::ToOwned::to_owned(TimestampRef::from_static(raw))
34 }
35
36 #[allow(unsafe_code)]
40 #[inline]
41 pub fn into_boxed_ref(self) -> Box<TimestampRef> {
42 fn _ptr_safety_comment() {}
44
45 let box_str = self.0.into_boxed_str();
46 unsafe { Box::from_raw(Box::into_raw(box_str) as *mut TimestampRef) }
47 }
48
49 #[inline]
51 pub fn take(self) -> String { self.0 }
52}
53
54impl ::std::convert::From<&'_ TimestampRef> for Timestamp {
55 #[inline]
56 fn from(s: &TimestampRef) -> Self { ::std::borrow::ToOwned::to_owned(s) }
57}
58
59impl ::std::convert::From<Timestamp> for ::std::string::String {
60 #[inline]
61 fn from(s: Timestamp) -> Self { s.0 }
62}
63
64impl ::std::borrow::Borrow<TimestampRef> for Timestamp {
65 #[inline]
66 fn borrow(&self) -> &TimestampRef { ::std::ops::Deref::deref(self) }
67}
68
69impl ::std::convert::AsRef<TimestampRef> for Timestamp {
70 #[inline]
71 fn as_ref(&self) -> &TimestampRef { ::std::ops::Deref::deref(self) }
72}
73
74impl ::std::convert::AsRef<str> for Timestamp {
75 #[inline]
76 fn as_ref(&self) -> &str { self.as_str() }
77}
78
79impl ::std::convert::From<Timestamp> for Box<TimestampRef> {
80 #[inline]
81 fn from(r: Timestamp) -> Self { r.into_boxed_ref() }
82}
83
84impl ::std::convert::From<Box<TimestampRef>> for Timestamp {
85 #[inline]
86 fn from(r: Box<TimestampRef>) -> Self { r.into_owned() }
87}
88
89impl<'a> ::std::convert::From<::std::borrow::Cow<'a, TimestampRef>> for Timestamp {
90 #[inline]
91 fn from(r: ::std::borrow::Cow<'a, TimestampRef>) -> Self {
92 match r {
93 ::std::borrow::Cow::Borrowed(b) => ::std::borrow::ToOwned::to_owned(b),
94 ::std::borrow::Cow::Owned(o) => o,
95 }
96 }
97}
98
99impl ::std::convert::From<Timestamp> for ::std::borrow::Cow<'_, TimestampRef> {
100 #[inline]
101 fn from(owned: Timestamp) -> Self { ::std::borrow::Cow::Owned(owned) }
102}
103
104impl ::std::convert::TryFrom<::std::string::String> for Timestamp {
105 type Error = TimestampParseError;
106
107 #[inline]
108 fn try_from(s: ::std::string::String) -> Result<Self, Self::Error> {
109 const fn ensure_try_from_string_error_converts_to_validator_error<
110 T: From<<String as ::std::convert::TryFrom<::std::string::String>>::Error>,
111 >() {
112 }
113
114 ensure_try_from_string_error_converts_to_validator_error::<Self::Error>();
115 Self::new(s)
116 }
117}
118
119impl ::std::convert::TryFrom<&'_ str> for Timestamp {
120 type Error = TimestampParseError;
121
122 #[inline]
123 fn try_from(s: &str) -> Result<Self, Self::Error> {
124 let ref_ty = TimestampRef::from_str(s)?;
125 Ok(::std::borrow::ToOwned::to_owned(ref_ty))
126 }
127}
128
129impl ::std::str::FromStr for Timestamp {
130 type Err = TimestampParseError;
131
132 #[inline]
133 fn from_str(s: &str) -> Result<Self, Self::Err> {
134 let ref_ty = TimestampRef::from_str(s)?;
135 Ok(::std::borrow::ToOwned::to_owned(ref_ty))
136 }
137}
138
139impl ::std::borrow::Borrow<str> for Timestamp {
140 #[inline]
141 fn borrow(&self) -> &str { self.as_str() }
142}
143
144impl ::std::ops::Deref for Timestamp {
145 type Target = TimestampRef;
146
147 #[allow(unsafe_code)]
148 #[inline]
149 fn deref(&self) -> &Self::Target {
150 fn _unchecked_safety_comment() {}
152
153 unsafe { TimestampRef::from_str_unchecked(::std::convert::AsRef::as_ref(&self.0)) }
154 }
155}
156
157impl ::std::fmt::Debug for Timestamp {
158 #[inline]
159 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
160 <TimestampRef as ::std::fmt::Debug>::fmt(::std::ops::Deref::deref(self), f)
161 }
162}
163
164impl ::std::fmt::Display for Timestamp {
165 #[inline]
166 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
167 <TimestampRef as ::std::fmt::Display>::fmt(::std::ops::Deref::deref(self), f)
168 }
169}
170
171#[cfg(feature = "serde")]
172impl ::serde::Serialize for Timestamp {
173 fn serialize<S: ::serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
174 <String as ::serde::Serialize>::serialize(&self.0, serializer)
175 }
176}
177
178#[cfg(feature = "serde")]
179impl<'de> ::serde::Deserialize<'de> for Timestamp {
180 fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
181 let raw = <String as ::serde::Deserialize<'de>>::deserialize(deserializer)?;
182 Self::new(raw).map_err(<D::Error as ::serde::de::Error>::custom)
183 }
184}
185#[repr(transparent)]
186#[derive(Hash, PartialEq, Eq)]
187pub struct TimestampRef(str);
189
190impl TimestampRef {
191 #[allow(unsafe_code, clippy::should_implement_trait)]
192 #[inline]
193 pub fn from_str(raw: &str) -> Result<&Self, TimestampParseError> {
195 Self::validate(raw)?;
196 fn _unchecked_safety_comment() {}
198
199 Ok(unsafe { Self::from_str_unchecked(raw) })
200 }
201
202 #[allow(unsafe_code)]
203 #[inline]
204 pub const unsafe fn from_str_unchecked(raw: &str) -> &Self {
206 fn _ptr_safety_comment() {}
208
209 &*(raw as *const str as *const Self)
210 }
211
212 #[inline]
213 #[track_caller]
219 pub fn from_static(raw: &'static str) -> &'static Self {
220 Self::from_str(raw).expect(concat!("invalid ", stringify!(TimestampRef)))
221 }
222
223 #[allow(unsafe_code)]
224 #[inline]
225 pub fn into_owned(self: Box<TimestampRef>) -> Timestamp {
227 fn _ptr_safety_comment() {}
229
230 let raw = Box::into_raw(self);
231 let boxed = unsafe { Box::from_raw(raw as *mut str) };
232 let s = ::std::convert::From::from(boxed);
233 fn _unchecked_safety_comment() {}
235
236 unsafe { Timestamp::new_unchecked(s) }
237 }
238
239 #[inline]
241 pub const fn as_str(&self) -> &str { &self.0 }
242}
243
244impl ::std::borrow::ToOwned for TimestampRef {
245 type Owned = Timestamp;
246
247 #[inline]
248 fn to_owned(&self) -> Self::Owned { Timestamp(self.0.into()) }
249}
250
251impl ::std::cmp::PartialEq<TimestampRef> for Timestamp {
252 #[inline]
253 fn eq(&self, other: &TimestampRef) -> bool { self.as_str() == other.as_str() }
254}
255
256impl ::std::cmp::PartialEq<Timestamp> for TimestampRef {
257 #[inline]
258 fn eq(&self, other: &Timestamp) -> bool { self.as_str() == other.as_str() }
259}
260
261impl ::std::cmp::PartialEq<&'_ TimestampRef> for Timestamp {
262 #[inline]
263 fn eq(&self, other: &&TimestampRef) -> bool { self.as_str() == other.as_str() }
264}
265
266impl ::std::cmp::PartialEq<Timestamp> for &'_ TimestampRef {
267 #[inline]
268 fn eq(&self, other: &Timestamp) -> bool { self.as_str() == other.as_str() }
269}
270
271impl<'a> ::std::convert::TryFrom<&'a str> for &'a TimestampRef {
272 type Error = TimestampParseError;
273
274 #[inline]
275 fn try_from(s: &'a str) -> Result<&'a TimestampRef, Self::Error> { TimestampRef::from_str(s) }
276}
277
278impl ::std::borrow::Borrow<str> for TimestampRef {
279 #[inline]
280 fn borrow(&self) -> &str { &self.0 }
281}
282
283impl ::std::convert::AsRef<str> for TimestampRef {
284 #[inline]
285 fn as_ref(&self) -> &str { &self.0 }
286}
287
288impl<'a> ::std::convert::From<&'a TimestampRef> for ::std::borrow::Cow<'a, TimestampRef> {
289 #[inline]
290 fn from(r: &'a TimestampRef) -> Self { ::std::borrow::Cow::Borrowed(r) }
291}
292
293impl<'a, 'b: 'a> ::std::convert::From<&'a ::std::borrow::Cow<'b, TimestampRef>>
294 for &'a TimestampRef
295{
296 #[inline]
297 fn from(r: &'a ::std::borrow::Cow<'b, TimestampRef>) -> &'a TimestampRef {
298 ::std::borrow::Borrow::borrow(r)
299 }
300}
301
302impl ::std::convert::From<&'_ TimestampRef> for ::std::rc::Rc<TimestampRef> {
303 #[allow(unsafe_code)]
304 #[inline]
305 fn from(r: &'_ TimestampRef) -> Self {
306 fn _ptr_safety_comment() {}
308
309 let rc = ::std::rc::Rc::<str>::from(r.as_str());
310 unsafe { ::std::rc::Rc::from_raw(::std::rc::Rc::into_raw(rc) as *const TimestampRef) }
311 }
312}
313
314impl ::std::convert::From<&'_ TimestampRef> for ::std::sync::Arc<TimestampRef> {
315 #[allow(unsafe_code)]
316 #[inline]
317 fn from(r: &'_ TimestampRef) -> Self {
318 fn _ptr_safety_comment() {}
320
321 let arc = ::std::sync::Arc::<str>::from(r.as_str());
322 unsafe {
323 ::std::sync::Arc::from_raw(::std::sync::Arc::into_raw(arc) as *const TimestampRef)
324 }
325 }
326}
327
328impl ::std::fmt::Debug for TimestampRef {
329 #[inline]
330 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
331 <str as ::std::fmt::Debug>::fmt(&self.0, f)
332 }
333}
334
335impl ::std::fmt::Display for TimestampRef {
336 #[inline]
337 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
338 <str as ::std::fmt::Display>::fmt(&self.0, f)
339 }
340}
341
342#[cfg(feature = "serde")]
343impl ::serde::Serialize for TimestampRef {
344 fn serialize<S: ::serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
345 <str as ::serde::Serialize>::serialize(self.as_str(), serializer)
346 }
347}
348
349#[cfg(feature = "serde")]
350impl<'de: 'a, 'a> ::serde::Deserialize<'de> for &'a TimestampRef {
351 fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
352 let raw = <&str as ::serde::Deserialize<'de>>::deserialize(deserializer)?;
353 TimestampRef::from_str(raw).map_err(<D::Error as ::serde::de::Error>::custom)
354 }
355}
356
357#[cfg(feature = "serde")]
358impl<'de> ::serde::Deserialize<'de> for Box<TimestampRef> {
359 fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
360 let owned = <Timestamp as ::serde::Deserialize<'de>>::deserialize(deserializer)?;
361 Ok(owned.into_boxed_ref())
362 }
363}
364
365impl_extra!(validated, Timestamp, TimestampRef, TimestampParseError);
366
367#[cfg(feature = "arbitrary")]
368impl<'a> arbitrary::Arbitrary<'a> for Timestamp {
369 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
370 let year = u.int_in_range(0..=9999)?;
371 let month = u.int_in_range(1..=12)?;
372 const M_D: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
373 let day = u.int_in_range(1..=M_D[month as usize - 1])?;
374 let hour = u.int_in_range(0..=23)?;
375 let minute = u.int_in_range(0..=59)?;
376 let second = u.int_in_range(0..=59)?;
377 let millis = if bool::arbitrary(u)? {
378 let millis = u.int_in_range(0..=999)?;
379 format!(".{millis:03}")
380 } else {
381 "".to_owned()
382 };
383 format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}{millis}Z")
384 .parse()
385 .map_err(|_| arbitrary::Error::IncorrectFormat)
386 }
387}
388
389impl TimestampRef {
390 fn validate(s: &str) -> Result<(), TimestampParseError> {
391 #[cfg(feature = "time")]
392 {
393 let _ = time::OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339)?;
394 Ok(())
395 }
396 #[cfg(not(feature = "time"))]
397 {
398 if !s.chars().all(|c| {
400 c.is_numeric()
401 || c == 'T'
402 || c == 'Z'
403 || c == '+'
404 || c == '.'
405 || c == '-'
406 || c == ':'
407 }) {
408 return Err(TimestampParseError::invalid());
409 }
410 if let Some(i) = s.find('T') {
412 if i < 1 {
414 return Err(TimestampParseError::invalid());
415 };
416 let (full_date, full_time) = s.split_at(i);
417 if full_date.len() != "1900-00-00".len() {
418 return Err(TimestampParseError::invalid_s(full_date));
419 }
420 if !full_date.chars().all(|c| c.is_numeric() || c == '-') {
421 return Err(TimestampParseError::invalid_s(full_date));
422 }
423 let partial_time = if let Some(stripped) = full_time.strip_suffix('Z') {
424 stripped
425 } else {
426 return Err(TimestampParseError::Other("unsupported non-UTC timestamp, enable the `time` feature in `twitch_types` to enable parsing these"));
427 };
428 if 2 != partial_time
429 .chars()
430 .into_iter()
431 .filter(|&b| b == ':')
432 .count()
433 {
434 return Err(TimestampParseError::invalid_s(partial_time));
435 };
436 if !partial_time.contains('.') && partial_time.len() != "T00:00:00".len() {
437 return Err(TimestampParseError::invalid_s(partial_time));
438 } else if partial_time.contains('.') {
439 let mut i = partial_time.split('.');
440 if !i
442 .next()
443 .map(|s| s.len() == "T00:00:00".len())
444 .unwrap_or_default()
445 {
446 return Err(TimestampParseError::invalid_s(partial_time));
447 }
448 }
449 } else {
450 return Err(TimestampParseError::invalid());
451 }
452 Ok(())
453 }
454 }
455}
456
457impl From<std::convert::Infallible> for TimestampParseError {
458 fn from(value: std::convert::Infallible) -> Self { match value {} }
459}
460
461#[derive(Debug)]
463#[non_exhaustive]
464pub enum TimestampParseError {
465 #[cfg(feature = "time")]
467 TimeError(time::error::Parse),
468 #[cfg(feature = "time")]
470 TimeFormatError(time::error::Format),
471 Other(&'static str),
473 InvalidFormat {
475 location: &'static std::panic::Location<'static>,
477 s: Option<String>,
479 },
480}
481
482impl core::fmt::Display for TimestampParseError {
483 fn fmt(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
484 #[allow(unused_variables)]
485 match self {
486 #[cfg(feature = "time")]
487 TimestampParseError::TimeError(parse_error) => {
488 write!(formatter, "Could not parse the timestamp using `time`")
489 }
490 #[cfg(feature = "time")]
491 TimestampParseError::TimeFormatError(_) => {
492 write!(formatter, "Could not format the timestamp using `time`")
493 }
494 TimestampParseError::Other(other) => {
495 write!(formatter, "{other}")
496 }
497 TimestampParseError::InvalidFormat { location, s } => {
498 write!(
499 formatter,
500 "timestamp has an invalid format. {s:?} - {location}"
501 )
502 }
503 }
504 }
505}
506
507impl std::error::Error for TimestampParseError {
508 fn source(&self) -> std::option::Option<&(dyn std::error::Error + 'static)> {
509 match self {
510 #[cfg(feature = "time")]
511 TimestampParseError::TimeError { 0: source, .. } => {
512 std::option::Option::Some(source as _)
513 }
514 #[cfg(feature = "time")]
515 TimestampParseError::TimeFormatError { 0: source, .. } => {
516 std::option::Option::Some(source as _)
517 }
518 TimestampParseError::Other { .. } => std::option::Option::None,
519 TimestampParseError::InvalidFormat { .. } => std::option::Option::None,
520 }
521 }
522}
523#[cfg(feature = "time")]
524impl std::convert::From<time::error::Parse> for TimestampParseError {
525 fn from(source: time::error::Parse) -> Self { TimestampParseError::TimeError(source) }
526}
527#[cfg(feature = "time")]
528impl std::convert::From<time::error::Format> for TimestampParseError {
529 fn from(source: time::error::Format) -> Self { TimestampParseError::TimeFormatError(source) }
530}
531
532impl TimestampParseError {
533 #[cfg(not(feature = "time"))]
534 #[track_caller]
535 fn invalid() -> Self {
536 Self::InvalidFormat {
537 location: std::panic::Location::caller(),
538 s: None,
539 }
540 }
541
542 #[cfg(not(feature = "time"))]
543 #[track_caller]
544 fn invalid_s(s: &str) -> Self {
545 Self::InvalidFormat {
546 location: std::panic::Location::caller(),
547 s: Some(s.to_string()),
548 }
549 }
550}
551
552impl Timestamp {
553 fn set_time(&mut self, hours: u8, minutes: u8, seconds: u8) {
559 #[cfg(feature = "time")]
560 {
561 let _ = std::mem::replace(
562 self,
563 self.to_fixed_offset()
564 .replace_time(
565 time::Time::from_hms(hours, minutes, seconds)
566 .expect("could not create time"),
567 )
568 .try_into()
569 .expect("could not make timestamp"),
570 );
571 }
572 #[cfg(not(feature = "time"))]
573 {
574 const ERROR_MSG: &str = "malformed timestamp";
575 assert!(hours < 24);
576 assert!(minutes < 60);
577 assert!(seconds < 60);
578
579 #[inline]
580 fn replace_len2(s: &mut str, replace: &str) {
581 assert!(replace.as_bytes().len() == 2);
582 assert!(s.as_bytes().len() == 2);
583
584 let replace = replace.as_bytes();
585 let b = unsafe { s.as_bytes_mut() };
591 b[0] = replace[0];
592 b[1] = replace[1];
593 }
594 let t = self.0.find('T').expect(ERROR_MSG);
595 let partial_time: &mut str = &mut self.0[t + 1..];
596 let mut matches = partial_time.match_indices(':');
598 let (h, m, s) = (
599 0,
600 matches.next().expect(ERROR_MSG).0 + 1,
601 matches.next().expect(ERROR_MSG).0 + 1,
602 );
603 assert!(matches.next().is_none());
604 partial_time
606 .get_mut(h..h + 2)
607 .map(|s| replace_len2(s, &format!("{:02}", hours)))
608 .expect(ERROR_MSG);
609 partial_time
610 .get_mut(m..m + 2)
611 .map(|s| replace_len2(s, &format!("{:02}", minutes)))
612 .expect(ERROR_MSG);
613 partial_time
614 .get_mut(s..s + 2)
615 .map(|s| replace_len2(s, &format!("{:02}", seconds)))
616 .expect(ERROR_MSG);
617 }
618 }
619}
620
621#[cfg(feature = "time")]
622#[cfg_attr(nightly, doc(cfg(feature = "time")))]
623impl Timestamp {
624 pub fn now() -> Timestamp {
626 time::OffsetDateTime::now_utc()
627 .try_into()
628 .expect("could not make timestamp")
629 }
630
631 pub fn today() -> Timestamp {
633 time::OffsetDateTime::now_utc()
634 .replace_time(time::Time::MIDNIGHT)
635 .try_into()
636 .expect("could not make timestamp")
637 }
638}
639
640impl TimestampRef {
641 #[allow(unreachable_code)]
655 pub fn normalize(&'_ self) -> Result<std::borrow::Cow<'_, TimestampRef>, TimestampParseError> {
656 let s = self.as_str();
657 if s.ends_with('Z') {
658 Ok(self.into())
659 } else {
660 #[cfg(feature = "time")]
661 {
662 let utc = self.to_utc();
663 return Ok(std::borrow::Cow::Owned(utc.try_into()?));
664 }
665 panic!("non `Z` timestamps are not possible to use without the `time` feature enabled for `twitch_types`")
666 }
667 }
668
669 pub fn is_before<T: ?Sized>(&self, other: &T) -> bool
681 where Self: PartialOrd<T> {
682 self < other
683 }
684
685 pub fn to_day(&self) -> Timestamp {
696 let mut c = self.to_owned();
697 c.set_time(0, 0, 0);
698 c
699 }
700
701 pub fn year(&self) -> &str { &self.0[0..4] }
712
713 pub fn month(&self) -> &str { &self.0[5..7] }
724
725 pub fn day(&self) -> &str { &self.0[8..10] }
736
737 pub fn hour(&self) -> &str { &self.0[11..13] }
748
749 pub fn minute(&self) -> &str { &self.0[14..16] }
760
761 pub fn second(&self) -> &str { &self.0[17..19] }
772
773 pub fn millis(&self) -> Option<&str> {
786 if self.0[19..].contains('.') {
787 let sub = &self.0[20..];
788 Some(&sub[..sub.find(|c: char| !c.is_ascii_digit()).unwrap_or(sub.len())])
789 } else {
790 None
791 }
792 }
793}
794
795#[cfg(feature = "time")]
796#[cfg_attr(nightly, doc(cfg(feature = "time")))]
797impl TimestampRef {
798 pub fn to_utc(&self) -> time::OffsetDateTime {
804 self.to_fixed_offset().to_offset(time::UtcOffset::UTC)
805 }
806
807 pub fn to_fixed_offset(&self) -> time::OffsetDateTime {
813 time::OffsetDateTime::parse(&self.0, &time::format_description::well_known::Rfc3339)
814 .expect("this should never fail")
815 }
816}
817
818impl PartialOrd for Timestamp {
819 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
820 let this: &TimestampRef = self.as_ref();
822 let other: &TimestampRef = other.as_ref();
823 this.partial_cmp(other)
824 }
825}
826
827impl PartialOrd<Timestamp> for TimestampRef {
828 fn partial_cmp(&self, other: &Timestamp) -> Option<std::cmp::Ordering> {
829 let other: &TimestampRef = other.as_ref();
831 self.partial_cmp(other)
832 }
833}
834
835impl PartialOrd for TimestampRef {
836 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
837 let this = self
841 .normalize()
842 .expect("normalization failed, this is a bug");
843 let other = other
844 .normalize()
845 .expect("normalization of other failed, this is a bug");
846 #[allow(clippy::if_same_then_else)]
848 if this.as_ref().as_str().contains('.') ^ other.as_ref().as_str().contains('.') {
849 #[cfg(feature = "tracing")]
850 tracing::warn!("comparing two `Timestamps` with differing punctuation");
851 return None;
852 } else if this.0.len() != other.0.len() {
853 #[cfg(feature = "tracing")]
854 tracing::warn!("comparing two `Timestamps` with differing length");
855 return None;
856 }
857 this.as_str().partial_cmp(other.as_str())
858 }
859}
860
861#[cfg(feature = "time")]
862#[cfg_attr(nightly, doc(cfg(feature = "time")))]
863impl PartialEq<time::OffsetDateTime> for Timestamp {
864 fn eq(&self, other: &time::OffsetDateTime) -> bool {
865 let this: &TimestampRef = self.as_ref();
867 this.eq(other)
868 }
869}
870
871#[cfg(feature = "time")]
872#[cfg_attr(nightly, doc(cfg(feature = "time")))]
873impl PartialOrd<time::OffsetDateTime> for Timestamp {
874 fn partial_cmp(&self, other: &time::OffsetDateTime) -> Option<std::cmp::Ordering> {
875 let this: &TimestampRef = self.as_ref();
877 this.partial_cmp(other)
878 }
879}
880
881#[cfg(feature = "time")]
882#[cfg_attr(nightly, doc(cfg(feature = "time")))]
883impl PartialEq<time::OffsetDateTime> for TimestampRef {
884 fn eq(&self, other: &time::OffsetDateTime) -> bool { &self.to_utc() == other }
885}
886
887#[cfg(feature = "time")]
888#[cfg_attr(nightly, doc(cfg(feature = "time")))]
889impl PartialOrd<time::OffsetDateTime> for TimestampRef {
890 fn partial_cmp(&self, other: &time::OffsetDateTime) -> Option<std::cmp::Ordering> {
891 self.to_utc().partial_cmp(other)
892 }
893}
894
895#[cfg(feature = "time")]
896#[cfg_attr(nightly, doc(cfg(feature = "time")))]
897impl std::convert::TryFrom<time::OffsetDateTime> for Timestamp {
898 type Error = time::error::Format;
899
900 fn try_from(value: time::OffsetDateTime) -> Result<Self, Self::Error> {
901 Ok(Timestamp(
902 value.format(&time::format_description::well_known::Rfc3339)?,
903 ))
904 }
905}
906
907#[cfg(test)]
908mod tests {
909 use super::*;
910
911 #[test]
912 pub fn time_test() {
913 let mut time1 = Timestamp::try_from("2021-11-11T10:00:00Z").unwrap();
914 time1.set_time(10, 0, 32);
915 let time2 = Timestamp::try_from("2021-11-10T10:00:00Z").unwrap();
916 assert!(time2.is_before(&time1));
917 dbg!(time1.normalize().unwrap());
918 #[cfg(feature = "time")]
919 let time = Timestamp::try_from("2021-11-11T13:37:00-01:00").unwrap();
920 #[cfg(feature = "time")]
921 dbg!(time.normalize().unwrap());
922 }
923}