1use super::i_culture_data_provider::find_culture_data;
14use super::CultureInfo;
15use std::cell::Cell;
16use std::rc::Rc;
17
18crate::ferro_thread_local! {
19 static INVARIANT: Rc<NumberFormatInfo> = Rc::new(NumberFormatInfo::new());
20}
21
22#[derive(Clone, Debug)]
28pub struct NumberFormatInfo {
29 currency_decimal_digits: Cell<i32>,
30 currency_decimal_separator: String,
31 currency_group_separator: String,
32 currency_group_sizes: Vec<i32>,
33 currency_negative_pattern: Cell<i32>,
34 currency_positive_pattern: Cell<i32>,
35 currency_symbol: String,
36 nan_symbol: String,
37 negative_infinity_symbol: String,
38 negative_sign: String,
39 number_decimal_digits: Cell<i32>,
40 number_decimal_separator: String,
41 number_group_separator: String,
42 number_group_sizes: Vec<i32>,
43 number_negative_pattern: Cell<i32>,
44 percent_decimal_digits: Cell<i32>,
45 percent_decimal_separator: String,
46 percent_group_separator: String,
47 percent_group_sizes: Vec<i32>,
48 percent_negative_pattern: Cell<i32>,
49 percent_positive_pattern: Cell<i32>,
50 percent_symbol: String,
51 per_mille_symbol: String,
52 positive_infinity_symbol: String,
53 positive_sign: String,
54}
55
56impl PartialEq for NumberFormatInfo {
57 #[inline]
58 fn eq(&self, other: &Self) -> bool {
59 std::ptr::eq(self, other)
60 }
61}
62
63impl Default for NumberFormatInfo {
64 #[inline]
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70macro_rules! text_properties {
71 ($($(#[$doc:meta])* $name:ident, $set:ident, $with:ident;)*) => {
72 impl NumberFormatInfo {
73 $(
74 $(#[$doc])*
75 #[inline]
76 pub fn $name(&self) -> &str {
77 &self.$name
78 }
79
80 $(#[$doc])*
81 #[inline]
82 pub fn $set(&mut self, value: impl Into<String>) {
83 self.$name = value.into();
84 }
85
86 $(#[$doc])*
87 #[inline]
88 pub fn $with(mut self, value: impl Into<String>) -> Self {
89 self.$name = value.into();
90 self
91 }
92 )*
93 }
94 };
95}
96
97macro_rules! number_properties {
98 ($($(#[$doc:meta])* $name:ident, $set:ident, $with:ident, $max:literal, $label:literal;)*) => {
99 impl NumberFormatInfo {
100 $(
101 $(#[$doc])*
102 #[inline]
103 pub fn $name(&self) -> i32 {
104 self.$name.get()
105 }
106
107 $(#[$doc])*
108 pub fn $set(&self, value: i32) {
114 assert!(
115 (0..=$max).contains(&value),
116 concat!($label, " must be between 0 and ", stringify!($max), ", inclusive.")
117 );
118 self.$name.set(value);
119 }
120
121 $(#[$doc])*
122 #[inline]
125 pub fn $with(self, value: i32) -> Self {
126 self.$set(value);
127 self
128 }
129 )*
130 }
131 };
132}
133
134macro_rules! group_properties {
135 ($($(#[$doc:meta])* $name:ident, $set:ident, $with:ident;)*) => {
136 impl NumberFormatInfo {
137 $(
138 $(#[$doc])*
139 #[inline]
140 pub fn $name(&self) -> &[i32] {
141 &self.$name
142 }
143
144 $(#[$doc])*
145 pub fn $set(&mut self, value: &[i32]) {
150 Self::check_group_sizes(value);
151 self.$name = value.to_vec();
152 }
153
154 $(#[$doc])*
155 #[inline]
158 pub fn $with(mut self, value: &[i32]) -> Self {
159 self.$set(value);
160 self
161 }
162 )*
163 }
164 };
165}
166
167text_properties! {
168 currency_decimal_separator, set_currency_decimal_separator, with_currency_decimal_separator;
170 currency_group_separator, set_currency_group_separator, with_currency_group_separator;
172 currency_symbol, set_currency_symbol, with_currency_symbol;
174 nan_symbol, set_nan_symbol, with_nan_symbol;
176 negative_infinity_symbol, set_negative_infinity_symbol, with_negative_infinity_symbol;
178 negative_sign, set_negative_sign, with_negative_sign;
180 number_decimal_separator, set_number_decimal_separator, with_number_decimal_separator;
182 number_group_separator, set_number_group_separator, with_number_group_separator;
184 percent_decimal_separator, set_percent_decimal_separator, with_percent_decimal_separator;
186 percent_group_separator, set_percent_group_separator, with_percent_group_separator;
188 percent_symbol, set_percent_symbol, with_percent_symbol;
190 per_mille_symbol, set_per_mille_symbol, with_per_mille_symbol;
192 positive_infinity_symbol, set_positive_infinity_symbol, with_positive_infinity_symbol;
194 positive_sign, set_positive_sign, with_positive_sign;
196}
197
198number_properties! {
199 currency_decimal_digits, set_currency_decimal_digits, with_currency_decimal_digits, 99, "CurrencyDecimalDigits";
201 currency_negative_pattern, set_currency_negative_pattern, with_currency_negative_pattern, 16,
203 "CurrencyNegativePattern";
204 currency_positive_pattern, set_currency_positive_pattern, with_currency_positive_pattern, 3,
207 "CurrencyPositivePattern";
208 number_decimal_digits, set_number_decimal_digits, with_number_decimal_digits, 99, "NumberDecimalDigits";
210 number_negative_pattern, set_number_negative_pattern, with_number_negative_pattern, 4, "NumberNegativePattern";
213 percent_decimal_digits, set_percent_decimal_digits, with_percent_decimal_digits, 99, "PercentDecimalDigits";
215 percent_negative_pattern, set_percent_negative_pattern, with_percent_negative_pattern, 11,
217 "PercentNegativePattern";
218 percent_positive_pattern, set_percent_positive_pattern, with_percent_positive_pattern, 3,
221 "PercentPositivePattern";
222}
223
224group_properties! {
225 currency_group_sizes, set_currency_group_sizes, with_currency_group_sizes;
228 number_group_sizes, set_number_group_sizes, with_number_group_sizes;
231 percent_group_sizes, set_percent_group_sizes, with_percent_group_sizes;
234}
235
236impl NumberFormatInfo {
237 pub fn new() -> NumberFormatInfo {
240 NumberFormatInfo {
241 currency_decimal_digits: Cell::new(2),
242 currency_decimal_separator: ".".to_string(),
243 currency_group_separator: ",".to_string(),
244 currency_group_sizes: vec![3],
245 currency_negative_pattern: Cell::new(0),
246 currency_positive_pattern: Cell::new(0),
247 currency_symbol: "\u{00A4}".to_string(),
248 nan_symbol: "NaN".to_string(),
249 negative_infinity_symbol: "-Infinity".to_string(),
250 negative_sign: "-".to_string(),
251 number_decimal_digits: Cell::new(2),
252 number_decimal_separator: ".".to_string(),
253 number_group_separator: ",".to_string(),
254 number_group_sizes: vec![3],
255 number_negative_pattern: Cell::new(1),
256 percent_decimal_digits: Cell::new(2),
257 percent_decimal_separator: ".".to_string(),
258 percent_group_separator: ",".to_string(),
259 percent_group_sizes: vec![3],
260 percent_negative_pattern: Cell::new(0),
261 percent_positive_pattern: Cell::new(0),
262 percent_symbol: "%".to_string(),
263 per_mille_symbol: "\u{2030}".to_string(),
264 positive_infinity_symbol: "Infinity".to_string(),
265 positive_sign: "+".to_string(),
266 }
267 }
268
269 pub(crate) fn for_culture(culture: &CultureInfo) -> Rc<NumberFormatInfo> {
273 find_culture_data(culture, |provider, name| provider.get_number_format(name))
274 .unwrap_or_else(Self::invariant_info)
275 }
276
277 pub fn invariant_info() -> Rc<NumberFormatInfo> {
279 INVARIANT.with(Clone::clone)
280 }
281
282 pub fn current_info() -> Rc<NumberFormatInfo> {
284 Self::for_culture(&CultureInfo::current_culture())
285 }
286
287 pub fn get_instance(provider: Option<&Rc<NumberFormatInfo>>) -> Rc<NumberFormatInfo> {
290 match provider {
291 Some(info) => info.clone(),
292 None => Self::current_info(),
293 }
294 }
295
296 fn check_group_sizes(sizes: &[i32]) {
297 for (index, size) in sizes.iter().enumerate() {
298 let valid = (1..=9).contains(size) || (*size == 0 && index + 1 == sizes.len());
299 assert!(
300 valid,
301 "Every element in the value array should be between one and nine, except for the last element, which can be zero."
302 );
303 }
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310 use crate::utilities::TestCultureDataProvider;
311 use crate::FerroLocator;
312
313 #[test]
314 fn a_new_instance_has_the_invariant_values() {
315 let info = NumberFormatInfo::new();
316 assert_eq!(info.currency_decimal_digits(), 2);
317 assert_eq!(info.currency_decimal_separator(), ".");
318 assert_eq!(info.currency_group_separator(), ",");
319 assert_eq!(info.currency_group_sizes(), [3]);
320 assert_eq!(info.currency_negative_pattern(), 0);
321 assert_eq!(info.currency_positive_pattern(), 0);
322 assert_eq!(info.currency_symbol(), "\u{00A4}");
323 assert_eq!(info.nan_symbol(), "NaN");
324 assert_eq!(info.negative_infinity_symbol(), "-Infinity");
325 assert_eq!(info.negative_sign(), "-");
326 assert_eq!(info.number_decimal_digits(), 2);
327 assert_eq!(info.number_decimal_separator(), ".");
328 assert_eq!(info.number_group_separator(), ",");
329 assert_eq!(info.number_group_sizes(), [3]);
330 assert_eq!(info.number_negative_pattern(), 1);
331 assert_eq!(info.percent_decimal_digits(), 2);
332 assert_eq!(info.percent_decimal_separator(), ".");
333 assert_eq!(info.percent_group_separator(), ",");
334 assert_eq!(info.percent_group_sizes(), [3]);
335 assert_eq!(info.percent_negative_pattern(), 0);
336 assert_eq!(info.percent_positive_pattern(), 0);
337 assert_eq!(info.percent_symbol(), "%");
338 assert_eq!(info.per_mille_symbol(), "\u{2030}");
339 assert_eq!(info.positive_infinity_symbol(), "Infinity");
340 assert_eq!(info.positive_sign(), "+");
341 }
342
343 #[test]
344 fn properties_can_be_changed() {
345 let mut info = NumberFormatInfo::new().with_number_decimal_separator(";").with_number_group_sizes(&[3, 2, 0]);
346 info.set_currency_symbol("kr");
347 info.set_number_negative_pattern(0);
348 assert_eq!(info.number_decimal_separator(), ";");
349 assert_eq!(info.number_group_sizes(), [3, 2, 0]);
350 assert_eq!(info.currency_symbol(), "kr");
351 assert_eq!(info.number_negative_pattern(), 0);
352 }
353
354 #[test]
355 #[should_panic(expected = "NumberNegativePattern must be between 0 and 4, inclusive.")]
356 fn a_pattern_out_of_range_panics() {
357 NumberFormatInfo::new().set_number_negative_pattern(5);
358 }
359
360 #[test]
361 #[should_panic(expected = "between one and nine")]
362 fn a_group_size_out_of_range_panics() {
363 NumberFormatInfo::new().set_number_group_sizes(&[0, 3]);
364 }
365
366 #[test]
367 fn instances_compare_by_identity() {
368 let a = Rc::new(NumberFormatInfo::new());
369 let b = Rc::new(NumberFormatInfo::new());
370 assert!(a == a.clone());
371 assert!(a != b);
372 assert!(NumberFormatInfo::invariant_info() == NumberFormatInfo::invariant_info());
373 }
374
375 #[test]
376 fn cultures() {
377 let _scope = FerroLocator::enter_scope();
378
379 let invariant = CultureInfo::invariant_culture().number_format();
380 assert!(invariant == NumberFormatInfo::invariant_info());
381 assert_eq!(invariant.currency_symbol(), "\u{00A4}");
382
383 assert!(CultureInfo::get_culture_info("en-US").number_format() == invariant);
385
386 TestCultureDataProvider::register();
387
388 let english = CultureInfo::get_culture_info("en-US").number_format();
389 assert_eq!(english.currency_symbol(), "$");
390 assert_eq!(english.currency_negative_pattern(), 1);
391 assert_eq!(english.percent_positive_pattern(), 1);
392 assert_eq!(english.percent_negative_pattern(), 1);
393 assert!(english == CultureInfo::get_culture_info("EN-us").number_format());
395
396 assert!(CultureInfo::get_culture_info("en-AU").number_format() == CultureInfo::get_culture_info("en").number_format());
398 assert!(CultureInfo::get_culture_info("ja-JP").number_format() == invariant);
399 assert!(CultureInfo::invariant_culture().number_format() == invariant);
400
401 assert_eq!(NumberFormatInfo::current_info().currency_symbol(), "\u{00A4}");
403 let given = Rc::new(NumberFormatInfo::new());
404 assert!(NumberFormatInfo::get_instance(Some(&given)) == given);
405 assert!(NumberFormatInfo::get_instance(None) == NumberFormatInfo::current_info());
406 }
407}