1use std::{borrow, fmt, mem, ops};
2
3use chrono::Timelike;
4pub use chrono::{
5 DateTime, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
6 format::{DelayedFormat, StrftimeItems},
7};
8pub use rust_decimal::Decimal;
9use serde::{
10 Deserialize, Deserializer, Serialize, Serializer,
11 de::{self, Visitor},
12};
13
14pub type Int = i64;
15pub type TagNum = u16;
16pub type SeqNum = u32;
17pub type NumInGroup = u8;
18pub type DayOfMonth = u8;
19
20pub type Float = Decimal;
21pub type Qty = Float;
22pub type Price = Float;
23pub type PriceOffset = Float;
24pub type Amt = Float;
25pub type Percentage = Float;
26
27pub type Boolean = bool;
28
29pub type Char = u8;
30pub type MultipleCharValue = Vec<Char>;
31
32#[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
33pub struct FixString(Vec<u8>);
34
35#[derive(Eq, Hash, Ord, PartialEq, PartialOrd)]
36#[repr(transparent)]
37pub struct FixStr([u8]);
38
39pub type MultipleStringValue = Vec<FixString>;
40
41pub use crate::{country::Country, currency::Currency};
42pub type Exchange = [u8; 4];
43pub type MonthYear = Vec<u8>;
45pub type Language = [u8; 2];
46
47#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
48pub enum TimePrecision {
49 Secs = 0,
50 Millis = 3,
51 Micros = 6,
52 #[default]
53 Nanos = 9,
54}
55
56#[derive(Clone, Copy, Debug, Default)]
57pub struct UtcTimestamp {
58 timestamp: DateTime<Utc>,
59 precision: TimePrecision,
60}
61
62#[derive(Clone, Copy, Debug, Serialize)]
63pub struct UtcTimeOnly {
64 timestamp: NaiveTime,
65 precision: TimePrecision,
66}
67pub type UtcDateOnly = NaiveDate;
68
69pub type LocalMktTime = NaiveTime;
70pub type LocalMktDate = NaiveDate;
71
72pub type TzTimestamp = Vec<u8>;
74pub type TzTimeOnly = Vec<u8>;
75
76pub type Length = u16;
77pub type Data = Vec<u8>;
78pub type XmlData = Data;
79
80pub type Tenor = Vec<u8>;
82
83#[derive(Debug)]
84pub struct FixStringError {
85 idx: usize,
86 value: u8,
87}
88
89impl FixStringError {
90 pub fn idx(&self) -> usize {
92 self.idx
93 }
94
95 pub fn value(&self) -> u8 {
97 self.value
98 }
99}
100
101impl fmt::Display for FixStringError {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(
104 f,
105 "Unexpected character '{:#04x}' at idx {}",
106 self.value, self.idx
107 )
108 }
109}
110
111impl std::error::Error for FixStringError {}
112
113const fn is_non_control_ascii_char(byte: u8) -> bool {
114 byte > 0x1f && byte < 0x80
115}
116
117impl FixStr {
118 pub const fn from_ascii(buf: &[u8]) -> Result<&FixStr, FixStringError> {
149 let mut i = 0;
150 while i < buf.len() {
151 let c = buf[i];
152 if !is_non_control_ascii_char(c) {
153 return Err(FixStringError { idx: i, value: c });
154 }
155 i += 1;
156 }
157 unsafe { Ok(FixStr::from_ascii_unchecked(buf)) }
159 }
160
161 pub const unsafe fn from_ascii_unchecked(buf: &[u8]) -> &FixStr {
172 unsafe { mem::transmute(buf) }
175 }
176
177 pub const fn as_utf8(&self) -> &str {
178 unsafe { std::str::from_utf8_unchecked(&self.0) }
180 }
181
182 pub const fn as_bytes(&self) -> &[u8] {
183 &self.0
184 }
185
186 pub const fn len(&self) -> usize {
187 self.0.len()
188 }
189
190 pub const fn is_empty(&self) -> bool {
191 self.0.is_empty()
192 }
193}
194
195impl fmt::Display for FixStr {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
197 self.as_utf8().fmt(f)
198 }
199}
200
201impl fmt::Debug for FixStr {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 write!(f, "FixStr(\"{}\")", self)
204 }
205}
206
207impl AsRef<FixStr> for FixStr {
208 fn as_ref(&self) -> &FixStr {
209 self
210 }
211}
212
213impl AsRef<[u8]> for FixStr {
214 fn as_ref(&self) -> &[u8] {
215 self.as_bytes()
216 }
217}
218
219impl AsRef<str> for FixStr {
220 fn as_ref(&self) -> &str {
221 self.as_utf8()
222 }
223}
224
225impl From<&FixStr> for String {
226 fn from(input: &FixStr) -> String {
227 input.to_owned().into()
228 }
229}
230
231impl ToOwned for FixStr {
232 type Owned = FixString;
233
234 #[inline]
235 fn to_owned(&self) -> FixString {
236 unsafe { FixString::from_ascii_unchecked(self.as_bytes().to_owned()) }
237 }
238
239 fn clone_into(&self, target: &mut FixString) {
240 let mut buf = mem::take(target).into_bytes();
241 self.as_bytes().clone_into(&mut buf);
242 *target = unsafe { FixString::from_ascii_unchecked(buf) }
243 }
244}
245
246macro_rules! impl_eq {
247 ($lhs:ty, $lhs_bytes: ident, $rhs: ty, $rhs_bytes: ident) => {
248 impl PartialEq<$rhs> for $lhs {
249 #[inline]
250 fn eq(&self, other: &$rhs) -> bool {
251 PartialEq::eq(self.$lhs_bytes(), other.$rhs_bytes())
252 }
253 }
254
255 impl PartialEq<$lhs> for $rhs {
256 #[inline]
257 fn eq(&self, other: &$lhs) -> bool {
258 PartialEq::eq(self.$rhs_bytes(), other.$lhs_bytes())
259 }
260 }
261 };
262}
263
264impl_eq!([u8], as_ref, FixStr, as_bytes);
265impl_eq!([u8], as_ref, &FixStr, as_bytes);
266impl_eq!(&[u8], as_ref, FixStr, as_bytes);
267impl_eq!(Vec<u8>, as_slice, FixStr, as_bytes);
268impl_eq!(Vec<u8>, as_slice, &FixStr, as_bytes);
269impl_eq!(str, as_bytes, FixStr, as_bytes);
270impl_eq!(&str, as_bytes, FixStr, as_bytes);
271impl_eq!(str, as_bytes, &FixStr, as_bytes);
272impl_eq!(String, as_bytes, FixStr, as_bytes);
273impl_eq!(String, as_bytes, &FixStr, as_bytes);
274
275impl_eq!([u8], as_ref, FixString, as_bytes);
276impl_eq!(&[u8], as_ref, FixString, as_bytes);
277impl_eq!(Vec<u8>, as_slice, FixString, as_bytes);
278impl_eq!(str, as_bytes, FixString, as_bytes);
279impl_eq!(&str, as_bytes, FixString, as_bytes);
280impl_eq!(String, as_bytes, FixString, as_bytes);
281
282impl_eq!(FixString, as_bytes, FixStr, as_bytes);
283impl_eq!(FixString, as_bytes, &FixStr, as_bytes);
284
285impl<const N: usize> PartialEq<[u8; N]> for FixStr {
286 fn eq(&self, other: &[u8; N]) -> bool {
287 self.0.eq(&other[..])
288 }
289}
290
291impl<const N: usize> PartialEq<&'_ [u8; N]> for FixStr {
292 fn eq(&self, other: &&[u8; N]) -> bool {
293 self.0.eq(*other)
294 }
295}
296
297impl<const N: usize> PartialEq<[u8; N]> for &FixStr {
298 fn eq(&self, other: &[u8; N]) -> bool {
299 self.0.eq(&other[..])
300 }
301}
302
303impl<const N: usize> PartialEq<[u8; N]> for FixString {
304 fn eq(&self, other: &[u8; N]) -> bool {
305 self.0.eq(other)
306 }
307}
308
309impl<const N: usize> PartialEq<&'_ [u8; N]> for FixString {
310 fn eq(&self, other: &&[u8; N]) -> bool {
311 self.0.eq(other)
312 }
313}
314
315#[macro_export]
320macro_rules! fix_format {
321 ($($arg:tt)*) => {{
322 FixString::from_ascii_lossy(std::format!($($arg)*).into_bytes())
323 }}
324}
325
326impl FixString {
328 pub const fn new() -> FixString {
329 FixString(Vec::new())
330 }
331
332 pub fn with_capacity(capacity: usize) -> FixString {
333 FixString(Vec::with_capacity(capacity))
334 }
335
336 pub fn from_ascii(buf: Vec<u8>) -> Result<FixString, FixStringError> {
368 for i in 0..buf.len() {
369 let c = unsafe { *buf.get_unchecked(i) };
371 if !is_non_control_ascii_char(c) {
372 return Err(FixStringError { idx: i, value: c });
373 }
374 }
375 Ok(FixString(buf))
376 }
377
378 pub unsafe fn from_ascii_unchecked(buf: Vec<u8>) -> FixString {
392 FixString(buf)
393 }
394
395 pub fn from_ascii_lossy(mut buf: Vec<u8>) -> FixString {
398 for i in 0..buf.len() {
399 let c = unsafe { buf.get_unchecked_mut(i) };
401 if !is_non_control_ascii_char(*c) {
402 *c = b'?';
403 }
404 }
405 FixString(buf)
406 }
407
408 pub fn as_utf8(&self) -> &str {
409 unsafe { std::str::from_utf8_unchecked(&self.0) }
411 }
412
413 pub fn into_utf8(self) -> String {
414 unsafe { String::from_utf8_unchecked(self.0) }
416 }
417
418 pub fn into_bytes(self) -> Vec<u8> {
419 self.0
420 }
421
422 pub fn len(&self) -> usize {
423 self.0.len()
424 }
425
426 pub fn is_empty(&self) -> bool {
427 self.0.is_empty()
428 }
429}
430
431impl fmt::Display for FixString {
432 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
433 self.as_utf8().fmt(f)
434 }
435}
436
437impl fmt::Debug for FixString {
438 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439 write!(f, "FixString(\"{}\")", self)
440 }
441}
442
443impl ops::Deref for FixString {
444 type Target = FixStr;
445
446 fn deref(&self) -> &FixStr {
447 unsafe { FixStr::from_ascii_unchecked(&self.0) }
448 }
449}
450
451impl AsRef<FixStr> for FixString {
452 fn as_ref(&self) -> &FixStr {
453 self
454 }
455}
456
457impl AsRef<[u8]> for FixString {
458 fn as_ref(&self) -> &[u8] {
459 self.as_bytes()
460 }
461}
462
463impl AsRef<str> for FixString {
464 fn as_ref(&self) -> &str {
465 self.as_utf8()
466 }
467}
468
469impl borrow::Borrow<FixStr> for FixString {
470 fn borrow(&self) -> &FixStr {
471 self
472 }
473}
474
475impl From<&FixStr> for FixString {
476 fn from(input: &FixStr) -> FixString {
477 input.to_owned()
478 }
479}
480
481impl From<FixString> for String {
482 fn from(input: FixString) -> String {
483 unsafe { String::from_utf8_unchecked(input.0) }
485 }
486}
487
488impl TryFrom<&[u8]> for FixString {
489 type Error = FixStringError;
490
491 fn try_from(input: &[u8]) -> Result<FixString, Self::Error> {
492 FixString::from_ascii(input.to_vec())
494 }
495}
496
497impl TryFrom<Vec<u8>> for FixString {
498 type Error = FixStringError;
499
500 fn try_from(buf: Vec<u8>) -> Result<FixString, Self::Error> {
501 FixString::from_ascii(buf)
502 }
503}
504
505impl TryFrom<&str> for FixString {
506 type Error = FixStringError;
507
508 fn try_from(buf: &str) -> Result<FixString, Self::Error> {
509 FixString::from_ascii(buf.as_bytes().to_owned())
510 }
511}
512
513impl TryFrom<String> for FixString {
514 type Error = FixStringError;
515
516 fn try_from(buf: String) -> Result<FixString, Self::Error> {
517 FixString::from_ascii(buf.into_bytes())
518 }
519}
520
521impl<const N: usize> TryFrom<[u8; N]> for FixString {
522 type Error = FixStringError;
523
524 fn try_from(buf: [u8; N]) -> Result<FixString, Self::Error> {
525 FixString::from_ascii(buf.to_vec())
526 }
527}
528
529impl<const N: usize> From<&[u8; N]> for FixString {
530 fn from(input: &[u8; N]) -> FixString {
531 FixString(input.as_slice().into())
532 }
533}
534
535struct FixStringVisitor;
536
537impl Visitor<'_> for FixStringVisitor {
538 type Value = FixString;
539
540 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
541 formatter.write_str("string")
542 }
543
544 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
545 where
546 E: de::Error,
547 {
548 value.try_into().map_err(de::Error::custom)
549 }
550}
551
552impl<'de> Deserialize<'de> for FixString {
553 fn deserialize<D>(deserializer: D) -> Result<FixString, D::Error>
554 where
555 D: Deserializer<'de>,
556 {
557 deserializer.deserialize_str(FixStringVisitor)
558 }
559}
560
561impl Serialize for FixString {
562 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
563 where
564 S: Serializer,
565 {
566 serializer.serialize_str(self.as_utf8())
567 }
568}
569
570pub trait ToFixString {
571 fn to_fix_string(&self) -> FixString;
572}
573
574impl ToFixString for FixStr {
575 fn to_fix_string(&self) -> FixString {
576 unsafe { FixString::from_ascii_unchecked(self.as_bytes().to_owned()) }
578 }
579}
580
581macro_rules! impl_to_fix_string_for_integer {
582 ($t:ty) => {
583 impl ToFixString for $t {
584 fn to_fix_string(&self) -> FixString {
585 unsafe {
587 FixString::from_ascii_unchecked(
588 itoa::Buffer::new().format(*self).as_bytes().to_vec(),
589 )
590 }
591 }
592 }
593 };
594}
595
596impl_to_fix_string_for_integer!(i8);
597impl_to_fix_string_for_integer!(i16);
598impl_to_fix_string_for_integer!(i32);
599impl_to_fix_string_for_integer!(i64);
600impl_to_fix_string_for_integer!(isize);
601impl_to_fix_string_for_integer!(u8);
602impl_to_fix_string_for_integer!(u16);
603impl_to_fix_string_for_integer!(u32);
604impl_to_fix_string_for_integer!(u64);
605impl_to_fix_string_for_integer!(usize);
606
607fn deserialize_fraction_of_second<E>(buf: &[u8]) -> Result<(u32, u8), E>
608where
609 E: de::Error,
610{
611 let [b'.', buf @ ..] = buf else {
620 return Err(de::Error::custom("incorrecct data format for UtcTimestamp"));
621 };
622
623 let mut fraction_of_second: u64 = 0;
624 for i in 0..buf.len() {
625 match unsafe { buf.get_unchecked(i) } {
627 n @ b'0'..=b'9' => {
628 fraction_of_second = fraction_of_second
629 .checked_mul(10)
630 .and_then(|v| v.checked_add((n - b'0') as u64))
631 .ok_or_else(|| de::Error::custom("incorrect fraction of second (overflow)"))?;
632 }
633 _ => {
634 return Err(de::Error::custom(
635 "incorrecct data format for fraction of second",
636 ));
637 }
638 }
639 }
640 let (multiplier, divider) = match buf.len() {
641 3 => (1_000_000, 1),
642 6 => (1_000, 1),
643 9 => (1, 1),
644 12 => (1, 1_000),
647 _ => {
648 return Err(de::Error::custom(
649 "incorrect fraction of second (wrong precision)",
650 ));
651 }
652 };
653 (fraction_of_second * multiplier / divider)
654 .try_into()
655 .map(|adjusted_fraction_of_second| (adjusted_fraction_of_second, buf.len() as u8))
656 .map_err(|_| de::Error::custom("incorrecct data format for UtcTimestamp"))
657}
658
659struct UtcTimestampVisitor;
660
661impl Visitor<'_> for UtcTimestampVisitor {
662 type Value = UtcTimestamp;
663
664 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
665 formatter.write_str("string")
666 }
667
668 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
687 where
688 E: de::Error,
689 {
690 match value.as_bytes() {
691 [
692 y3 @ b'0'..=b'9',
694 y2 @ b'0'..=b'9',
695 y1 @ b'0'..=b'9',
696 y0 @ b'0'..=b'9',
697 m1 @ b'0'..=b'1',
699 m0 @ b'0'..=b'9',
700 d1 @ b'0'..=b'3',
702 d0 @ b'0'..=b'9',
703 b'-',
704 h1 @ b'0'..=b'2',
706 h0 @ b'0'..=b'9',
707 b':',
708 mm1 @ b'0'..=b'5',
710 mm0 @ b'0'..=b'9',
711 b':',
712 s1 @ b'0'..=b'5',
715 s0 @ b'0'..=b'9',
716 ..,
717 ] => {
718 let value = &value[17..];
719 let year = (y3 - b'0') as i32 * 1000
720 + (y2 - b'0') as i32 * 100
721 + (y1 - b'0') as i32 * 10
722 + (y0 - b'0') as i32;
723 let month = (m1 - b'0') as u32 * 10 + (m0 - b'0') as u32;
724 let day = (d1 - b'0') as u32 * 10 + (d0 - b'0') as u32;
725 let naive_date = NaiveDate::from_ymd_opt(year, month, day)
726 .ok_or_else(|| de::Error::custom("incorrecct data format for UtcTimestamp"))?;
727 let hour = (h1 - b'0') as u32 * 10 + (h0 - b'0') as u32;
728 let min = (mm1 - b'0') as u32 * 10 + (mm0 - b'0') as u32;
729 let sec = (s1 - b'0') as u32 * 10 + (s0 - b'0') as u32;
730 let (fraction_of_second, precision) =
731 deserialize_fraction_of_second(value.as_bytes())?;
732 let naive_date_time = naive_date
733 .and_hms_nano_opt(hour, min, sec, fraction_of_second)
734 .ok_or_else(|| de::Error::custom("incorrecct data format for UtcTimestamp"))?;
735 let timestamp = Utc.from_utc_datetime(&naive_date_time);
736
737 match precision {
738 0 => Ok(UtcTimestamp::with_secs(timestamp)),
739 3 => Ok(UtcTimestamp::with_millis(timestamp)),
740 6 => Ok(UtcTimestamp::with_micros(timestamp)),
741 9 => Ok(UtcTimestamp::with_nanos(timestamp)),
742 12 => Ok(UtcTimestamp::with_nanos(timestamp)),
745 _ => Err(de::Error::custom("incorrecct data format for UtcTimestamp")),
746 }
747 }
748 _ => Err(de::Error::custom("incorrecct data format for UtcTimestamp")),
749 }
750 }
751}
752
753impl<'de> Deserialize<'de> for UtcTimestamp {
754 fn deserialize<D>(deserializer: D) -> Result<UtcTimestamp, D::Error>
755 where
756 D: Deserializer<'de>,
757 {
758 deserializer.deserialize_str(UtcTimestampVisitor)
759 }
760}
761
762impl Serialize for UtcTimestamp {
763 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
764 where
765 S: Serializer,
766 {
767 let formatted_timestamp = self.format_precisely().to_string();
768 serializer.serialize_str(&formatted_timestamp)
769 }
770}
771
772impl PartialEq for UtcTimestamp {
773 fn eq(&self, other: &Self) -> bool {
774 self.timestamp == other.timestamp
775 }
776}
777
778impl Eq for UtcTimestamp {}
779
780#[expect(clippy::non_canonical_partial_ord_impl)]
781impl PartialOrd for UtcTimestamp {
782 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
783 Some(self.timestamp.cmp(&other.timestamp))
784 }
785}
786
787impl Ord for UtcTimestamp {
788 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
789 self.timestamp().cmp(&other.timestamp())
790 }
791}
792
793impl fmt::Display for UtcTimestamp {
794 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
795 let result = self.format_precisely().to_string();
796 write!(f, "{}", result)
797 }
798}
799
800impl UtcTimestamp {
801 pub const MAX_UTC: UtcTimestamp = UtcTimestamp {
802 timestamp: DateTime::<Utc>::MAX_UTC,
803 precision: TimePrecision::Nanos,
804 };
805 pub const MIN_UTC: UtcTimestamp = UtcTimestamp {
806 timestamp: DateTime::<Utc>::MIN_UTC,
807 precision: TimePrecision::Nanos,
808 };
809
810 pub fn now() -> UtcTimestamp {
812 UtcTimestamp::with_precision(Utc::now(), TimePrecision::default())
813 }
814
815 pub fn with_precision(date_time: DateTime<Utc>, precision: TimePrecision) -> UtcTimestamp {
818 match precision {
819 TimePrecision::Secs => UtcTimestamp::with_secs(date_time),
820 TimePrecision::Millis => UtcTimestamp::with_millis(date_time),
821 TimePrecision::Micros => UtcTimestamp::with_micros(date_time),
822 TimePrecision::Nanos => UtcTimestamp::with_nanos(date_time),
823 }
824 }
825
826 fn timestamp_from_secs_and_nsecs(secs: i64, nsecs: u32) -> DateTime<Utc> {
827 DateTime::from_timestamp(secs, nsecs).unwrap()
828 }
829
830 pub fn with_secs(date_time: DateTime<Utc>) -> UtcTimestamp {
833 let secs = date_time.timestamp();
834 UtcTimestamp {
835 timestamp: Self::timestamp_from_secs_and_nsecs(secs, 0),
836 precision: TimePrecision::Secs,
837 }
838 }
839
840 pub fn now_with_secs() -> UtcTimestamp {
841 UtcTimestamp::with_secs(Utc::now())
842 }
843
844 pub fn with_millis(date_time: DateTime<Utc>) -> UtcTimestamp {
847 let secs = date_time.timestamp();
848 let nsecs = date_time.timestamp_subsec_millis() * 1_000_000;
849 UtcTimestamp {
850 timestamp: Self::timestamp_from_secs_and_nsecs(secs, nsecs),
851 precision: TimePrecision::Millis,
852 }
853 }
854
855 pub fn with_micros(date_time: DateTime<Utc>) -> UtcTimestamp {
858 let secs = date_time.timestamp();
859 let nsecs = date_time.timestamp_subsec_micros() * 1_000;
860 UtcTimestamp {
861 timestamp: Self::timestamp_from_secs_and_nsecs(secs, nsecs),
862 precision: TimePrecision::Micros,
863 }
864 }
865
866 pub fn with_nanos(date_time: DateTime<Utc>) -> UtcTimestamp {
869 let secs = date_time.timestamp();
870 let nsecs = date_time.timestamp_subsec_nanos();
871 UtcTimestamp {
872 timestamp: Self::timestamp_from_secs_and_nsecs(secs, nsecs),
873 precision: TimePrecision::Nanos,
874 }
875 }
876
877 pub fn format_precisely(&self) -> DelayedFormat<StrftimeItems<'_>> {
879 match self.precision {
880 TimePrecision::Secs => self.format("%Y%m%d-%H:%M:%S"),
881 TimePrecision::Millis => self.format("%Y%m%d-%H:%M:%S%.3f"),
882 TimePrecision::Micros => self.format("%Y%m%d-%H:%M:%S%.6f"),
883 TimePrecision::Nanos => self.format("%Y%m%d-%H:%M:%S%.9f"),
884 }
885 }
886
887 pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat<StrftimeItems<'a>> {
888 self.timestamp.format(fmt)
889 }
890
891 pub fn timestamp(&self) -> DateTime<Utc> {
892 self.timestamp
893 }
894
895 pub fn precision(&self) -> TimePrecision {
896 self.precision
897 }
898}
899
900impl UtcTimeOnly {
901 pub fn with_secs(time: NaiveTime) -> UtcTimeOnly {
904 UtcTimeOnly {
905 timestamp: time.with_nanosecond(0).unwrap(),
906 precision: TimePrecision::Secs,
907 }
908 }
909
910 pub fn with_millis(time: NaiveTime) -> UtcTimeOnly {
913 UtcTimeOnly {
914 timestamp: time.with_nanosecond(time.nanosecond() / 1_000_000).unwrap(),
915 precision: TimePrecision::Millis,
916 }
917 }
918
919 pub fn with_micros(time: NaiveTime) -> UtcTimeOnly {
922 UtcTimeOnly {
923 timestamp: time.with_nanosecond(time.nanosecond() / 1_000).unwrap(),
924 precision: TimePrecision::Micros,
925 }
926 }
927
928 pub fn with_nanos(time: NaiveTime) -> UtcTimeOnly {
931 UtcTimeOnly {
932 timestamp: time,
933 precision: TimePrecision::Nanos,
934 }
935 }
936
937 pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat<StrftimeItems<'a>> {
938 self.timestamp.format(fmt)
939 }
940
941 pub fn timestamp(&self) -> NaiveTime {
942 self.timestamp
943 }
944
945 pub fn precision(&self) -> TimePrecision {
946 self.precision
947 }
948}
949
950#[cfg(test)]
951mod tests {
952 use super::*;
953
954 #[test]
955 fn fix_string_fail_on_ctrl_character() {
956 let buf = b"Hello\x01world!".to_vec();
957 assert!(FixString::from_ascii(buf).is_err());
958 }
959
960 #[test]
961 fn fix_string_fail_on_out_of_range_character() {
962 let buf = b"Hello\x85world!".to_vec();
963 assert!(FixString::from_ascii(buf).is_err());
964 }
965
966 #[test]
967 fn fix_string_replacemen_character_on_ctrl() {
968 let buf = b"Hello\x01world!".to_vec();
969 assert_eq!(FixString::from_ascii_lossy(buf), "Hello?world!");
970 }
971
972 #[test]
973 fn fix_string_replacemen_character_on_out_of_range() {
974 let buf = b"Hello\x85world!".to_vec();
975 assert_eq!(FixString::from_ascii_lossy(buf), "Hello?world!");
976 }
977
978 #[test]
979 fn utc_timestamp_default_precision_nanos() {
980 let now = UtcTimestamp::now();
981 assert_eq!(now.precision(), TimePrecision::Nanos);
982 }
983}