use chrono::{NaiveDate, NaiveDateTime};
use lazy_static::lazy_static;
use rust_decimal::Decimal;
use std::fmt;
use std::sync::Mutex;
lazy_static! {
static ref OUTPUT_DATE_FORMAT: &'static str = "%Y-%m-%d %H:%M:%S";
pub static ref INPUT_DATE_FORMAT: Mutex<String> = Mutex::new("%Y-%m-%d %H:%M:%S".to_string());
}
pub fn set_date_format(s: String) {
*INPUT_DATE_FORMAT.lock().unwrap() = s;
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct DecimalWrapper {
pub item: Decimal,
}
impl std::str::FromStr for DecimalWrapper {
type Err = rust_decimal::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Decimal::from_str(s)
.or_else(|_| Decimal::from_scientific(s))
.map(|v| DecimalWrapper { item: v })
}
}
impl fmt::Display for DecimalWrapper {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.item.to_string())
}
}
impl std::ops::Sub for DecimalWrapper {
type Output = Decimal;
fn sub(self, other: DecimalWrapper) -> Decimal {
self.item - other.item
}
}
impl std::ops::AddAssign for DecimalWrapper {
fn add_assign(&mut self, other: Self) {
*self = Self {
item: self.item + other.item,
}
}
}
impl std::ops::Add for DecimalWrapper {
type Output = DecimalWrapper;
fn add(self, other: Self) -> Self {
Self {
item: self.item + other.item,
}
}
}
impl std::ops::Div for DecimalWrapper {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
DecimalWrapper {
item: self.item / rhs.item,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct CustomDateObject(NaiveDateTime);
impl std::str::FromStr for CustomDateObject {
type Err = chrono::format::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let first_pass = NaiveDateTime::parse_from_str(s, &*INPUT_DATE_FORMAT.lock().unwrap());
let parsed_dt = if first_pass.is_err() {
NaiveDate::parse_from_str(s, &*INPUT_DATE_FORMAT.lock().unwrap())
.map(|v| v.and_hms(0, 0, 0))
} else {
first_pass
}?;
Ok(CustomDateObject(parsed_dt))
}
}
impl std::ops::Sub for CustomDateObject {
type Output = f64;
#[allow(clippy::suspicious_arithmetic_impl)]
fn sub(self, other: CustomDateObject) -> f64 {
let duration = self.0.signed_duration_since(other.0);
duration.num_seconds() as f64 / 86400.
}
}
impl fmt::Display for CustomDateObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.format(&OUTPUT_DATE_FORMAT))
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDate;
use proptest::prelude::*;
#[test]
fn test_date_subtraction() {
let day_recent = CustomDateObject(NaiveDate::from_ymd(2019, 1, 1).and_hms(0, 0, 0));
let day_previous = CustomDateObject(NaiveDate::from_ymd(2018, 12, 31).and_hms(0, 0, 0));
assert_eq!(day_recent - day_previous, 1.);
}
#[test]
fn test_scientific_notation() {
let scinot1: DecimalWrapper = "1e-4".parse().unwrap();
assert_eq!(scinot1.to_string(), "0.0001".to_string());
let scinot2: DecimalWrapper = "1.3E4".parse().unwrap();
assert_eq!(scinot2.to_string(), "13000".to_string());
}
#[test]
fn test_parse_multiple_dates() {
use std::panic;
let result = panic::catch_unwind(|| {
let parsed_date: Result<CustomDateObject, chrono::format::ParseError> =
"August 24, 2018".parse();
assert!(parsed_date.is_err());
set_date_format("%B %d, %Y".to_string());
let re_parse: Result<CustomDateObject, chrono::format::ParseError> =
"August 24, 2018".parse();
assert!(re_parse.is_ok());
set_date_format("%m-%d-%Y".to_string());
let mdy_parse: Result<CustomDateObject, chrono::format::ParseError> =
"12-23-2019".parse();
assert!(mdy_parse.is_ok());
});
set_date_format(OUTPUT_DATE_FORMAT.to_string());
if let Err(err) = result {
panic::resume_unwind(err);
}
}
proptest! {
#[test]
fn test_date_parsing(year in 1900..=2020i32, month in 1..=12u32, day in 1..=28u32, hour in 0..=23u32, minute in 0..=59u32, second in 0..=59u32) {
let dt = CustomDateObject(NaiveDate::from_ymd(year, month, day).and_hms(hour, minute, second));
let _ex = set_date_format("%Y-%m-%d %H:%M:%S".to_string());
let deser_ser : CustomDateObject = dt.to_string().parse().unwrap();
assert_eq!(dt, deser_ser);
}
#[test]
fn test_parses_decimal_normal(num in -1000000..=1000000i64, scale in 0..=16u32) {
let dec = Decimal::new(num, scale);
let decimal_wrapper = DecimalWrapper { item: dec };
let deser_ser : DecimalWrapper = decimal_wrapper.to_string().parse().unwrap();
assert_eq!(deser_ser.item, dec);
}
}
}