// 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 /// /// Memberships need to be in the format of: _"MEMBERSHIPNAME ($PRI.CE)"_ /// /// # Arguments /// * `current_membership` - The text content of what current membership you have /// * `new_membership` - The text content of what new membership you're getting /// * `last_billing` - The date of the last time the customer got billed _(mm/dd/yyyy)_ /// /// # Examples /// ```rs /// let result = calculator.start( /// "Super ($41.99)".to_string(), /// "Deluxe ($36.99)".to_string(), /// "7/16/2025".to_string() /// )?; /// ``` 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, }) } }