easyfix_messages/fields/
basic_types.rs

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];
43// TODO: don't use Vec here
44pub 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
72// TODO: don't use Vec here
73pub type TzTimestamp = Vec<u8>;
74pub type TzTimeOnly = Vec<u8>;
75
76pub type Length = u16;
77pub type Data = Vec<u8>;
78pub type XmlData = Data;
79
80// TODO: don't use Vec here
81pub type Tenor = Vec<u8>;
82
83#[derive(Debug)]
84pub struct FixStringError {
85    idx: usize,
86    value: u8,
87}
88
89impl FixStringError {
90    /// Returns the index of unexpected character.
91    pub fn idx(&self) -> usize {
92        self.idx
93    }
94
95    /// Returns the value of unexpected character.
96    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    /// Converts a slice of bytes to a string slice.
119    ///
120    /// A FIX string slice ([`&FixStr`]) is made of bytes ([`u8`]), and a byte
121    /// slice ([`&[u8]`][slice]) is made of bytes, so this function
122    /// converts between the two. Not all byte slices are valid string slices,
123    /// however: [`&FixStr`] requires that it is valid ASCII without controll
124    /// characters.
125    /// `from_ascii()` checks to ensure that the bytes are valid, and then does
126    /// the conversion.
127    ///
128    /// [`&FixStr`]: FixStr
129    ///
130    /// If you are sure that the byte slice is valid ASCII without controll
131    /// characters, and you don't want to incur the overhead of the validity
132    /// check, there is an unsafe version of this function,
133    /// [`from_ascii_unchecked`], which has the same behavior but skips
134    /// the check.
135    ///
136    /// [`from_ascii_unchecked`]: FixStr::from_ascii_unchecked
137    ///
138    /// If you need a `FixString` instead of a `&FixStr`, consider
139    /// [`FixString::from_ascii`].
140    ///
141    /// Because you can stack-allocate a `[u8; N]`, and you can take a
142    /// [`&[u8]`][slice] of it, this function is one way to have a
143    /// stack-allocated string.
144    ///
145    /// # Errors
146    ///
147    /// Returns `Err` if the slice is not ASCII.
148    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        // SAFETY: `buf` validity checked just above.
158        unsafe { Ok(FixStr::from_ascii_unchecked(buf)) }
159    }
160
161    /// Converts a slice of bytes to a FIX string slice without checking
162    /// that it contains only ASCII characters.
163    ///
164    /// See the safe version, [`from_ascii`], for more information.
165    ///
166    /// [`from_ascii`]: FixStr::from_ascii
167    ///
168    /// # Safety
169    ///
170    /// The bytes passed in must consists from ASCII characters only.
171    pub const unsafe fn from_ascii_unchecked(buf: &[u8]) -> &FixStr {
172        // SAFETY: the caller must guarantee that the bytes `buf` are valid ASCII.
173        // Also relies on `&FixStr` and `&[u8]` having the same layout.
174        unsafe { mem::transmute(buf) }
175    }
176
177    pub const fn as_utf8(&self) -> &str {
178        // SAFETY: ASCII is always valid UTF-8
179        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/// Creates a `FixString` using interpolation of runtime expressions, replacing
316/// invalid characters by `?`.
317///
318/// See [the formatting syntax documentation in `std::fmt`] for details.
319#[macro_export]
320macro_rules! fix_format {
321    ($($arg:tt)*) => {{
322        FixString::from_ascii_lossy(std::format!($($arg)*).into_bytes())
323    }}
324}
325
326// TODO: Optional feature for ISO 8859-1 encoded strings
327impl 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    /// Converts a vector of bytes to a `FixString`.
337    ///
338    /// A FIX string ([`FixString`]) is made of bytes ([`u8`]),
339    /// and a vector of bytes ([`Vec<u8>`]) is made of bytes, so this function
340    /// converts between the two. Not all byte slices are valid `FixString`s,
341    /// however: `FixString` requires that it is valid ASCII.
342    /// `from_ascii()` checks to ensure that the bytes are valid ASCII,
343    /// and then does the conversion.
344    ///
345    /// If you are sure that the byte slice is valid ASCII, and you don't want
346    /// to incur the overhead of the validity check, there is an unsafe version
347    /// of this function, [`from_ascii_unchecked`], which has the same behavior
348    /// but skips the check.
349    ///
350    /// This method will take care to not copy the vector, for efficiency's
351    /// sake.
352    ///
353    /// If you need a [`&FixStr`] instead of a `FixString`, consider
354    /// [`FixStr::from_ascii`].
355    ///
356    /// The inverse of this method is [`into_bytes`].
357    ///
358    /// # Errors
359    ///
360    /// Returns [`Err`] if the slice is not ASCII with a description as to why
361    /// the provided bytes are not ASCII.
362    ///
363    /// [`from_ascii_unchecked`]: FixString::from_ascii_unchecked
364    /// [`Vec<u8>`]: std::vec::Vec "Vec"
365    /// [`&FixStr`]: FixStr
366    /// [`into_bytes`]: FixString::into_bytes
367    pub fn from_ascii(buf: Vec<u8>) -> Result<FixString, FixStringError> {
368        for i in 0..buf.len() {
369            // SAFETY: `i` never exceeds buf.len()
370            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    /// Converts a vector of bytes to a `FixString` without checking that the
379    /// it contains only ASCII characters.
380    ///
381    /// See the safe version, [`from_ascii`], for more details.
382    ///
383    /// [`from_ascii`]: FixString::from_ascii
384    ///
385    /// # Safety
386    ///
387    /// This function is unsafe because it does not check that the bytes passed
388    /// to it are valid ASCII. If this constraint is violated, it may cause
389    /// memory unsafety issues with future users of the `FixString`,
390    /// as the rest of the library assumes that `FixString`s are valid ASCII.
391    pub unsafe fn from_ascii_unchecked(buf: Vec<u8>) -> FixString {
392        FixString(buf)
393    }
394
395    /// Converts a slice of bytes to a `FixString`, replacing invalid
396    /// characters by `?`.
397    pub fn from_ascii_lossy(mut buf: Vec<u8>) -> FixString {
398        for i in 0..buf.len() {
399            // SAFETY: `i` never exceeds buf.len()
400            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        // SAFETY: ASCII is always valid UTF-8
410        unsafe { std::str::from_utf8_unchecked(&self.0) }
411    }
412
413    pub fn into_utf8(self) -> String {
414        // SAFETY: ASCII is always valid UTF-8
415        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        // SAFETY: FixString consists of ASCII characters only thus it's valid UTF-8
484        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        // TODO: check vefore allocation
493        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        // SAFETY: FixStr is already checked against invalid characters
577        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                // SAFETY: integers are always formatted using ASCII characters
586                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    // match buf {
612    //     // Do nothing here, fraction of second will be deserializede below
613    //     [b'.', ..] => buf = &buf[1..],
614    //     _ => {
615    //         return Err(de::Error::custom("incorrecct data format for UtcTimestamp"));
616    //     }
617    // }
618
619    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        // SAFETY: i is between 0 and buf.len()
626        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        // XXX: Types from `chrono` crate can't hold
645        //      time at picosecond resolution
646        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    /// TODO: Same as in Deserializer
669    /// Deserialize string representing time/date combination represented
670    /// in UTC (Universal Time Coordinated) in either YYYYMMDD-HH:MM:SS
671    /// (whole seconds) or YYYYMMDD-HH:MM:SS.sss* format, colons, dash,
672    /// and period required.
673    ///
674    /// # Valid values:
675    /// - YYYY = 0000-9999,
676    /// - MM = 01-12,
677    /// - DD = 01-31,
678    /// - HH = 00-23,
679    /// - MM = 00-59,
680    /// - SS = 00-60 (60 only if UTC leap second),
681    /// - sss* fractions of seconds. The fractions of seconds may be empty when
682    ///   no fractions of seconds are conveyed (in such a case the period
683    ///   is not conveyed), it may include 3 digits to convey
684    ///   milliseconds, 6 digits to convey microseconds, 9 digits
685    ///   to convey nanoseconds, 12 digits to convey picoseconds;
686    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                // Year
693                y3 @ b'0'..=b'9',
694                y2 @ b'0'..=b'9',
695                y1 @ b'0'..=b'9',
696                y0 @ b'0'..=b'9',
697                // Month
698                m1 @ b'0'..=b'1',
699                m0 @ b'0'..=b'9',
700                // Day
701                d1 @ b'0'..=b'3',
702                d0 @ b'0'..=b'9',
703                b'-',
704                // Hour
705                h1 @ b'0'..=b'2',
706                h0 @ b'0'..=b'9',
707                b':',
708                // Minute
709                mm1 @ b'0'..=b'5',
710                mm0 @ b'0'..=b'9',
711                b':',
712                // TODO: leap second!
713                // Second
714                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                    // XXX: Types from `chrono` crate can't hold
743                    //      time at picosecond resolution
744                    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    /// Creates UtcTimestamp that represents current date and time with default precision
811    pub fn now() -> UtcTimestamp {
812        UtcTimestamp::with_precision(Utc::now(), TimePrecision::default())
813    }
814
815    /// Creates UtcTimestamp with given time precision
816    /// input's precision is adjusted to requested one
817    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    /// Creates UtcTimestamp with time precision set to full seconds
831    /// input's precision is adjusted to requested one
832    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    /// Creates UtcTimestamp with time precision set to milliseconds
845    /// input's precision is adjusted to requested one
846    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    /// Creates UtcTimestamp with time precision set to microseconds
856    /// input's precision is adjusted to requested one
857    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    /// Creates UtcTimestamp with time precision set to nanoseconds
867    /// input's precision is adjusted to requested one
868    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    /// Formats timestamp with precision set inside the struct
878    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    /// Creates UtcTimeOnly with time precision set to full seconds
902    /// input's precision is adjusted to requested one
903    pub fn with_secs(time: NaiveTime) -> UtcTimeOnly {
904        UtcTimeOnly {
905            timestamp: time.with_nanosecond(0).unwrap(),
906            precision: TimePrecision::Secs,
907        }
908    }
909
910    /// Creates UtcTimeOnly with time precision set to full milliseconds
911    /// input's precision is adjusted to requested one
912    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    /// Creates UtcTimeOnly with time precision set to full microseconds
920    /// input's precision is adjusted to requested one
921    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    /// Creates UtcTimeOnly with time precision set to full nanoseconds
929    /// input's precision is adjusted to requested one
930    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}