oxedyne/fe2o3/fe2o3_datime/src/batch/mod.rs
10.2 KiB, 42 runs
created by r1870400018:8317, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! Batch operations for improved performance. |
| 2 | //! |
| 3 | //! Processing many date/time operations together shares calculations between |
| 4 | //! them, cutting per-call overhead and improving cache utilisation. |
| 5 | //! |
| 6 | //! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\ |
| 7 | //! Anthropic Claude |
| 8 | |
| 9 | use crate::{ |
| 10 | calendar::CalendarDate, |
| 11 | constant::{DayOfWeek, MonthOfYear}, |
| 12 | format::{CalClockFormatter, FormatPattern}, |
| 13 | time::{CalClock, CalClockZone, CalClockDuration}, |
| 14 | }; |
| 15 | |
| 16 | use oxedyne_fe2o3_core::prelude::*; |
| 17 | |
| 18 | use std::collections::HashMap; |
| 19 | |
| 20 | pub mod operations; |
| 21 | |
| 22 | #[derive(Debug, Clone)] |
| 23 | pub struct BatchProcessor { |
| 24 | default_zone: CalClockZone, |
| 25 | #[allow(dead_code)] |
| 26 | string_buffer: String, // pre-allocated |
| 27 | formatter: CalClockFormatter, |
| 28 | } |
| 29 | |
| 30 | impl BatchProcessor { |
| 31 | pub fn new(default_zone: CalClockZone) -> Self { |
| 32 | Self { |
| 33 | default_zone, |
| 34 | string_buffer: String::with_capacity(64), // Pre-allocate for typical datetime strings |
| 35 | formatter: CalClockFormatter::new(), |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | pub fn new_utc() -> Self { |
| 40 | Self::new(CalClockZone::utc()) |
| 41 | } |
| 42 | |
| 43 | pub fn convert_timezones(&self, calclocks: &[CalClock], target_zone: &CalClockZone) -> Outcome<Vec<CalClock>> { |
| 44 | let mut results = Vec::with_capacity(calclocks.len()); |
| 45 | |
| 46 | for calclock in calclocks { |
| 47 | let converted = res!(calclock.with_zone(target_zone.clone())); |
| 48 | results.push(converted); |
| 49 | } |
| 50 | |
| 51 | Ok(results) |
| 52 | } |
| 53 | |
| 54 | pub fn format_batch(&self, calclocks: &[CalClock], pattern: &FormatPattern) -> Outcome<Vec<String>> { |
| 55 | let mut results = Vec::with_capacity(calclocks.len()); |
| 56 | |
| 57 | for calclock in calclocks { |
| 58 | let formatted = res!(self.formatter.format_with_pattern(calclock, pattern)); |
| 59 | results.push(formatted); |
| 60 | } |
| 61 | |
| 62 | Ok(results) |
| 63 | } |
| 64 | |
| 65 | pub fn add_duration_batch(&self, calclocks: &[CalClock], duration: &CalClockDuration) -> Outcome<Vec<CalClock>> { |
| 66 | let mut results = Vec::with_capacity(calclocks.len()); |
| 67 | |
| 68 | for calclock in calclocks { |
| 69 | let new_calclock = res!(calclock.add_duration(duration)); |
| 70 | results.push(new_calclock); |
| 71 | } |
| 72 | |
| 73 | Ok(results) |
| 74 | } |
| 75 | |
| 76 | pub fn day_of_week_batch(&self, dates: &[CalendarDate]) -> Vec<DayOfWeek> { |
| 77 | dates.iter().map(|date| date.day_of_week()).collect() |
| 78 | } |
| 79 | |
| 80 | pub fn validate_dates_batch(&self, dates: &[(i32, MonthOfYear, u8)]) -> Vec<Outcome<()>> { |
| 81 | dates.iter().map(|(year, month, day)| { |
| 82 | CalendarDate::from_ymd(*year, *month, *day, self.default_zone.clone()).map(|_| ()) |
| 83 | }).collect() |
| 84 | } |
| 85 | |
| 86 | pub fn from_timestamps_batch(&self, timestamps_millis: &[i64], zone: &CalClockZone) -> Outcome<Vec<CalClock>> { |
| 87 | let mut results = Vec::with_capacity(timestamps_millis.len()); |
| 88 | |
| 89 | for ×tamp in timestamps_millis { |
| 90 | let calclock = res!(CalClock::from_millis(timestamp, zone.clone())); |
| 91 | results.push(calclock); |
| 92 | } |
| 93 | |
| 94 | Ok(results) |
| 95 | } |
| 96 | |
| 97 | pub fn duration_between_batch(&self, pairs: &[(CalClock, CalClock)]) -> Outcome<Vec<CalClockDuration>> { |
| 98 | let mut results = Vec::with_capacity(pairs.len()); |
| 99 | |
| 100 | for (start, end) in pairs { |
| 101 | let duration = res!(start.duration_until(end)); |
| 102 | results.push(duration); |
| 103 | } |
| 104 | |
| 105 | Ok(results) |
| 106 | } |
| 107 | |
| 108 | pub fn sort_calclocks(&self, calclocks: Vec<CalClock>) -> Outcome<Vec<CalClock>> { |
| 109 | // Convert to timestamps for efficient sorting |
| 110 | let mut indexed_clocks: Vec<(i64, CalClock)> = Vec::with_capacity(calclocks.len()); |
| 111 | |
| 112 | for calclock in calclocks { |
| 113 | let timestamp = res!(calclock.to_millis()); |
| 114 | indexed_clocks.push((timestamp, calclock)); |
| 115 | } |
| 116 | |
| 117 | // Sort by timestamp |
| 118 | indexed_clocks.sort_by_key(|(timestamp, _)| *timestamp); |
| 119 | |
| 120 | // Extract sorted CalClocks |
| 121 | Ok(indexed_clocks.into_iter().map(|(_, calclock)| calclock).collect()) |
| 122 | } |
| 123 | |
| 124 | /// Both bounds are inclusive. |
| 125 | pub fn filter_by_range(&self, calclocks: &[CalClock], start: &CalClock, end: &CalClock) -> Outcome<Vec<CalClock>> { |
| 126 | let start_millis = res!(start.to_millis()); |
| 127 | let end_millis = res!(end.to_millis()); |
| 128 | |
| 129 | let mut results = Vec::new(); |
| 130 | |
| 131 | for calclock in calclocks { |
| 132 | let calclock_millis = res!(calclock.to_millis()); |
| 133 | if calclock_millis >= start_millis && calclock_millis <= end_millis { |
| 134 | results.push(calclock.clone()); |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | Ok(results) |
| 139 | } |
| 140 | |
| 141 | /// Keys are YYYY-MM-DD. |
| 142 | pub fn group_by_day(&self, calclocks: &[CalClock]) -> HashMap<String, Vec<CalClock>> { |
| 143 | let mut groups: HashMap<String, Vec<CalClock>> = HashMap::new(); |
| 144 | |
| 145 | for calclock in calclocks { |
| 146 | let day_key = format!("{:04}-{:02}-{:02}", calclock.year(), calclock.month(), calclock.day()); |
| 147 | groups.entry(day_key).or_insert_with(Vec::new).push(calclock.clone()); |
| 148 | } |
| 149 | |
| 150 | groups |
| 151 | } |
| 152 | |
| 153 | /// Nothing is tracked yet, so every field reads zero. |
| 154 | pub fn stats(&self) -> BatchStats { |
| 155 | // This would be enhanced with actual performance tracking |
| 156 | BatchStats { |
| 157 | operations_processed: 0, // Placeholder |
| 158 | average_operation_time_nanos: 0, |
| 159 | cache_hit_ratio: 0.0, |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | #[derive(Debug, Clone)] |
| 165 | pub struct BatchStats { |
| 166 | pub operations_processed: u64, |
| 167 | pub average_operation_time_nanos: u64, |
| 168 | pub cache_hit_ratio: f64, |
| 169 | } |
| 170 | |
| 171 | impl BatchStats { |
| 172 | pub fn operations_per_second(&self) -> f64 { |
| 173 | if self.average_operation_time_nanos > 0 { |
| 174 | 1_000_000_000.0 / self.average_operation_time_nanos as f64 |
| 175 | } else { |
| 176 | 0.0 |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | #[derive(Debug)] |
| 182 | pub struct BatchBuilder { |
| 183 | zone: Option<CalClockZone>, |
| 184 | formatter: Option<CalClockFormatter>, |
| 185 | buffer_size: Option<usize>, |
| 186 | } |
| 187 | |
| 188 | impl BatchBuilder { |
| 189 | pub fn new() -> Self { |
| 190 | Self { |
| 191 | zone: None, |
| 192 | formatter: None, |
| 193 | buffer_size: None, |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | pub fn with_zone(mut self, zone: CalClockZone) -> Self { |
| 198 | self.zone = Some(zone); |
| 199 | self |
| 200 | } |
| 201 | |
| 202 | pub fn with_formatter(mut self, formatter: CalClockFormatter) -> Self { |
| 203 | self.formatter = Some(formatter); |
| 204 | self |
| 205 | } |
| 206 | |
| 207 | pub fn with_buffer_size(mut self, size: usize) -> Self { |
| 208 | self.buffer_size = Some(size); |
| 209 | self |
| 210 | } |
| 211 | |
| 212 | pub fn build(self) -> BatchProcessor { |
| 213 | let zone = self.zone.unwrap_or_else(|| CalClockZone::utc()); |
| 214 | let formatter = self.formatter.unwrap_or_else(|| CalClockFormatter::new()); |
| 215 | |
| 216 | BatchProcessor { |
| 217 | default_zone: zone, |
| 218 | string_buffer: String::with_capacity(self.buffer_size.unwrap_or(64)), |
| 219 | formatter, |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | impl Default for BatchBuilder { |
| 225 | fn default() -> Self { |
| 226 | Self::new() |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | #[cfg(test)] |
| 231 | mod tests { |
| 232 | use super::*; |
| 233 | use crate::{constant::MonthOfYear, format::FormatPattern}; |
| 234 | |
| 235 | #[test] |
| 236 | fn test_batch_processor_creation() { |
| 237 | let processor = BatchProcessor::new_utc(); |
| 238 | assert_eq!(processor.default_zone.id(), "UTC"); |
| 239 | } |
| 240 | |
| 241 | #[test] |
| 242 | fn test_batch_formatting() -> Outcome<()> { |
| 243 | let processor = BatchProcessor::new_utc(); |
| 244 | let pattern = res!(FormatPattern::new("yyyy-MM-dd")); |
| 245 | |
| 246 | let dates = vec![ |
| 247 | res!(CalClock::new(2024, 1, 15, 12, 0, 0, 0, CalClockZone::utc())), |
| 248 | res!(CalClock::new(2024, 2, 20, 14, 30, 0, 0, CalClockZone::utc())), |
| 249 | res!(CalClock::new(2024, 3, 10, 9, 15, 0, 0, CalClockZone::utc())), |
| 250 | ]; |
| 251 | |
| 252 | let formatted = res!(processor.format_batch(&dates, &pattern)); |
| 253 | assert_eq!(formatted.len(), 3); |
| 254 | assert_eq!(formatted[0], "2024-01-15"); |
| 255 | assert_eq!(formatted[1], "2024-02-20"); |
| 256 | assert_eq!(formatted[2], "2024-03-10"); |
| 257 | |
| 258 | Ok(()) |
| 259 | } |
| 260 | |
| 261 | #[test] |
| 262 | fn test_batch_duration_addition() -> Outcome<()> { |
| 263 | let processor = BatchProcessor::new_utc(); |
| 264 | let duration = CalClockDuration::from_days(7); // Add one week |
| 265 | |
| 266 | let dates = vec![ |
| 267 | res!(CalClock::new(2024, 1, 1, 0, 0, 0, 0, CalClockZone::utc())), |
| 268 | res!(CalClock::new(2024, 1, 15, 12, 0, 0, 0, CalClockZone::utc())), |
| 269 | ]; |
| 270 | |
| 271 | let results = res!(processor.add_duration_batch(&dates, &duration)); |
| 272 | assert_eq!(results.len(), 2); |
| 273 | assert_eq!(results[0].day(), 8); // January 8 |
| 274 | assert_eq!(results[1].day(), 22); // January 22 |
| 275 | |
| 276 | Ok(()) |
| 277 | } |
| 278 | |
| 279 | #[test] |
| 280 | fn test_batch_validation() { |
| 281 | let processor = BatchProcessor::new_utc(); |
| 282 | |
| 283 | let test_dates = vec![ |
| 284 | (2024, MonthOfYear::February, 29), // Valid leap year date |
| 285 | (2023, MonthOfYear::February, 29), // Invalid non-leap year date |
| 286 | (2024, MonthOfYear::April, 31), // Invalid date |
| 287 | (2024, MonthOfYear::June, 15), // Valid date |
| 288 | ]; |
| 289 | |
| 290 | let results = processor.validate_dates_batch(&test_dates); |
| 291 | assert_eq!(results.len(), 4); |
| 292 | assert!(results[0].is_ok()); // Valid leap year |
| 293 | assert!(results[1].is_err()); // Invalid non-leap year |
| 294 | assert!(results[2].is_err()); // Invalid date |
| 295 | assert!(results[3].is_ok()); // Valid date |
| 296 | } |
| 297 | |
| 298 | #[test] |
| 299 | fn test_batch_builder() { |
| 300 | let processor = BatchBuilder::new() |
| 301 | .with_zone(CalClockZone::utc()) |
| 302 | .with_buffer_size(128) |
| 303 | .build(); |
| 304 | |
| 305 | assert_eq!(processor.default_zone.id(), "UTC"); |
| 306 | } |
| 307 | |
| 308 | #[test] |
| 309 | fn test_day_of_week_batch() -> Outcome<()> { |
| 310 | let processor = BatchProcessor::new_utc(); |
| 311 | |
| 312 | let dates = vec![ |
| 313 | res!(CalendarDate::from_ymd(2024, MonthOfYear::January, 1, CalClockZone::utc())), // Monday |
| 314 | res!(CalendarDate::from_ymd(2024, MonthOfYear::January, 7, CalClockZone::utc())), // Sunday |
| 315 | ]; |
| 316 | |
| 317 | let days_of_week = processor.day_of_week_batch(&dates); |
| 318 | assert_eq!(days_of_week.len(), 2); |
| 319 | assert_eq!(days_of_week[0], DayOfWeek::Monday); |
| 320 | assert_eq!(days_of_week[1], DayOfWeek::Sunday); |
| 321 | |
| 322 | Ok(()) |
| 323 | } |
| 324 | } |