1use crate::currency::FormattableCurrency;
2use crate::{Money, Round};
3use std::cmp::Ordering;
4
5pub struct Formatter;
7
8impl<'a> Formatter {
9 pub fn money<T: FormattableCurrency>(money: &Money<'a, T>, params: Params) -> String {
11 let mut decimal = *money.amount();
12
13 if let Some(x) = params.rounding {
15 decimal = *money.round(x, Round::HalfEven).amount();
16 }
17
18 let amount = Formatter::amount(&format!("{}", decimal), ¶ms);
20
21 let mut result = String::new();
23 for position in params.positions.iter() {
24 match position {
25 Position::Space => result.push(' '),
26 Position::Amount => result.push_str(&amount),
27 Position::Code => result.push_str(params.code.unwrap_or("")),
28 Position::Symbol => result.push_str(params.symbol.unwrap_or("")),
29 Position::Sign => result.push_str(if money.is_negative() { "-" } else { "" }),
30 }
31 }
32 result
33 }
34
35 fn amount(raw_amount: &str, params: &Params) -> String {
37 let amount_split: Vec<&str> = raw_amount.split('.').collect();
39 let mut amount_digits = amount_split[0].to_string();
40
41 amount_digits.retain(|c| c != '-');
43 amount_digits = Formatter::digits(
44 &amount_digits,
45 params.digit_separator,
46 ¶ms.separator_pattern,
47 );
48 let mut result = amount_digits;
49
50 match amount_split.len().cmp(&2) {
52 Ordering::Equal => {
53 result.push(params.exponent_separator);
55 result += amount_split[1];
56 }
57 Ordering::Less => {
58 }
60 Ordering::Greater => panic!("More than 1 exponent separators when parsing Decimal"),
61 }
62
63 result
64 }
65
66 fn digits(raw_digits: &str, separator: char, pattern: &[usize]) -> String {
68 let mut digits = raw_digits.to_string();
69
70 let mut current_position: usize = 0;
71 for position in pattern.iter() {
72 current_position += position;
73 if digits.len() > current_position {
74 digits.insert(digits.len() - current_position, separator);
75 current_position += 1;
76 }
77 }
78 digits
79 }
80}
81
82#[derive(Debug, Clone)]
84pub enum Position {
85 Space,
86 Amount,
87 Code,
88 Symbol,
89 Sign,
90}
91
92#[derive(Debug, Clone)]
94pub struct Params {
95 pub digit_separator: char,
97 pub exponent_separator: char,
99 pub separator_pattern: Vec<usize>,
101 pub positions: Vec<Position>,
103 pub rounding: Option<u32>,
105 pub symbol: Option<&'static str>,
107 pub code: Option<&'static str>,
109}
110
111impl Default for Params {
112 fn default() -> Params {
114 Params {
115 digit_separator: ',',
116 exponent_separator: '.',
117 separator_pattern: vec![3, 3, 3],
118 positions: vec![Position::Sign, Position::Symbol, Position::Amount],
119 rounding: None,
120 symbol: None,
121 code: None,
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::define_currency_set;
130
131 define_currency_set!(
132 test {
133 USD: {
134 code: "USD",
135 exponent: 2,
136 locale: EnUs,
137 minor_units: 100,
138 name: "USD",
139 symbol: "$",
140 symbol_first: true,
141 }
142 }
143 );
144
145 #[test]
146 fn format_position() {
147 let _usd = test::find("USD"); let money = Money::from_major(-1000, test::USD);
150
151 let params = Params {
153 symbol: Some("$"),
154 code: Some("USD"),
155 positions: vec![
156 Position::Sign,
157 Position::Space,
158 Position::Symbol,
159 Position::Amount,
160 Position::Space,
161 Position::Code,
162 ],
163 ..Default::default()
164 };
165 assert_eq!("- $1,000 USD", Formatter::money(&money, params));
166
167 let params = Params {
168 symbol: Some("$"),
169 code: Some("USD"),
170 positions: vec![
171 Position::Code,
172 Position::Space,
173 Position::Amount,
174 Position::Symbol,
175 Position::Space,
176 Position::Sign,
177 ],
178 ..Default::default()
179 };
180 assert_eq!("USD 1,000$ -", Formatter::money(&money, params));
181
182 let params = Params {
184 positions: vec![Position::Amount],
185 ..Default::default()
186 };
187 assert_eq!("1,000", Formatter::money(&money, params));
188
189 let params = Params {
190 symbol: Some("$"),
191 positions: vec![Position::Symbol],
192 ..Default::default()
193 };
194 assert_eq!("$", Formatter::money(&money, params));
195
196 let params = Params {
198 positions: vec![Position::Amount, Position::Symbol],
199 ..Default::default()
200 };
201 assert_eq!("1,000", Formatter::money(&money, params));
202 }
203
204 #[test]
205 fn format_digit_separators_with_custom_separators() {
206 let params = Params {
207 digit_separator: '/',
208 ..Default::default()
209 };
210
211 let money = Money::from_major(1_000_000, test::USD);
213 assert_eq!("1/000/000", Formatter::money(&money, params.clone()));
214
215 let money = Money::from_major(1_000, test::USD);
217 assert_eq!("1/000", Formatter::money(&money, params.clone()));
218
219 let money = Money::from_major(0, test::USD);
221 assert_eq!("0", Formatter::money(&money, params.clone()));
222 }
223
224 #[test]
225 fn format_digit_separators_with_custom_sequences() {
226 let params = Params {
227 separator_pattern: vec![3, 2, 2],
228 ..Default::default()
229 };
230
231 let money = Money::from_major(1_00_00_000, test::USD);
232 assert_eq!("1,00,00,000", Formatter::money(&money, params.clone()));
233
234 let money = Money::from_major(1_00_000, test::USD);
235 assert_eq!("1,00,000", Formatter::money(&money, params.clone()));
236
237 let money = Money::from_major(1_000, test::USD);
238 assert_eq!("1,000", Formatter::money(&money, params.clone()));
239
240 let params = Params {
242 separator_pattern: vec![0, 2],
243 ..Default::default()
244 };
245
246 let money = Money::from_major(100, test::USD);
247 assert_eq!("1,00,", Formatter::money(&money, params.clone()));
248
249 let money = Money::from_major(0, test::USD);
250 assert_eq!("0,", Formatter::money(&money, params.clone()));
251 }
252
253 #[test]
256 fn format_rounding() {
257 let money = Money::from_minor(1000, test::USD) / 3;
258
259 let params = Params {
261 rounding: Some(0),
262 ..Default::default()
263 };
264 assert_eq!("3", Formatter::money(&money, params));
265
266 let params = Params {
268 rounding: Some(2),
269 ..Default::default()
270 };
271 assert_eq!("3.33", Formatter::money(&money, params));
272
273 let params = Params {
275 ..Default::default()
276 };
277 assert_eq!(
278 "3.3333333333333333333333333333",
279 Formatter::money(&money, params)
280 );
281 }
282}