Skip to main content

nom_exif/
values.rs

1use std::{
2    fmt::{Display, LowerHex},
3    string::FromUtf8Error,
4};
5
6use chrono::{DateTime, FixedOffset, NaiveDateTime};
7
8use nom::{multi::many_m_n, number::Endianness, AsChar, Parser};
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize, Serializer};
11
12use crate::{error::EntryError, ExifTag};
13
14/// EXIF datetime value with timezone awareness preserved.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ExifDateTime {
17    /// Original value carried a timezone (e.g. assembled with `OffsetTimeOriginal`).
18    Aware(DateTime<FixedOffset>),
19    /// Original value had no timezone (raw `DateTime` tag).
20    Naive(NaiveDateTime),
21}
22
23impl ExifDateTime {
24    /// Returns the timezone-aware form only when the original value carried one.
25    pub fn aware(&self) -> Option<DateTime<FixedOffset>> {
26        match self {
27            ExifDateTime::Aware(dt) => Some(*dt),
28            ExifDateTime::Naive(_) => None,
29        }
30    }
31
32    /// Always returns a `NaiveDateTime` — strips the timezone if present.
33    pub fn into_naive(self) -> NaiveDateTime {
34        match self {
35            ExifDateTime::Aware(dt) => dt.naive_local(),
36            ExifDateTime::Naive(ndt) => ndt,
37        }
38    }
39
40    /// If naive, attaches `fallback`; if already aware, returns the original offset.
41    pub fn or_offset(self, fallback: FixedOffset) -> DateTime<FixedOffset> {
42        match self {
43            ExifDateTime::Aware(dt) => dt,
44            ExifDateTime::Naive(ndt) => ndt
45                .and_local_timezone(fallback)
46                .single()
47                .unwrap_or_else(|| ndt.and_utc().with_timezone(&fallback)),
48        }
49    }
50}
51
52/// Represent a parsed entry value.
53#[derive(Debug, Clone, PartialEq)]
54#[non_exhaustive]
55pub enum EntryValue {
56    Text(String),
57    URational(URational),
58    IRational(IRational),
59
60    U8(u8),
61    U16(u16),
62    U32(u32),
63    U64(u64),
64
65    I8(i8),
66    I16(i16),
67    I32(i32),
68    I64(i64),
69
70    F32(f32),
71    F64(f64),
72
73    DateTime(DateTime<FixedOffset>),
74    NaiveDateTime(NaiveDateTime),
75    Undefined(Vec<u8>),
76
77    URationalArray(Vec<URational>),
78    IRationalArray(Vec<IRational>),
79
80    U8Array(Vec<u8>),
81    U16Array(Vec<u16>),
82    U32Array(Vec<u32>),
83}
84
85#[derive(Clone, Debug, PartialEq, Eq)]
86pub(crate) struct EntryData<'a> {
87    pub endian: Endianness,
88    pub tag: u16,
89    pub data: &'a [u8],
90    pub data_format: DataFormat,
91    pub components_num: u32,
92}
93
94impl EntryData<'_> {
95    // Ensure that the returned Vec is not empty.
96    fn try_as_rationals<T: TryFromBytes + Copy>(&self) -> Result<Vec<Rational<T>>, EntryError> {
97        if self.components_num == 0 {
98            return Err(EntryError::InvalidShape {
99                format: self.data_format as u16,
100                count: self.components_num,
101            });
102        }
103
104        let mut vec = Vec::with_capacity(self.components_num as usize);
105        for i in 0..self.components_num {
106            let rational = decode_rational::<T>(&self.data[i as usize * 8..], self.endian)?;
107            vec.push(rational);
108        }
109        Ok(vec)
110    }
111}
112
113impl EntryValue {
114    /// Parse an IFD entry value.
115    ///
116    /// # Structure of IFD Entry
117    ///
118    /// ```txt
119    /// | 2   | 2           | 4              | 4                      |
120    /// | tag | data format | components num | data (value or offset) |
121    /// ```
122    ///
123    /// # Data size
124    ///
125    /// `data_size = components_num * bytes_per_component`
126    ///
127    /// `bytes_per_component` is determined by tag & data format.
128    ///
129    /// If data_size > 4, then the data area of entry stores the offset of the
130    /// value, not the value itself.
131    ///
132    /// # Data format
133    ///
134    /// See: [`DataFormat`].
135    pub(crate) fn parse(entry: &EntryData, tz: &Option<String>) -> Result<EntryValue, EntryError> {
136        if entry.data.is_empty() {
137            return Err(EntryError::InvalidShape {
138                format: entry.data_format as u16,
139                count: entry.components_num,
140            });
141        }
142
143        let endian = entry.endian;
144        let tag = entry.tag;
145        let data_format = entry.data_format;
146        let data = entry.data;
147        let components_num = entry.components_num;
148
149        if data.is_empty() || components_num == 0 {
150            return Ok(EntryValue::variant_default(data_format));
151        }
152
153        let exif_tag = ExifTag::from_code(tag);
154        if let Some(tag) = exif_tag {
155            if tag == ExifTag::DateTimeOriginal
156                || tag == ExifTag::CreateDate
157                || tag == ExifTag::ModifyDate
158            {
159                let s = get_cstr(data).map_err(|_| EntryError::InvalidValue("invalid utf-8"))?;
160
161                let t = if let Some(tz) = tz {
162                    let tz = repair_tz_str(tz);
163                    let ss = format!("{s} {tz}");
164                    match DateTime::parse_from_str(&ss, "%Y:%m:%d %H:%M:%S %z") {
165                        Ok(t) => t,
166                        Err(_) => return Ok(EntryValue::NaiveDateTime(parse_naive_time(s)?)),
167                    }
168                } else {
169                    return Ok(EntryValue::NaiveDateTime(parse_naive_time(s)?));
170                };
171
172                return Ok(EntryValue::DateTime(t));
173            }
174        }
175
176        match data_format {
177            DataFormat::U8 => match components_num {
178                1 => Ok(Self::U8(data[0])),
179                _ => Ok(Self::U8Array(data.into())),
180            },
181            DataFormat::Text => Ok(EntryValue::Text(
182                get_cstr(data).map_err(|_| EntryError::InvalidValue("invalid utf-8"))?,
183            )),
184            DataFormat::U16 => {
185                if components_num == 1 {
186                    Ok(Self::U16(u16::try_from_bytes(data, endian)?))
187                } else {
188                    let (_, v) = many_m_n::<_, nom::error::Error<_>, _>(
189                        components_num as usize,
190                        components_num as usize,
191                        nom::number::complete::u16(endian),
192                    )
193                    .parse(data)
194                    .map_err(|_| EntryError::InvalidShape {
195                        format: DataFormat::U16 as u16,
196                        count: components_num,
197                    })?;
198                    Ok(Self::U16Array(v))
199                }
200            }
201            DataFormat::U32 => {
202                if components_num == 1 {
203                    Ok(Self::U32(u32::try_from_bytes(data, endian)?))
204                } else {
205                    let (_, v) = many_m_n::<_, nom::error::Error<_>, _>(
206                        components_num as usize,
207                        components_num as usize,
208                        nom::number::complete::u32(endian),
209                    )
210                    .parse(data)
211                    .map_err(|_| EntryError::InvalidShape {
212                        format: DataFormat::U32 as u16,
213                        count: components_num,
214                    })?;
215                    Ok(Self::U32Array(v))
216                }
217            }
218            DataFormat::URational => {
219                let rationals = entry.try_as_rationals::<u32>()?;
220                if rationals.len() == 1 {
221                    Ok(Self::URational(rationals[0]))
222                } else {
223                    Ok(Self::URationalArray(rationals))
224                }
225            }
226            DataFormat::I8 => match components_num {
227                1 => Ok(Self::I8(data[0] as i8)),
228                x => Err(EntryError::InvalidShape {
229                    format: data_format as u16,
230                    count: x,
231                }),
232            },
233            DataFormat::Undefined => Ok(Self::Undefined(data.to_vec())),
234            DataFormat::I16 => match components_num {
235                1 => Ok(Self::I16(i16::try_from_bytes(data, endian)?)),
236                x => Err(EntryError::InvalidShape {
237                    format: data_format as u16,
238                    count: x,
239                }),
240            },
241            DataFormat::I32 => match components_num {
242                1 => Ok(Self::I32(i32::try_from_bytes(data, endian)?)),
243                x => Err(EntryError::InvalidShape {
244                    format: data_format as u16,
245                    count: x,
246                }),
247            },
248            DataFormat::IRational => {
249                let rationals = entry.try_as_rationals::<i32>()?;
250                if rationals.len() == 1 {
251                    Ok(Self::IRational(rationals[0]))
252                } else {
253                    Ok(Self::IRationalArray(rationals))
254                }
255            }
256            DataFormat::F32 => match components_num {
257                1 => Ok(Self::F32(f32::try_from_bytes(data, endian)?)),
258                x => Err(EntryError::InvalidShape {
259                    format: data_format as u16,
260                    count: x,
261                }),
262            },
263            DataFormat::F64 => match components_num {
264                1 => Ok(Self::F64(f64::try_from_bytes(data, endian)?)),
265                x => Err(EntryError::InvalidShape {
266                    format: data_format as u16,
267                    count: x,
268                }),
269            },
270        }
271    }
272
273    fn variant_default(data_format: DataFormat) -> EntryValue {
274        match data_format {
275            DataFormat::U8 => Self::U8(0),
276            DataFormat::Text => Self::Text(String::default()),
277            DataFormat::U16 => Self::U16(0),
278            DataFormat::U32 => Self::U32(0),
279            DataFormat::URational => Self::URational(URational::default()),
280            DataFormat::I8 => Self::I8(0),
281            DataFormat::Undefined => Self::Undefined(Vec::default()),
282            DataFormat::I16 => Self::I16(0),
283            DataFormat::I32 => Self::I32(0),
284            DataFormat::IRational => Self::IRational(IRational::default()),
285            DataFormat::F32 => Self::F32(0.0),
286            DataFormat::F64 => Self::F64(0.0),
287        }
288    }
289
290    pub fn as_str(&self) -> Option<&str> {
291        match self {
292            EntryValue::Text(v) => Some(v),
293            _ => None,
294        }
295    }
296
297    /// EXIF datetime accessor.
298    ///
299    /// Returns `Some(ExifDateTime::Aware)` when the parsed value carried a
300    /// timezone (e.g. composed with `OffsetTimeOriginal`); returns
301    /// `Some(ExifDateTime::Naive)` for tags that ship without timezone info;
302    /// returns `None` for non-datetime values.
303    ///
304    /// ```rust
305    /// use nom_exif::*;
306    /// use chrono::{DateTime, NaiveDateTime, FixedOffset};
307    ///
308    /// let dt = DateTime::parse_from_str("2023-07-09T20:36:33+08:00", "%+").unwrap();
309    /// let ev = EntryValue::DateTime(dt);
310    /// assert!(matches!(ev.as_datetime(), Some(ExifDateTime::Aware(_))));
311    ///
312    /// let ndt = NaiveDateTime::parse_from_str("2023-07-09T20:36:33", "%Y-%m-%dT%H:%M:%S").unwrap();
313    /// let ev = EntryValue::NaiveDateTime(ndt);
314    /// assert!(matches!(ev.as_datetime(), Some(ExifDateTime::Naive(_))));
315    /// ```
316    pub fn as_datetime(&self) -> Option<ExifDateTime> {
317        match self {
318            EntryValue::DateTime(v) => Some(ExifDateTime::Aware(*v)),
319            EntryValue::NaiveDateTime(v) => Some(ExifDateTime::Naive(*v)),
320            _ => None,
321        }
322    }
323
324    pub fn as_u8(&self) -> Option<u8> {
325        match self {
326            EntryValue::U8(v) => Some(*v),
327            _ => None,
328        }
329    }
330
331    pub fn as_i8(&self) -> Option<i8> {
332        match self {
333            EntryValue::I8(v) => Some(*v),
334            _ => None,
335        }
336    }
337
338    pub fn as_u16(&self) -> Option<u16> {
339        match self {
340            EntryValue::U16(v) => Some(*v),
341            _ => None,
342        }
343    }
344
345    pub fn as_i16(&self) -> Option<i16> {
346        match self {
347            EntryValue::I16(v) => Some(*v),
348            _ => None,
349        }
350    }
351
352    pub fn as_u64(&self) -> Option<u64> {
353        match self {
354            EntryValue::U64(v) => Some(*v),
355            _ => None,
356        }
357    }
358
359    pub fn as_u32(&self) -> Option<u32> {
360        match self {
361            EntryValue::U32(v) => Some(*v),
362            _ => None,
363        }
364    }
365
366    pub fn as_i32(&self) -> Option<i32> {
367        match self {
368            EntryValue::I32(v) => Some(*v),
369            _ => None,
370        }
371    }
372
373    pub fn as_i64(&self) -> Option<i64> {
374        if let EntryValue::I64(v) = self {
375            Some(*v)
376        } else {
377            None
378        }
379    }
380
381    pub fn as_f64(&self) -> Option<f64> {
382        if let EntryValue::F64(v) = self {
383            Some(*v)
384        } else {
385            None
386        }
387    }
388
389    /// Widen any integer EntryValue to i64. Returns None for non-integer values
390    /// (and for U64 values exceeding i64::MAX).
391    pub fn try_as_integer(&self) -> Option<i64> {
392        match self {
393            EntryValue::U8(v) => Some(*v as i64),
394            EntryValue::U16(v) => Some(*v as i64),
395            EntryValue::U32(v) => Some(*v as i64),
396            EntryValue::U64(v) => i64::try_from(*v).ok(),
397            EntryValue::I8(v) => Some(*v as i64),
398            EntryValue::I16(v) => Some(*v as i64),
399            EntryValue::I32(v) => Some(*v as i64),
400            EntryValue::I64(v) => Some(*v),
401            _ => None,
402        }
403    }
404
405    /// Widen any numeric EntryValue (integer / rational / float) to f64.
406    /// Rationals with denominator=0 return None.
407    pub fn try_as_float(&self) -> Option<f64> {
408        match self {
409            EntryValue::F32(v) => Some(*v as f64),
410            EntryValue::F64(v) => Some(*v),
411            EntryValue::URational(v) => v.to_f64(),
412            EntryValue::IRational(v) => v.to_f64(),
413            v => v.try_as_integer().map(|x| x as f64),
414        }
415    }
416
417    pub fn as_urational(&self) -> Option<URational> {
418        if let EntryValue::URational(v) = self {
419            Some(*v)
420        } else {
421            None
422        }
423    }
424
425    pub fn as_irational(&self) -> Option<IRational> {
426        if let EntryValue::IRational(v) = self {
427            Some(*v)
428        } else {
429            None
430        }
431    }
432
433    pub fn as_urational_slice(&self) -> Option<&[URational]> {
434        if let EntryValue::URationalArray(v) = self {
435            Some(v)
436        } else {
437            None
438        }
439    }
440
441    pub fn as_irational_slice(&self) -> Option<&[IRational]> {
442        if let EntryValue::IRationalArray(v) = self {
443            Some(v)
444        } else {
445            None
446        }
447    }
448
449    pub fn as_u8_slice(&self) -> Option<&[u8]> {
450        if let EntryValue::U8Array(v) = self {
451            Some(v)
452        } else {
453            None
454        }
455    }
456
457    pub fn as_u16_slice(&self) -> Option<&[u16]> {
458        if let EntryValue::U16Array(v) = self {
459            Some(v)
460        } else {
461            None
462        }
463    }
464
465    pub fn as_u32_slice(&self) -> Option<&[u32]> {
466        if let EntryValue::U32Array(v) = self {
467            Some(v)
468        } else {
469            None
470        }
471    }
472
473    pub fn as_undefined(&self) -> Option<&[u8]> {
474        if let EntryValue::Undefined(v) = self {
475            Some(v)
476        } else {
477            None
478        }
479    }
480}
481
482// Convert time components to EntryValue
483impl From<(NaiveDateTime, Option<FixedOffset>)> for EntryValue {
484    fn from(value: (NaiveDateTime, Option<FixedOffset>)) -> Self {
485        if let Some(offset) = value.1 {
486            EntryValue::DateTime(value.0.and_local_timezone(offset).unwrap())
487        } else {
488            EntryValue::NaiveDateTime(value.0)
489        }
490    }
491}
492
493fn parse_naive_time(s: String) -> Result<NaiveDateTime, EntryError> {
494    NaiveDateTime::parse_from_str(&s, "%Y:%m:%d %H:%M:%S")
495        .map_err(|_| EntryError::InvalidValue("invalid time format"))
496}
497
498fn repair_tz_str(tz: &str) -> String {
499    if let Some(idx) = tz.find(":") {
500        if tz[idx..].len() < 3 {
501            // Add tailed 0
502            return format!("{tz}0");
503        }
504    }
505    tz.into()
506}
507
508/// # Exif Data format
509///
510/// ```txt
511/// | Value           |             1 |             2 |              3 |               4 |                 5 |            6 |
512/// |-----------------+---------------+---------------+----------------+-----------------+-------------------+--------------|
513/// | Format          | unsigned byte | ascii strings | unsigned short |   unsigned long | unsigned rational |  signed byte |
514/// | Bytes/component |             1 |             1 |              2 |               4 |                 8 |            1 |
515///
516/// | Value           |             7 |             8 |              9 |              10 |                11 |           12 |
517/// |-----------------+---------------+---------------+----------------+-----------------+-------------------+--------------|
518/// | Format          |     undefined |  signed short |    signed long | signed rational |      single float | double float |
519/// | Bytes/component |             1 |             2 |              4 |               8 |                 4 |            8 |
520/// ```
521///
522/// See: [Exif](https://www.media.mit.edu/pia/Research/deepview/exif.html).
523#[repr(u16)]
524#[derive(Clone, Copy, Debug, PartialEq, Eq)]
525#[allow(unused)]
526pub(crate) enum DataFormat {
527    U8 = 1,
528    Text = 2,
529    U16 = 3,
530    U32 = 4,
531    URational = 5,
532    I8 = 6,
533    Undefined = 7,
534    I16 = 8,
535    I32 = 9,
536    IRational = 10,
537    F32 = 11,
538    F64 = 12,
539}
540
541impl DataFormat {
542    pub fn component_size(&self) -> usize {
543        match self {
544            Self::U8 | Self::I8 | Self::Text | Self::Undefined => 1,
545            Self::U16 | Self::I16 => 2,
546            Self::U32 | Self::I32 | Self::F32 => 4,
547            Self::URational | Self::IRational | Self::F64 => 8,
548        }
549    }
550}
551
552impl TryFrom<u16> for DataFormat {
553    /// On failure, returns the unrecognized format value so call sites can
554    /// pair it with the entry's `count` and build a richer `EntryError`.
555    type Error = u16;
556    fn try_from(v: u16) -> Result<Self, Self::Error> {
557        if v >= Self::U8 as u16 && v <= Self::F64 as u16 {
558            Ok(unsafe { std::mem::transmute::<u16, Self>(v) })
559        } else {
560            Err(v)
561        }
562    }
563}
564
565#[cfg(feature = "serde")]
566impl Serialize for EntryValue {
567    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
568    where
569        S: Serializer,
570    {
571        serializer.serialize_str(&self.to_string())
572    }
573}
574
575// impl std::fmt::Debug for EntryValue {
576//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
577//         Display::fmt(self, f)
578//     }
579// }
580
581impl Display for EntryValue {
582    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
583        match self {
584            EntryValue::Text(v) => v.fmt(f),
585            EntryValue::URational(v) => format!(
586                "{}/{} ({:.04})",
587                v.numerator(),
588                v.denominator(),
589                v.numerator() as f64 / v.denominator() as f64
590            )
591            .fmt(f),
592            EntryValue::IRational(v) => format!(
593                "{}/{} ({:.04})",
594                v.numerator(),
595                v.denominator(),
596                v.numerator() as f64 / v.denominator() as f64
597            )
598            .fmt(f),
599            EntryValue::U32(v) => Display::fmt(&v, f),
600            EntryValue::U16(v) => Display::fmt(&v, f),
601            EntryValue::U64(v) => Display::fmt(&v, f),
602            EntryValue::I16(v) => Display::fmt(&v, f),
603            EntryValue::I32(v) => Display::fmt(&v, f),
604            EntryValue::I64(v) => Display::fmt(&v, f),
605            EntryValue::F32(v) => Display::fmt(&v, f),
606            EntryValue::F64(v) => Display::fmt(&v, f),
607            EntryValue::U8(v) => Display::fmt(&v, f),
608            EntryValue::I8(v) => Display::fmt(&v, f),
609            EntryValue::DateTime(v) => Display::fmt(&v.to_rfc3339(), f),
610            EntryValue::NaiveDateTime(v) => Display::fmt(&v.format("%Y-%m-%d %H:%M:%S"), f),
611            EntryValue::Undefined(v) => fmt_array_to_string("Undefined", v, f),
612            EntryValue::URationalArray(v) => {
613                format!("URationalArray[{}]", rationals_to_string::<u32>(v)).fmt(f)
614            }
615            EntryValue::IRationalArray(v) => {
616                format!("IRationalArray[{}]", rationals_to_string::<i32>(v)).fmt(f)
617            }
618            EntryValue::U8Array(v) => fmt_array_to_string("U8Array", v, f),
619            EntryValue::U32Array(v) => fmt_array_to_string("U32Array", v, f),
620            EntryValue::U16Array(v) => fmt_array_to_string("U16Array", v, f),
621        }
622    }
623}
624
625pub(crate) fn fmt_array_to_string<T: Display + LowerHex>(
626    name: &str,
627    v: &[T],
628    f: &mut std::fmt::Formatter,
629) -> Result<(), std::fmt::Error> {
630    array_to_string(name, v).fmt(f)
631    // format!(
632    //     "{}[{}]",
633    //     name,
634    //     v.iter()
635    //         .map(|x| x.to_string())
636    //         .collect::<Vec<String>>()
637    //         .join(", ")
638    // )
639    // .fmt(f)
640}
641
642pub(crate) fn array_to_string<T: Display + LowerHex>(name: &str, v: &[T]) -> String {
643    // Display up to MAX_DISPLAY_NUM components, and replace the rest with ellipsis
644    const MAX_DISPLAY_NUM: usize = 8;
645    let s = v
646        .iter()
647        .map(|x| format!("0x{x:02x}"))
648        .take(MAX_DISPLAY_NUM + 1)
649        .enumerate()
650        .map(|(i, x)| {
651            if i >= MAX_DISPLAY_NUM {
652                "...".to_owned()
653            } else {
654                x
655            }
656        })
657        .collect::<Vec<String>>()
658        .join(", ");
659    format!("{}[{}]", name, s)
660}
661
662fn rationals_to_string<T>(rationals: &[Rational<T>]) -> String
663where
664    T: Display + Into<f64> + Copy,
665{
666    // Display up to MAX_DISPLAY_NUM components, and replace the rest with ellipsis
667    const MAX_DISPLAY_NUM: usize = 3;
668    rationals
669        .iter()
670        .map(|x| {
671            format!(
672                "{}/{} ({:.04})",
673                x.numerator(),
674                x.denominator(),
675                x.numerator().into() / x.denominator().into()
676            )
677        })
678        .take(MAX_DISPLAY_NUM + 1)
679        .enumerate()
680        .map(|(i, x)| {
681            if i >= MAX_DISPLAY_NUM {
682                "...".to_owned()
683            } else {
684                x
685            }
686        })
687        .collect::<Vec<String>>()
688        .join(", ")
689}
690
691impl From<DateTime<FixedOffset>> for EntryValue {
692    fn from(value: DateTime<FixedOffset>) -> Self {
693        EntryValue::DateTime(value)
694    }
695}
696
697impl From<u8> for EntryValue {
698    fn from(value: u8) -> Self {
699        EntryValue::U8(value)
700    }
701}
702impl From<u16> for EntryValue {
703    fn from(value: u16) -> Self {
704        EntryValue::U16(value)
705    }
706}
707impl From<u32> for EntryValue {
708    fn from(value: u32) -> Self {
709        EntryValue::U32(value)
710    }
711}
712impl From<u64> for EntryValue {
713    fn from(value: u64) -> Self {
714        EntryValue::U64(value)
715    }
716}
717
718impl From<i8> for EntryValue {
719    fn from(value: i8) -> Self {
720        EntryValue::I8(value)
721    }
722}
723impl From<i16> for EntryValue {
724    fn from(value: i16) -> Self {
725        EntryValue::I16(value)
726    }
727}
728impl From<i32> for EntryValue {
729    fn from(value: i32) -> Self {
730        EntryValue::I32(value)
731    }
732}
733impl From<i64> for EntryValue {
734    fn from(value: i64) -> Self {
735        EntryValue::I64(value)
736    }
737}
738
739impl From<f32> for EntryValue {
740    fn from(value: f32) -> Self {
741        EntryValue::F32(value)
742    }
743}
744impl From<f64> for EntryValue {
745    fn from(value: f64) -> Self {
746        EntryValue::F64(value)
747    }
748}
749
750impl From<String> for EntryValue {
751    fn from(value: String) -> Self {
752        EntryValue::Text(value)
753    }
754}
755
756impl From<&str> for EntryValue {
757    fn from(value: &str) -> Self {
758        value.to_owned().into()
759    }
760}
761
762pub type URational = Rational<u32>;
763pub type IRational = Rational<i32>;
764
765#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
766#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
767pub struct Rational<T> {
768    numerator: T,
769    denominator: T,
770}
771
772impl<T: Copy> Rational<T> {
773    pub const fn new(numerator: T, denominator: T) -> Self {
774        Self {
775            numerator,
776            denominator,
777        }
778    }
779
780    pub const fn numerator(&self) -> T {
781        self.numerator
782    }
783
784    pub const fn denominator(&self) -> T {
785        self.denominator
786    }
787}
788
789impl<T: Copy + Into<f64> + PartialEq + Default> Rational<T> {
790    /// Returns `None` if the denominator is zero.
791    #[allow(clippy::wrong_self_convention)]
792    pub fn to_f64(&self) -> Option<f64> {
793        if self.denominator == T::default() {
794            None
795        } else {
796            Some(self.numerator.into() / self.denominator.into())
797        }
798    }
799}
800
801impl TryFrom<IRational> for URational {
802    type Error = crate::ConvertError;
803    fn try_from(value: IRational) -> Result<Self, Self::Error> {
804        let n = value.numerator();
805        let d = value.denominator();
806        if n < 0 || d < 0 {
807            Err(crate::ConvertError::NegativeRational)
808        } else {
809            Ok(URational::new(n as u32, d as u32))
810        }
811    }
812}
813
814pub(crate) fn get_cstr(data: &[u8]) -> std::result::Result<String, FromUtf8Error> {
815    let vec = filter_zero(data);
816    if let Ok(s) = String::from_utf8(vec) {
817        Ok(s)
818    } else {
819        Ok(filter_zero(data)
820            .into_iter()
821            .map(|x| x.as_char())
822            .collect::<String>())
823    }
824}
825
826pub(crate) fn filter_zero(data: &[u8]) -> Vec<u8> {
827    data.iter()
828        // skip leading zero bytes
829        .skip_while(|b| **b == 0)
830        // ignore tailing zero bytes, and all bytes after zero bytes
831        .take_while(|b| **b != 0)
832        .cloned()
833        .collect::<Vec<u8>>()
834}
835
836pub(crate) trait TryFromBytes: Sized {
837    fn try_from_bytes(bs: &[u8], endian: Endianness) -> Result<Self, EntryError>;
838}
839
840macro_rules! impl_try_from_bytes {
841    ($type:ty) => {
842        impl TryFromBytes for $type {
843            fn try_from_bytes(bs: &[u8], endian: Endianness) -> Result<Self, EntryError> {
844                fn make_err<T>(available: usize) -> EntryError {
845                    EntryError::Truncated {
846                        needed: std::mem::size_of::<T>(),
847                        available,
848                    }
849                }
850                match endian {
851                    Endianness::Big => {
852                        let (int_bytes, _) = bs
853                            .split_at_checked(std::mem::size_of::<Self>())
854                            .ok_or_else(|| make_err::<Self>(bs.len()))?;
855                        Ok(Self::from_be_bytes(
856                            int_bytes
857                                .try_into()
858                                .map_err(|_| make_err::<Self>(bs.len()))?,
859                        ))
860                    }
861                    Endianness::Little => {
862                        let (int_bytes, _) = bs
863                            .split_at_checked(std::mem::size_of::<Self>())
864                            .ok_or_else(|| make_err::<Self>(bs.len()))?;
865                        Ok(Self::from_le_bytes(
866                            int_bytes
867                                .try_into()
868                                .map_err(|_| make_err::<Self>(bs.len()))?,
869                        ))
870                    }
871                    Endianness::Native => unimplemented!(),
872                }
873            }
874        }
875    };
876}
877
878impl_try_from_bytes!(u32);
879impl_try_from_bytes!(i32);
880impl_try_from_bytes!(u16);
881impl_try_from_bytes!(i16);
882impl_try_from_bytes!(f32);
883impl_try_from_bytes!(f64);
884
885pub(crate) fn decode_rational<T: TryFromBytes + Copy>(
886    data: &[u8],
887    endian: Endianness,
888) -> Result<Rational<T>, EntryError> {
889    if data.len() < 8 {
890        return Err(EntryError::Truncated {
891            needed: 8,
892            available: data.len(),
893        });
894    }
895
896    let numerator = T::try_from_bytes(data, endian)?;
897    let denominator = T::try_from_bytes(&data[4..], endian)?; // Safe-slice
898    Ok(Rational::<T>::new(numerator, denominator))
899}
900
901#[cfg(test)]
902mod tests {
903    use chrono::{Local, NaiveDateTime, TimeZone};
904
905    use super::*;
906
907    #[test]
908    fn test_parse_time() {
909        let s = "2023:07:09 20:36:33";
910        let t1 = NaiveDateTime::parse_from_str(s, "%Y:%m:%d %H:%M:%S").unwrap();
911        let t1 = Local.from_local_datetime(&t1).unwrap();
912
913        let tz = t1.format("%:z").to_string();
914
915        let s = format!("2023:07:09 20:36:33 {tz}");
916        let t2 = DateTime::parse_from_str(&s, "%Y:%m:%d %H:%M:%S %z").unwrap();
917
918        let t3 = t2.with_timezone(t2.offset());
919
920        assert_eq!(t1, t2);
921        assert_eq!(t1, t3);
922    }
923
924    #[test]
925    fn test_iso_8601() {
926        let s = "2023-11-02T19:58:34+0800";
927        let t1 = DateTime::parse_from_str(s, "%+").unwrap();
928
929        let s = "2023-11-02T19:58:34+08:00";
930        let t2 = DateTime::parse_from_str(s, "%+").unwrap();
931
932        let s = "2023-11-02T19:58:34.026490+08:00";
933        let t3 = DateTime::parse_from_str(s, "%+").unwrap();
934
935        assert_eq!(t1, t2);
936        assert!(t3 > t2);
937    }
938
939    #[test]
940    fn test_date_time_components() {
941        let dt = DateTime::parse_from_str("2023-07-09T20:36:33+08:00", "%+").unwrap();
942        let ndt =
943            NaiveDateTime::parse_from_str("2023-07-09T20:36:33", "%Y-%m-%dT%H:%M:%S").unwrap();
944        let offset = FixedOffset::east_opt(8 * 3600).unwrap();
945
946        let ev = EntryValue::DateTime(dt);
947        let edt = ev.as_datetime().unwrap();
948        assert_eq!(edt.aware(), Some(dt));
949        assert_eq!(edt.into_naive(), ndt);
950        assert_eq!(edt.or_offset(FixedOffset::east_opt(0).unwrap()), dt);
951
952        let ev = EntryValue::NaiveDateTime(ndt);
953        let edt = ev.as_datetime().unwrap();
954        assert_eq!(edt.aware(), None);
955        assert_eq!(edt.into_naive(), ndt);
956        assert_eq!(edt.or_offset(offset), dt);
957    }
958
959    #[test]
960    fn rational_to_f64_normal() {
961        let r = URational::new(1, 2);
962        assert_eq!(r.numerator(), 1);
963        assert_eq!(r.denominator(), 2);
964        assert_eq!(r.to_f64(), Some(0.5));
965    }
966
967    #[test]
968    fn rational_to_f64_zero_denominator() {
969        let r = URational::new(1, 0);
970        assert_eq!(r.to_f64(), None);
971
972        let r = IRational::new(-1, 0);
973        assert_eq!(r.to_f64(), None);
974    }
975
976    #[test]
977    fn rational_default() {
978        let r = URational::default();
979        assert_eq!(r.numerator(), 0);
980        assert_eq!(r.denominator(), 0);
981    }
982
983    #[test]
984    fn irational_to_urational_positive() {
985        let i = IRational::new(3, 4);
986        let u: URational = i.try_into().unwrap();
987        assert_eq!(u, URational::new(3, 4));
988    }
989
990    #[test]
991    fn irational_to_urational_negative_numerator() {
992        let i = IRational::new(-3, 4);
993        let err = URational::try_from(i).unwrap_err();
994        assert!(matches!(err, crate::ConvertError::NegativeRational));
995    }
996
997    #[test]
998    fn irational_to_urational_negative_denominator() {
999        let i = IRational::new(3, -4);
1000        let err = URational::try_from(i).unwrap_err();
1001        assert!(matches!(err, crate::ConvertError::NegativeRational));
1002    }
1003
1004    #[test]
1005    fn entry_value_as_i64_f64() {
1006        assert_eq!(EntryValue::I64(-7).as_i64(), Some(-7));
1007        assert_eq!(EntryValue::F64(2.5).as_f64(), Some(2.5));
1008        assert_eq!(EntryValue::I32(7).as_i64(), None);
1009        assert_eq!(EntryValue::F32(2.5).as_f64(), None);
1010    }
1011
1012    #[test]
1013    fn entry_value_try_as_integer() {
1014        assert_eq!(EntryValue::U8(7).try_as_integer(), Some(7));
1015        assert_eq!(
1016            EntryValue::U32(0xffff_ffff).try_as_integer(),
1017            Some(0xffff_ffff_i64)
1018        );
1019        assert_eq!(EntryValue::I32(-7).try_as_integer(), Some(-7));
1020        assert_eq!(EntryValue::U64(u64::MAX).try_as_integer(), None);
1021        assert_eq!(EntryValue::Text("x".into()).try_as_integer(), None);
1022    }
1023
1024    #[test]
1025    fn entry_value_try_as_float() {
1026        assert_eq!(EntryValue::U8(7).try_as_float(), Some(7.0));
1027        assert_eq!(EntryValue::F32(1.5).try_as_float(), Some(1.5));
1028        assert_eq!(
1029            EntryValue::URational(URational::new(1, 2)).try_as_float(),
1030            Some(0.5)
1031        );
1032        assert_eq!(
1033            EntryValue::URational(URational::new(1, 0)).try_as_float(),
1034            None
1035        );
1036        assert_eq!(EntryValue::Text("x".into()).try_as_float(), None);
1037    }
1038
1039    #[test]
1040    fn entry_value_slice_accessors() {
1041        assert_eq!(
1042            EntryValue::U8Array(vec![1, 2]).as_u8_slice(),
1043            Some(&[1u8, 2][..])
1044        );
1045        assert_eq!(
1046            EntryValue::U16Array(vec![1, 2]).as_u16_slice(),
1047            Some(&[1u16, 2][..])
1048        );
1049        assert_eq!(
1050            EntryValue::U32Array(vec![1, 2]).as_u32_slice(),
1051            Some(&[1u32, 2][..])
1052        );
1053        assert_eq!(
1054            EntryValue::Undefined(vec![1, 2]).as_undefined(),
1055            Some(&[1u8, 2][..])
1056        );
1057        let r = URational::new(1, 2);
1058        assert_eq!(
1059            EntryValue::URationalArray(vec![r]).as_urational_slice(),
1060            Some(&[r][..])
1061        );
1062    }
1063}