From 31601a2dd69e13d211367f2b1441d5df9f60a2b3 Mon Sep 17 00:00:00 2001 From: Maddox Werts Date: Sat, 2 Aug 2025 15:27:02 -0400 Subject: [PATCH] Created Calculator --- project/src/backend/calculator.rs | 173 ++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 project/src/backend/calculator.rs diff --git a/project/src/backend/calculator.rs b/project/src/backend/calculator.rs new file mode 100644 index 0000000..2e2884a --- /dev/null +++ b/project/src/backend/calculator.rs @@ -0,0 +1,173 @@ +// Libraries +use chrono::{self, Datelike, Local, NaiveDate}; +use regex::Regex; +use std::io::{Error, ErrorKind, Result}; + +// Functions +pub fn extract_price(input: String) -> Option { + let re = Regex::new(r"\$?(\d+\.\d+)").unwrap(); + re.captures(&input).and_then(|caps| { + caps.get(1).and_then(|m| { + let num_str = m.as_str().replace(',', ""); + num_str.parse::().ok() + }) + }) +} +fn add_months(date: &NaiveDate, months: i32) -> NaiveDate { + // Getting all required dates for our addition + let mut year = date.year(); + let mut month = date.month() as i32 + months; + let day = date.day(); + + // Adjust year and month to correct values + if month > 12 { + year += (month - 1) / 12; + month = (month - 1) % 12 + 1; + } else if month < 1 { + year -= (12 - month) / 12 + 1; + month = 12 - ((12 - month) % 12); + } + + // Trying to build date on target year/month/day - if invalid (e.g. 31st Feb), fallback: + // We first try creating date with the original day + if let Some(new_date) = NaiveDate::from_ymd_opt(year, month as u32, day) { + new_date + } else { + // If invalid day, set day to last day of previous month by setting day = 0 on next month + // Since NaiveDate::from_ymd_opt(year, month + 1, 0) is last day of month + NaiveDate::from_ymd_opt(year, month as u32 + 1, 0).expect("Invalid date calculation") + } +} + +// Structures +pub struct Calculator {} +pub struct CalculationResult { + pub change_reg: f32, + pub change_abs: f32, + pub date_str: String, + pub date_num: i32, + pub reversed: bool, + pub invalid: bool, +} + +// Implementations +impl Calculator { + // Constructors + /// Creates a new Calculator + /// + /// # Examples + /// ```rs + /// let calculator = Calculator::new(); + /// ``` + pub fn new() -> Self { + Self {} + } + + // Functions + /// Calculates the Prorate with the given parameters + /// + /// # Arguments + /// + /// # Examples + pub fn start( + &self, + current_membership: String, + new_membership: String, + last_billing: String, + ) -> Result { + // Extracting the Prices from our Memberships + let current_membership = match extract_price(current_membership) { + Some(m) => m, + None => { + return Err(Error::new( + ErrorKind::InvalidData, + "No Price Found (Current).", + )) + } + }; + let new_membership = match extract_price(new_membership) { + Some(m) => m, + None => return Err(Error::new(ErrorKind::InvalidData, "No Price Found (New).")), + }; + + // Getting the Dates + let last_billing = match NaiveDate::parse_from_str(&last_billing, "%m/%d/%Y") { + Ok(date) => date, + Err(_) => return Err(Error::new(ErrorKind::InvalidData, "Date Format Incorrect.")), + }; + let current_date = Local::now().date_naive(); + + // Getting the amount of Months that have passed + let mut months_passed = (current_date.year() - last_billing.year()) * 12 + + (current_date.month() as i32 - last_billing.month() as i32); + + // Saftey Check + if current_date.day() < last_billing.day() { + months_passed -= 1; + } + + // Are the amount of months that have passed less than 0? + if months_passed < 0 { + months_passed = 0; + } + + // Getting the Cost Difference + let cost_difference = new_membership - current_membership; + + // Stop if there's no price difference + if cost_difference == 0.0 { + return Ok(CalculationResult { + change_reg: 0.0, + change_abs: 0.0, + date_str: String::new(), + date_num: 0, + reversed: false, + invalid: true, + }); + } + + // Getting the billing Start & End + let billing_start = add_months(&last_billing, months_passed); + let billing_end = add_months(&last_billing, months_passed + 1); + + // Getting the days left in our billing cycle + let days_in_cycle = (billing_end - billing_start).num_days() as f32; + + // Getting our daily rate in our plan + let current_daily_rate = current_membership / days_in_cycle; + + // Reversing the adjustment logic here + let adjustment_days = (-cost_difference / current_daily_rate * 10.0).round() / 10.0; + + // Getting the Billing Dates + let billing_date_normal = add_months(&last_billing, months_passed + 1); + let adjustment_days_int = adjustment_days.round() as i64; + let billing_date_adjusted = if adjustment_days_int >= 0 { + billing_date_normal.checked_add_signed(chrono::Duration::days(adjustment_days_int)) + } else { + billing_date_normal.checked_add_signed(chrono::Duration::days(-adjustment_days_int)) + } + .unwrap(); + + // Getting the Adjustment Days + let adjustment_days_f32 = adjustment_days.abs(); + + // Getting the Adjustment Days String + let adjusted_date_string = billing_date_adjusted.format("%m/%d/%Y").to_string(); + let date_number = billing_date_adjusted + .format("%d") + .to_string() + .parse::() + .unwrap_or(0); + + // Ok!! + Ok(CalculationResult { + change_abs: adjustment_days_f32, + change_reg: adjustment_days, + date_str: adjusted_date_string, + date_num: date_number, + reversed: adjustment_days <= 0.0, + invalid: false, + }) + } +}