use crate::cents::Cents;
use chrono::prelude::*;
pub struct Formatter<'a> {
thousands_delimiter: &'a str,
decimal_delimiter: &'a str,
currency: &'a str,
currency_position: CurrencyPosition,
datetime_format: &'a str,
date_format: &'a str,
}
enum CurrencyPosition {
Before,
After,
}
impl<'a> Formatter<'a> {
pub fn new(locale: &str, currency: &'a str) -> Self {
match locale {
"de" => Formatter {
thousands_delimiter: ".",
decimal_delimiter: ",",
currency,
currency_position: CurrencyPosition::After,
datetime_format: "%d.%m.%Y %H:%M:%S",
date_format: "%d.%m.%Y",
},
_ => Formatter {
thousands_delimiter: ",",
decimal_delimiter: ".",
currency,
currency_position: CurrencyPosition::Before,
datetime_format: "%Y-%m-%d %H:%M:%S",
date_format: "%Y-%m-%d",
},
}
}
fn format_number(&self, mut s: String) -> String {
let dec_ix = match s.find('.') {
Some(ix) => {
s.remove(ix);
s.insert_str(ix, self.decimal_delimiter);
ix
}
None => s.len(),
};
let mut offset = dec_ix % 3;
let mut blocks = dec_ix / 3;
if offset == 0 {
blocks -= 1;
offset = 3;
}
for i in 0..blocks {
let ix = offset + (blocks - i - 1) * 3;
s.insert_str(ix, self.thousands_delimiter);
}
s
}
pub fn f64(&self, n: f64) -> String {
let s = n.to_string();
self.format_number(s)
}
pub fn decimal(&self, n: Cents) -> String {
let s = n.to_string();
self.format_number(s)
}
pub fn currency(&self, n: Cents) -> String {
let mut s = self.decimal(n);
match self.currency_position {
CurrencyPosition::Before => {
s.insert(0, ' ');
s.insert_str(0, self.currency);
}
CurrencyPosition::After => {
s = s + " " + self.currency;
}
}
s
}
#[allow(unused)]
pub fn datetime(&self, dt: &DateTime<Utc>) -> String {
dt.format(self.datetime_format).to_string()
}
pub fn date(&self, dt: &DateTime<Utc>) -> String {
dt.format(self.date_format).to_string()
}
pub fn plus_minus(&self, n: f64) -> String {
let mut s = n.to_string();
match s.find('.') {
Some(ix) => match s.len() - ix - 1 {
0 => s += "00",
1 => s += "0",
_ => {}
},
None => s += ".00",
};
s = self.format_number(s);
match n {
n if n == 0f64 => String::from("±") + &s,
n if n > 0f64 => String::from("+") + &s,
_ => s,
}
}
}
#[cfg(test)]
mod test {
use super::Formatter;
use crate::cents::Cents;
#[test]
fn unchanged() {
let fmt = Formatter::new("de", "€");
assert_eq!(fmt.decimal(Cents::new(123, 0)), "123,00");
}
#[test]
fn decimal_delimiter() {
let fmt = Formatter::new("de", "€");
assert_eq!(fmt.decimal(Cents::new(1234, 2)), "12,34");
}
#[test]
fn thousands_delimiter() {
let fmt = Formatter::new("de", "€");
assert_eq!(fmt.decimal(Cents::new(12345, 2)), "123,45");
assert_eq!(fmt.decimal(Cents::new(123456, 2)), "1.234,56");
assert_eq!(fmt.decimal(Cents::new(1234567, 2)), "12.345,67");
assert_eq!(fmt.decimal(Cents::new(12345678, 2)), "123.456,78");
assert_eq!(fmt.decimal(Cents::new(123456789, 2)), "1.234.567,89");
assert_eq!(fmt.decimal(Cents::new(1234567890, 2)), "12.345.678,90");
assert_eq!(fmt.decimal(Cents::new(12345678900, 2)), "123.456.789,00");
}
#[test]
fn at_least_two_decimal_places() {
let fmt = Formatter::new("de", "€");
assert_eq!(fmt.currency(Cents::new(12, 0)), "12,00 €");
assert_eq!(fmt.currency(Cents::new(123, 1)), "12,30 €");
assert_eq!(fmt.currency(Cents::new(1234, 2)), "12,34 €");
assert_eq!(fmt.currency(Cents::new(12345, 3)), "12,345 €");
}
#[test]
fn currency_after() {
let fmt = Formatter::new("de", "€");
assert_eq!(fmt.currency(Cents::new(42, 0)), "42,00 €");
}
#[test]
fn currency_before() {
let fmt = Formatter::new("en", "€");
assert_eq!(fmt.currency(Cents::new(42, 0)), "€ 42.00");
}
#[test]
fn plus_minus() {
let fmt = Formatter::new("de", "€");
assert_eq!(fmt.plus_minus(0f64), "±0,00");
assert_eq!(fmt.plus_minus(1f64), "+1,00");
assert_eq!(fmt.plus_minus(-1f64), "-1,00");
assert_eq!(fmt.plus_minus(1.2), "+1,20");
assert_eq!(fmt.plus_minus(-1.2), "-1,20");
assert_eq!(fmt.plus_minus(1.23), "+1,23");
assert_eq!(fmt.plus_minus(-1.23), "-1,23");
assert_eq!(fmt.plus_minus(1.234), "+1,234");
assert_eq!(fmt.plus_minus(-1.234), "-1,234");
}
}