icu_decimal/
format.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Lower-level types for decimal formatting.
6
7use core::fmt::Write;
8
9use crate::grouper;
10use crate::options::*;
11use crate::parts;
12use crate::provider::*;
13use fixed_decimal::Decimal;
14use fixed_decimal::Sign;
15use writeable::Part;
16use writeable::PartsWrite;
17use writeable::Writeable;
18
19/// An intermediate structure returned by [`DecimalFormatter`](crate::DecimalFormatter).
20/// Use [`Writeable`][Writeable] to render the formatted decimal to a string or buffer.
21#[derive(Debug, PartialEq, Clone)]
22pub struct FormattedDecimal<'l> {
23    pub(crate) value: &'l Decimal,
24    pub(crate) options: &'l DecimalFormatterOptions,
25    pub(crate) symbols: &'l DecimalSymbols<'l>,
26    pub(crate) digits: &'l [char; 10],
27}
28
29impl FormattedDecimal<'_> {
30    /// Returns the affixes needed for the current sign, as (prefix, suffix)
31    fn get_affixes(&self) -> Option<(Part, (&str, &str))> {
32        match self.value.sign() {
33            Sign::None => None,
34            Sign::Negative => Some((parts::MINUS_SIGN, self.symbols.minus_sign_affixes())),
35            Sign::Positive => Some((parts::PLUS_SIGN, self.symbols.plus_sign_affixes())),
36        }
37    }
38}
39
40impl Writeable for FormattedDecimal<'_> {
41    fn write_to_parts<W>(&self, w: &mut W) -> core::result::Result<(), core::fmt::Error>
42    where
43        W: writeable::PartsWrite + ?Sized,
44    {
45        let affixes = self.get_affixes();
46        if let Some((part, affixes)) = affixes {
47            w.with_part(part, |w| w.write_str(affixes.0))?;
48        }
49        let range = self.value.absolute.magnitude_range();
50        let upper_magnitude = *range.end();
51        let mut range = range.rev();
52        let mut has_fraction = false;
53        w.with_part(parts::INTEGER, |w| {
54            loop {
55                let m = match range.next() {
56                    Some(m) if m < 0 => {
57                        has_fraction = true;
58                        break Ok(());
59                    }
60                    Some(m) => m,
61                    None => {
62                        break Ok(());
63                    }
64                };
65                #[allow(clippy::indexing_slicing)] // digit_at in 0..=9
66                w.write_char(self.digits[self.value.digit_at(m) as usize])?;
67                if grouper::check(
68                    upper_magnitude,
69                    m,
70                    self.options.grouping_strategy.unwrap_or_default(),
71                    self.symbols.grouping_sizes,
72                ) {
73                    w.with_part(parts::GROUP, |w| {
74                        w.write_str(self.symbols.grouping_separator())
75                    })?;
76                }
77            }
78        })?;
79        if has_fraction {
80            w.with_part(parts::DECIMAL, |w| {
81                w.write_str(self.symbols.decimal_separator())
82            })?;
83            w.with_part(parts::FRACTION, |w| {
84                let mut m = -1; // read in the previous loop
85                loop {
86                    #[allow(clippy::indexing_slicing)] // digit_at in 0..=9
87                    w.write_char(self.digits[self.value.digit_at(m) as usize])?;
88                    m = match range.next() {
89                        Some(m) => m,
90                        None => {
91                            break Ok(());
92                        }
93                    };
94                }
95            })?;
96        }
97        if let Some((part, affixes)) = affixes {
98            w.with_part(part, |w| w.write_str(affixes.1))?;
99        }
100        Ok(())
101    }
102}
103
104writeable::impl_display_with_writeable!(FormattedDecimal<'_>);
105
106#[cfg(test)]
107mod tests {
108    use icu_locale_core::locale;
109    use writeable::assert_writeable_eq;
110
111    use crate::DecimalFormatter;
112
113    #[test]
114    pub fn test_es_mx() {
115        let locale = locale!("es-MX").into();
116        let fmt = DecimalFormatter::try_new(locale, Default::default()).unwrap();
117        let fd = "12345.67".parse().unwrap();
118        assert_writeable_eq!(fmt.format(&fd), "12,345.67");
119    }
120}