From f5fd93547576f9410146e3910dc5c95ec7747cac Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Wed, 14 May 2025 23:51:36 +0100 Subject: [PATCH 1/8] Refactor groups method in DatesList to use a helper function for modularity --- src/utils/dateutils/dates.rs | 59 ++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/src/utils/dateutils/dates.rs b/src/utils/dateutils/dates.rs index 77946c2..d004cc3 100644 --- a/src/utils/dateutils/dates.rs +++ b/src/utils/dateutils/dates.rs @@ -340,32 +340,7 @@ impl DatesList { /// Returns an error if the start or end date strings cannot be parsed. pub fn groups(&self) -> Result>, Box> { let dates = self.list()?; - let mut groups: HashMap> = HashMap::new(); - - for date in dates { - let key = match self.freq { - DateFreq::Daily => GroupKey::Daily(date), - DateFreq::WeeklyMonday | DateFreq::WeeklyFriday => { - let iso_week = date.iso_week(); - GroupKey::Weekly(iso_week.year(), iso_week.week()) - } - DateFreq::MonthStart | DateFreq::MonthEnd => { - GroupKey::Monthly(date.year(), date.month()) - } - DateFreq::QuarterStart | DateFreq::QuarterEnd => { - GroupKey::Quarterly(date.year(), month_to_quarter(date.month())) - } - DateFreq::YearStart | DateFreq::YearEnd => GroupKey::Yearly(date.year()), - }; - groups.entry(key).or_insert_with(Vec::new).push(date); - } - - let mut sorted_groups: Vec<(GroupKey, Vec)> = groups.into_iter().collect(); - sorted_groups.sort_by(|(k1, _), (k2, _)| k1.cmp(k2)); - - // Dates within groups are already sorted because they came from the sorted `self.list()`. - let result_groups = sorted_groups.into_iter().map(|(_, dates)| dates).collect(); - Ok(result_groups) + group_dates_helper(dates, self.freq) } /// Returns the start date parsed as a `NaiveDate`. @@ -563,6 +538,38 @@ impl Iterator for DatesGenerator { // --- Internal helper functions --- +pub fn group_dates_helper( + dates: Vec, + freq: DateFreq, +) -> Result>, Box> { + let mut groups: HashMap> = HashMap::new(); + + for date in dates { + let key = match freq { + DateFreq::Daily => GroupKey::Daily(date), + DateFreq::WeeklyMonday | DateFreq::WeeklyFriday => { + let iso_week = date.iso_week(); + GroupKey::Weekly(iso_week.year(), iso_week.week()) + } + DateFreq::MonthStart | DateFreq::MonthEnd => { + GroupKey::Monthly(date.year(), date.month()) + } + DateFreq::QuarterStart | DateFreq::QuarterEnd => { + GroupKey::Quarterly(date.year(), month_to_quarter(date.month())) + } + DateFreq::YearStart | DateFreq::YearEnd => GroupKey::Yearly(date.year()), + }; + groups.entry(key).or_insert_with(Vec::new).push(date); + } + + let mut sorted_groups: Vec<(GroupKey, Vec)> = groups.into_iter().collect(); + sorted_groups.sort_by(|(k1, _), (k2, _)| k1.cmp(k2)); + + // Dates within groups are already sorted because they came from the sorted `self.list()`. + let result_groups = sorted_groups.into_iter().map(|(_, dates)| dates).collect(); + Ok(result_groups) +} + /// Generates the flat list of dates for the given range and frequency. /// Assumes the `collect_*` functions return sorted dates. pub fn get_dates_list_with_freq( From 629c9d84e224dcfbb81de5096f819298c517a3f4 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Wed, 14 May 2025 23:52:11 +0100 Subject: [PATCH 2/8] Refactor groups method in BDatesList to use `dates::group_dates_helper` --- src/utils/dateutils/bdates.rs | 39 +---------------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/src/utils/dateutils/bdates.rs b/src/utils/dateutils/bdates.rs index f4dc849..0182ae7 100644 --- a/src/utils/dateutils/bdates.rs +++ b/src/utils/dateutils/bdates.rs @@ -219,45 +219,8 @@ impl BDatesList { /// /// Returns an error if the start or end date strings cannot be parsed. pub fn groups(&self) -> Result>, Box> { - // Retrieve all business dates in chronological order. let dates = self.list()?; - - // Aggregate dates into buckets keyed by period. - let mut groups: HashMap> = HashMap::new(); - - for date in dates { - // Derive the appropriate GroupKey for the current date based on the configured frequency. - let key = match self.freq { - DateFreq::Daily => GroupKey::Daily(date), - DateFreq::WeeklyMonday | DateFreq::WeeklyFriday => { - let iso_week = date.iso_week(); - GroupKey::Weekly(iso_week.year(), iso_week.week()) - } - DateFreq::MonthStart | DateFreq::MonthEnd => { - GroupKey::Monthly(date.year(), date.month()) - } - DateFreq::QuarterStart | DateFreq::QuarterEnd => { - GroupKey::Quarterly(date.year(), dates::month_to_quarter(date.month())) - } - DateFreq::YearStart | DateFreq::YearEnd => GroupKey::Yearly(date.year()), - }; - - // Append the date to its period group. - groups.entry(key).or_insert_with(Vec::new).push(date); - } - - // Transform the group map into a vector of (GroupKey, Vec) tuples. - let mut sorted_groups: Vec<(GroupKey, Vec)> = groups.into_iter().collect(); - - // Sort groups chronologically using the derived `Ord` implementation on `GroupKey`. - sorted_groups.sort_by(|(k1, _), (k2, _)| k1.cmp(k2)); - - // Note: Dates within each group remain sorted due to initial ordered input. - - // Discard group keys to return only the list of date vectors. - let result_groups = sorted_groups.into_iter().map(|(_, dates)| dates).collect(); - - Ok(result_groups) + dates::group_dates_helper(dates, self.freq) } /// Returns the start date parsed as a `NaiveDate`. From c37659f09daef19db5d6158361b787d2035fda33 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Wed, 14 May 2025 23:54:11 +0100 Subject: [PATCH 3/8] removing unused comments and cleaning up the code structure --- src/utils/dateutils/bdates.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/utils/dateutils/bdates.rs b/src/utils/dateutils/bdates.rs index 0182ae7..bac5f5c 100644 --- a/src/utils/dateutils/bdates.rs +++ b/src/utils/dateutils/bdates.rs @@ -1,7 +1,9 @@ +//! This module provides functionality for generating and manipulating business dates. +//! It includes the `BDatesList`, which emulates a `DateList` structure and its properties. +//! It uses `DateList` and `DateListGenerator`, adjusting the output to work on business dates. + use chrono::{Datelike, Duration, NaiveDate, Weekday}; -use std::collections::HashMap; use std::error::Error; -use std::hash::Hash; use std::result::Result; use crate::utils::dateutils::dates::{find_next_date, AggregationType, DateFreq, DatesGenerator}; @@ -16,19 +18,6 @@ pub struct BDatesList { start_date_str: String, end_date_str: String, freq: DateFreq, - // TODO: cache the generated date list to reduce repeated computation. - // Currently, list(), count(), and groups() regenerate the list on every invocation. - // cached_list: Option>, -} - -// Enumeration of period keys used for grouping dates. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -enum GroupKey { - Daily(NaiveDate), // Daily grouping: use the exact date - Weekly(i32, u32), // Weekly grouping: use year and ISO week number - Monthly(i32, u32), // Monthly grouping: use year and month (1-12) - Quarterly(i32, u32), // Quarterly grouping: use year and quarter (1-4) - Yearly(i32), // Yearly grouping: use year } /// Represents a collection of business dates generated according to specific rules. @@ -404,7 +393,6 @@ impl Iterator for BDatesGenerator { DateFreq::WeeklyMonday | DateFreq::WeeklyFriday => next_date, DateFreq::MonthEnd | DateFreq::QuarterEnd | DateFreq::YearEnd => { - // Adjust to the last business date of the month, quarter, or year. let adjusted_date = iter_reverse_till_bdate(next_date); if self.start_date > adjusted_date { // Skip this iteration if the adjusted date is before the start date. @@ -1192,4 +1180,4 @@ mod tests { ); Ok(()) } -} // end mod tests +} From 52729e55bedd3959e632d51bc699e2bc5b6076bc Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Thu, 15 May 2025 00:00:56 +0100 Subject: [PATCH 4/8] Clean up comments and improve code clarity --- src/utils/dateutils/dates.rs | 86 ++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/src/utils/dateutils/dates.rs b/src/utils/dateutils/dates.rs index d004cc3..1bf516a 100644 --- a/src/utils/dateutils/dates.rs +++ b/src/utils/dateutils/dates.rs @@ -5,8 +5,6 @@ use std::hash::Hash; use std::result::Result; use std::str::FromStr; -// --- Core Enums --- - /// Represents the frequency at which calendar dates should be generated. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DateFreq { @@ -124,8 +122,6 @@ impl FromStr for DateFreq { } } -// --- DatesList Struct --- - /// Represents a list of calendar dates generated between a start and end date /// at a specified frequency. Provides methods to retrieve the full list, /// count, or dates grouped by period. @@ -382,8 +378,6 @@ impl DatesList { } } -// --- Dates Generator (Iterator) --- - /// An iterator that generates a sequence of calendar dates based on a start date, /// frequency, and a specified number of periods. /// @@ -536,7 +530,7 @@ impl Iterator for DatesGenerator { } } -// --- Internal helper functions --- +// Internal helper functions pub fn group_dates_helper( dates: Vec, @@ -608,7 +602,7 @@ pub fn get_dates_list_with_freq( Ok(dates) } -/* ---------------------- Low-Level Date Collection Functions (Internal) ---------------------- */ +// Low-Level Date Collection Functions (Internal) // These functions generate dates within a *range* [start_date, end_date] /// Returns all calendar days day-by-day within the range. @@ -740,6 +734,9 @@ fn collect_quarterly( Ok(result) } +/// Returns a list of dates between the given start and end dates, inclusive, +/// at the specified frequency. +/// This function is a convenience wrapper around `get_dates_list_with_freq`. pub fn get_dates_list_with_freq_from_naive_date( start_date: NaiveDate, end_date: NaiveDate, @@ -781,8 +778,6 @@ fn collect_yearly( Ok(result) } -/* ---------------------- Core Date Utility Functions (Internal) ---------------------- */ - /// Given a date and a `target_weekday`, returns the date that is the first /// `target_weekday` on or after the given date. fn move_to_day_of_week_on_or_after( @@ -1474,8 +1469,6 @@ mod tests { Ok(()) } - // --- Tests for internal helper functions --- - #[test] fn test_move_to_day_of_week_on_or_after() -> Result<(), Box> { assert_eq!( @@ -1527,12 +1520,15 @@ mod tests { fn test_days_in_month() -> Result<(), Box> { assert_eq!(days_in_month(2023, 1)?, 31); assert_eq!(days_in_month(2023, 2)?, 28); - assert_eq!(days_in_month(2024, 2)?, 29); // Leap + // Leap + assert_eq!(days_in_month(2024, 2)?, 29); assert_eq!(days_in_month(2023, 4)?, 30); assert_eq!(days_in_month(2023, 12)?, 31); - assert!(days_in_month(2023, 0).is_err()); // Invalid month 0 - assert!(days_in_month(2023, 13).is_err()); // Invalid month 13 - // Test near max date year overflow - Use MAX.year() + // Invalid month 0 + assert!(days_in_month(2023, 0).is_err()); + // Invalid month 13 + // Test near max date year overflow - Use MAX.year() + assert!(days_in_month(2023, 13).is_err()); assert!(days_in_month(NaiveDate::MAX.year(), 12).is_err()); Ok(()) } @@ -1542,9 +1538,12 @@ mod tests { assert_eq!(last_day_of_month(2023, 11)?, date(2023, 11, 30)); assert_eq!(last_day_of_month(2024, 2)?, date(2024, 2, 29)); // Leap assert_eq!(last_day_of_month(2023, 12)?, date(2023, 12, 31)); - assert!(last_day_of_month(2023, 0).is_err()); // Invalid month 0 - assert!(last_day_of_month(2023, 13).is_err()); // Invalid month 13 - // Test near max date year overflow - use MAX.year() + // Invalid month 0 + assert!(last_day_of_month(2023, 0).is_err()); + // Invalid month 13 + // Test near max date year overflow - use MAX.year() + assert!(last_day_of_month(2023, 13).is_err()); + assert!(last_day_of_month(NaiveDate::MAX.year(), 12).is_err()); Ok(()) } @@ -1588,7 +1587,8 @@ mod tests { assert_eq!(first_day_of_quarter(2023, 2)?, date(2023, 4, 1)); assert_eq!(first_day_of_quarter(2023, 3)?, date(2023, 7, 1)); assert_eq!(first_day_of_quarter(2023, 4)?, date(2023, 10, 1)); - assert!(first_day_of_quarter(2023, 5).is_err()); // Invalid quarter + // Invalid quarter + assert!(first_day_of_quarter(2023, 5).is_err()); Ok(()) } @@ -1608,9 +1608,11 @@ mod tests { assert_eq!(last_day_of_quarter(2023, 2)?, date(2023, 6, 30)); assert_eq!(last_day_of_quarter(2023, 3)?, date(2023, 9, 30)); assert_eq!(last_day_of_quarter(2023, 4)?, date(2023, 12, 31)); - assert_eq!(last_day_of_quarter(2024, 1)?, date(2024, 3, 31)); // Leap year doesn't affect March end - assert!(last_day_of_quarter(2023, 5).is_err()); // Invalid quarter - // Test overflow propagation - use MAX.year() + // Leap year doesn't affect March end + assert_eq!(last_day_of_quarter(2024, 1)?, date(2024, 3, 31)); + // Invalid quarter + // Test overflow propagation - use MAX.year() + assert!(last_day_of_quarter(2023, 5).is_err()); assert!(last_day_of_quarter(NaiveDate::MAX.year(), 4).is_err()); Ok(()) } @@ -1627,16 +1629,13 @@ mod tests { #[test] fn test_last_day_of_year() -> Result<(), Box> { assert_eq!(last_day_of_year(2023)?, date(2023, 12, 31)); - assert_eq!(last_day_of_year(2024)?, date(2024, 12, 31)); // Leap year doesn't affect Dec 31st existence - // Test MAX year - should be okay since MAX is Dec 31 + // Leap year doesn't affect Dec 31st existence + // Test MAX year - should be okay since MAX is Dec 31 + assert_eq!(last_day_of_year(2024)?, date(2024, 12, 31)); assert_eq!(last_day_of_year(NaiveDate::MAX.year())?, NaiveDate::MAX); Ok(()) } - // Overflow tests for collect_* removed as they were misleading - - // --- Tests for Generator Helper Functions --- - #[test] fn test_find_first_date_on_or_after() -> Result<(), Box> { // Daily @@ -1644,10 +1643,11 @@ mod tests { find_first_date_on_or_after(date(2023, 11, 8), DateFreq::Daily)?, date(2023, 11, 8) ); + // Sat -> Sat assert_eq!( find_first_date_on_or_after(date(2023, 11, 11), DateFreq::Daily)?, date(2023, 11, 11) - ); // Sat -> Sat + ); // Weekly Mon assert_eq!( @@ -1658,10 +1658,11 @@ mod tests { find_first_date_on_or_after(date(2023, 11, 13), DateFreq::WeeklyMonday)?, date(2023, 11, 13) ); + // Sun -> Mon assert_eq!( find_first_date_on_or_after(date(2023, 11, 12), DateFreq::WeeklyMonday)?, date(2023, 11, 13) - ); // Sun -> Mon + ); // Weekly Fri assert_eq!( @@ -1690,10 +1691,11 @@ mod tests { find_first_date_on_or_after(date(2023, 12, 15), DateFreq::MonthStart)?, date(2024, 1, 1) ); + // Oct 1 -> Oct 1 assert_eq!( find_first_date_on_or_after(date(2023, 10, 1), DateFreq::MonthStart)?, date(2023, 10, 1) - ); // Oct 1 -> Oct 1 + ); // Month End assert_eq!( @@ -1704,18 +1706,21 @@ mod tests { find_first_date_on_or_after(date(2023, 11, 15), DateFreq::MonthEnd)?, date(2023, 11, 30) ); + // Dec 31 -> Dec 31 assert_eq!( find_first_date_on_or_after(date(2023, 12, 31), DateFreq::MonthEnd)?, date(2023, 12, 31) - ); // Dec 31 -> Dec 31 + ); + // Mid Feb (Leap) -> Feb 29 assert_eq!( find_first_date_on_or_after(date(2024, 2, 15), DateFreq::MonthEnd)?, date(2024, 2, 29) - ); // Mid Feb (Leap) -> Feb 29 + ); + // Feb 29 -> Feb 29 assert_eq!( find_first_date_on_or_after(date(2024, 2, 29), DateFreq::MonthEnd)?, date(2024, 2, 29) - ); // Feb 29 -> Feb 29 + ); // Quarter Start assert_eq!( @@ -2146,12 +2151,15 @@ mod tests { // find_first returns start_date (YE MAX-1) assert_eq!(generator.next(), Some(start_date)); // find_next finds YE(MAX) - assert_eq!(generator.next(), Some(last_day_of_year(start_year)?)); // Should be MAX - // find_next tries YE(MAX+1) - this call to find_next_date fails internally - assert_eq!(generator.next(), None); // Returns None because internal find_next_date failed + assert_eq!(generator.next(), Some(last_day_of_year(start_year)?)); + // Should be MAX + // find_next tries YE(MAX+1) - this call to find_next_date fails internally + assert_eq!(generator.next(), None); + // Returns None because internal find_next_date failed // State after the *first* None is returned: - assert_eq!(generator.periods_remaining, 0); // Corrected assertion + // Corrected assertion + assert_eq!(generator.periods_remaining, 0); assert!(generator.next_date_candidate.is_none()); // Calling next() again should also return None From 127e178b7902d9fe0480021647c647121b92fc2d Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Thu, 15 May 2025 00:08:58 +0100 Subject: [PATCH 5/8] Remove commented-out code and streamline module exports in dateutils --- src/utils/dateutils/mod.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/utils/dateutils/mod.rs b/src/utils/dateutils/mod.rs index d95aef8..53ea47c 100644 --- a/src/utils/dateutils/mod.rs +++ b/src/utils/dateutils/mod.rs @@ -1,8 +1,5 @@ pub mod bdates; -// pub use bdates::{BDateFreq, BDatesList, BDatesGenerator}; - pub mod dates; -// pub use dates::{DateFreq, DatesList, DatesGenerator}; -// pub mod base; -// pub use base::{BDatesGenerator, BDatesList}; -// pub use base::{DateFreq, DatesGenerator, DatesList}; + +pub use bdates::{BDateFreq, BDatesGenerator, BDatesList}; +pub use dates::{DateFreq, DatesGenerator, DatesList}; From 8f3bc6843bf462e0e4df84878366ee2728c5b2c1 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Thu, 15 May 2025 00:09:11 +0100 Subject: [PATCH 6/8] Refactor module exports in mod.rs for clarity and consistency --- src/utils/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index f759f8e..e00de87 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,4 @@ pub mod dateutils; -pub use dateutils::bdates::{BDatesGenerator, BDatesList}; -pub use dateutils::dates::{DateFreq, DatesGenerator, DatesList}; -// pub use dateutils::{BDatesGenerator, BDatesList}; -// pub use dateutils::{DateFreq, DatesGenerator, DatesList}; + +pub use dateutils::{BDateFreq, BDatesGenerator, BDatesList}; +pub use dateutils::{DateFreq, DatesGenerator, DatesList}; From 4e98aa1490f7cb86f8585980d7967a0b9dcd7e06 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Thu, 15 May 2025 00:09:17 +0100 Subject: [PATCH 7/8] Reorder imports in benchmarks.rs for consistency and clarity --- benches/benchmarks.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index bb74a64..93cf8cd 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -5,7 +5,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use rustframe::{ frame::{Frame, RowIndex}, matrix::{BoolMatrix, Matrix, SeriesOps}, - utils::{BDateFreq, BDatesList}, + utils::{BDatesList, BDateFreq}, }; use std::time::Duration; @@ -254,7 +254,6 @@ fn config_large_arrays() -> Criterion { .warm_up_time(Duration::from_millis(200)) } - criterion_group!( name = benches_small_arrays; config = config_small_arrays(); From bc00035bc0e8973ee2436dd2a45a737fb5ceb942 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Thu, 15 May 2025 00:09:23 +0100 Subject: [PATCH 8/8] Add type alias for `DateFreq` to enhance clarity in business date frequency representation --- src/utils/dateutils/bdates.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/dateutils/bdates.rs b/src/utils/dateutils/bdates.rs index bac5f5c..d53f6e2 100644 --- a/src/utils/dateutils/bdates.rs +++ b/src/utils/dateutils/bdates.rs @@ -10,6 +10,9 @@ use crate::utils::dateutils::dates::{find_next_date, AggregationType, DateFreq, use crate::utils::dateutils::dates; +/// Type alias for `DateFreq` to represent business date frequency. +pub type BDateFreq = DateFreq; + /// Represents a list of business dates generated between a start and end date /// at a specified frequency. Provides methods to retrieve the full list, /// count, or dates grouped by period.