1use std::fmt::Display;
2use std::str::FromStr;
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5pub enum NumberStyle {
6 Decimal,
7 Currency {
8 code: CurrencyCode,
9 style: CurrencyDisplayStyle,
10 sign: CurrencySignMode,
11 },
12 Percent,
13 Unit {
14 identifier: UnitIdentifier,
15 style: UnitDisplayStyle,
16 },
17}
18
19impl Default for NumberStyle {
20 fn default() -> Self {
21 Self::Decimal
22 }
23}
24
25impl NumberStyle {
26 pub fn is_currency(&self) -> bool {
27 match self {
28 NumberStyle::Currency { .. } => true,
29 _ => false,
30 }
31 }
32
33 pub fn is_unit(&self) -> bool {
34 match self {
35 NumberStyle::Unit { .. } => true,
36 _ => false,
37 }
38 }
39
40 pub fn is_decimal(&self) -> bool {
41 match self {
42 NumberStyle::Decimal => true,
43 _ => false,
44 }
45 }
46
47 pub fn is_percent(&self) -> bool {
48 match self {
49 NumberStyle::Percent => true,
50 _ => false,
51 }
52 }
53
54 pub fn set_currency_display_style(&mut self, new_style: CurrencyDisplayStyle) -> bool {
55 if let NumberStyle::Currency { style, .. } = self {
56 *style = new_style;
57 true
58 } else {
59 false
60 }
61 }
62
63 pub fn set_currency_sign_mode(&mut self, new_sign: CurrencySignMode) -> bool {
64 if let NumberStyle::Currency { sign, .. } = self {
65 *sign = new_sign;
66 true
67 } else {
68 false
69 }
70 }
71
72 pub fn set_unit_display_style(&mut self, new_style: UnitDisplayStyle) -> bool {
73 if let NumberStyle::Unit { style, .. } = self {
74 *style = new_style;
75 true
76 } else {
77 false
78 }
79 }
80}
81
82#[derive(Debug, thiserror::Error)]
83#[error("Invalid currency code: '{0}")]
84pub struct InvalidCurrencyCode(String);
85
86macro_rules! create_currency_code_enum {
87 ($($code:ident),*) => {
88 #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
89 pub enum CurrencyCode {
90 $($code),*
91 }
92
93 impl FromStr for CurrencyCode {
94 type Err = InvalidCurrencyCode;
95
96 fn from_str(s: &str) -> Result<Self, Self::Err> {
97 match s {
98 $(stringify!($code) => Ok(CurrencyCode::$code),)*
99 _ => Err(InvalidCurrencyCode(s.to_string())),
100 }
101 }
102 }
103
104 impl Display for CurrencyCode {
105 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
106 let s = match self {
107 $(CurrencyCode::$code => stringify!($code)),*
108 };
109 f.write_str(s)
110 }
111 }
112 };
113}
114
115create_currency_code_enum!(
116 AED, AFN, ALL, AMD, ANG, AOA, ARS, AUD, AWG, AZN, BAM, BBD, BDT, BGN, BHD, BIF, BMD, BND, BOB,
117 BOV, BRL, BSD, BTN, BWP, BYN, BZD, CAD, CDF, CHE, CHF, CHW, CLF, CLP, CNY, COP, COU, CRC, CUP,
118 CVE, CZK, DJF, DKK, DOP, DZD, EGP, ERN, ETB, EUR, FJD, FKP, GBP, GEL, GHS, GIP, GMD, GNF, GTQ,
119 GYD, HKD, HNL, HTG, HUF, IDR, ILS, INR, IQD, IRR, ISK, JMD, JOD, JPY, KES, KGS, KHR, KMF, KPW,
120 KRW, KWD, KYD, KZT, LAK, LBP, LKR, LRD, LSL, LYD, MAD, MDL, MGA, MKD, MMK, MNT, MOP, MRU, MUR,
121 MVR, MWK, MXN, MXV, MYR, MZN, NAD, NGN, NIO, NOK, NPR, NZD, OMR, PAB, PEN, PGK, PHP, PKR, PLN,
122 PYG, QAR, RON, RSD, RUB, RWF, SAR, SBD, SCR, SDG, SEK, SGD, SHP, SLE, SOS, SRD, SSP, STN, SVC,
123 SYP, SZL, THB, TJS, TMT, TND, TOP, TRY, TTD, TWD, TZS, UAH, UGX, USD, USN, UYI, UYU, UYW, UZS,
124 VED, VES, VND, VUV, WST, XAF, XAG, XAU, XBA, XBB, XBC, XBD, XCD, XDR, XOF, XPD, XPF, XPT, XSU,
125 XTS, XUA, XXX, YER, ZAR, ZMW, ZWG
126);
127
128#[derive(Debug, Clone, PartialEq, Eq, Hash)]
129pub enum CurrencyDisplayStyle {
130 Code,
131 Symbol,
132 NarrowSymbol,
133 Name,
134}
135
136#[derive(Debug, thiserror::Error)]
137#[error("Invalid currency display style: '{0}'")]
138pub struct InvalidCurrencyDisplayStyleError(String);
139
140impl FromStr for CurrencyDisplayStyle {
141 type Err = InvalidCurrencyDisplayStyleError;
142
143 fn from_str(s: &str) -> Result<Self, Self::Err> {
144 match s.to_lowercase().as_str() {
145 "code" => Ok(CurrencyDisplayStyle::Code),
146 "symbol" => Ok(CurrencyDisplayStyle::Symbol),
147 "narrowsymbol" => Ok(CurrencyDisplayStyle::NarrowSymbol),
148 "name" => Ok(CurrencyDisplayStyle::Name),
149 _ => Err(InvalidCurrencyDisplayStyleError(s.to_string())),
150 }
151 }
152}
153
154impl Default for CurrencyDisplayStyle {
155 fn default() -> Self {
156 Self::Symbol
157 }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Hash)]
161pub enum CurrencySignMode {
162 Standard,
163 Accounting,
164}
165
166#[derive(Debug, thiserror::Error)]
167#[error("Invalid currency sign mode: '{0}'")]
168pub struct InvalidCurrencySignModeError(String);
169
170impl FromStr for CurrencySignMode {
171 type Err = InvalidCurrencySignModeError;
172
173 fn from_str(s: &str) -> Result<Self, Self::Err> {
174 match s.to_lowercase().as_str() {
175 "standard" => Ok(CurrencySignMode::Standard),
176 "accounting" => Ok(CurrencySignMode::Accounting),
177 _ => Err(InvalidCurrencySignModeError(s.to_string())),
178 }
179 }
180}
181
182impl Default for CurrencySignMode {
183 fn default() -> Self {
184 Self::Standard
185 }
186}
187
188#[derive(Debug, Clone, thiserror::Error)]
189#[error("Invalid currency sign mode: '{0}'")]
190pub struct InvalidUnitIdentifierError(String);
191
192macro_rules! generate_unit_identifier {
193 ($($raw_id:ident),*) => {
194 ::paste::paste! {
195 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
196 pub enum UnitIdentifier {
197 Derived(Box<UnitIdentifier>, Box<UnitIdentifier>),
198 $( [< $raw_id:camel >] ),*
199 }
200
201 impl UnitIdentifier {
202 pub fn per(&self, denominator: UnitIdentifier) -> UnitIdentifier {
203 Self::Derived(Box::new(self.clone()), Box::new(denominator))
204 }
205 }
206
207 impl FromStr for UnitIdentifier {
208 type Err = InvalidUnitIdentifierError;
209
210 fn from_str(s: &str) -> Result<Self, Self::Err> {
211 match s {
212 $(stringify!($raw_id) => Ok(UnitIdentifier::[< $raw_id:camel >]),)*
213 _ => if let Some(index) = s.find("-per-") {
214 let (left_str, right_str) = s.split_at(index);
215 let right_str = &right_str[5..]; let left = UnitIdentifier::from_str(left_str)?;
218 let right = UnitIdentifier::from_str(right_str)?;
219
220 Ok(UnitIdentifier::Derived(Box::new(left), Box::new(right)))
221 } else {
222 Err(InvalidUnitIdentifierError(s.to_string()))
223 }
224 }
225 }
226 }
227
228 impl Display for UnitIdentifier {
229 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
230 match self {
231 UnitIdentifier::Derived(n, d) => write!(f, "{}-per-{}", n, d),
232 $(UnitIdentifier::[< $raw_id:camel >] => f.write_str(stringify!($raw_id))),*
233 }
234 }
235 }
236 }
237 };
238}
239
240generate_unit_identifier!(
242 acre,
243 bit,
244 byte,
245 celsius,
246 centimeter,
247 day,
248 degree,
249 fahrenheit,
250 foot,
251 gallon,
252 gigabit,
253 gigabyte,
254 gram,
255 hectare,
256 hour,
257 inch,
258 kilobit,
259 kilobyte,
260 kilogram,
261 kilometer,
262 liter,
263 megabit,
264 megabyte,
265 meter,
266 microsecond,
267 mile,
268 milliliter,
269 millimeter,
270 millisecond,
271 minute,
272 month,
273 nanosecond,
274 ounce,
275 percent,
276 petabyte,
277 pound,
278 second,
279 stone,
280 terabit,
281 terabyte,
282 week,
283 yard,
284 year
285);
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
288pub enum UnitDisplayStyle {
289 Short,
290 Narrow,
291 Long,
292}
293
294impl Default for UnitDisplayStyle {
295 fn default() -> Self {
296 Self::Short
297 }
298}
299
300#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
301pub enum GroupingStyle {
302 Always,
303 Auto,
304 Min2,
305 Off,
306}
307
308impl Default for GroupingStyle {
309 fn default() -> Self {
310 Self::Auto
311 }
312}
313
314#[derive(Debug, thiserror::Error)]
315#[error("Invalid unit display style: '{0}")]
316pub struct InvalidUnitDisplayStyleError(String);
317
318impl FromStr for UnitDisplayStyle {
319 type Err = InvalidUnitDisplayStyleError;
320
321 fn from_str(s: &str) -> Result<Self, Self::Err> {
322 match s.to_lowercase().as_str() {
323 "short" => Ok(UnitDisplayStyle::Short),
324 "narrow" => Ok(UnitDisplayStyle::Narrow),
325 "long" => Ok(UnitDisplayStyle::Long),
326 _ => Err(InvalidUnitDisplayStyleError(s.to_string())),
327 }
328 }
329}
330
331#[derive(Debug, thiserror::Error)]
332#[error("Invalid grouping style: '{0}")]
333pub struct InvalidGroupingStyleError(String);
334
335impl FromStr for GroupingStyle {
336 type Err = InvalidGroupingStyleError;
337
338 fn from_str(s: &str) -> Result<Self, Self::Err> {
339 match s {
340 "auto" | "true" => Ok(Self::Auto),
341 "off" | "false" => Ok(Self::Off),
342 "always" => Ok(Self::Always),
343 "min2" => Ok(Self::Min2),
344 _ => Err(InvalidGroupingStyleError(s.to_string())),
345 }
346 }
347}
348
349#[derive(Debug, Clone, PartialEq, Eq, Hash)]
350pub struct NumberFormat {
351 pub style: NumberStyle,
352
353 pub use_grouping: GroupingStyle,
354
355 pub minimum_integer_digits: Option<usize>,
356 pub minimum_fraction_digits: Option<usize>,
357 pub maximum_fraction_digits: Option<usize>,
358 pub minimum_significant_digits: Option<usize>,
359 pub maximum_significant_digits: Option<usize>,
360}
361
362impl NumberFormat {
363 pub fn currency(code: CurrencyCode) -> Self {
364 Self {
365 style: NumberStyle::Currency {
366 code,
367 style: CurrencyDisplayStyle::default(),
368 sign: CurrencySignMode::default(),
369 },
370 ..Default::default()
371 }
372 }
373
374 pub fn unit(unit_id: UnitIdentifier) -> Self {
375 Self {
376 style: NumberStyle::Unit {
377 identifier: unit_id,
378 style: UnitDisplayStyle::default(),
379 },
380 ..Default::default()
381 }
382 }
383
384 pub fn percent() -> Self {
385 Self {
386 style: NumberStyle::Percent,
387 ..Default::default()
388 }
389 }
390}
391
392impl Default for NumberFormat {
393 fn default() -> Self {
394 Self {
395 style: NumberStyle::Decimal,
396 use_grouping: GroupingStyle::Auto,
397 minimum_integer_digits: None,
398 minimum_fraction_digits: None,
399 maximum_fraction_digits: None,
400 minimum_significant_digits: None,
401 maximum_significant_digits: None,
402 }
403 }
404}
405
406#[cfg(test)]
407mod test {
408 use crate::number::format::UnitIdentifier;
409
410 #[test]
411 fn test_unit_from_str() {
412 assert_eq!(UnitIdentifier::Meter, "meter".parse().unwrap());
413 assert_eq!(
414 UnitIdentifier::Meter.per(UnitIdentifier::Second),
415 "meter-per-second".parse().unwrap()
416 );
417 }
418
419 #[test]
420 fn test_unit_display() {
421 assert_eq!(
422 "kilometer-per-hour",
423 format!("{}", UnitIdentifier::Kilometer.per(UnitIdentifier::Hour))
424 );
425 }
426}