Oregami
Repositories/oxedyne/fe2o3

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
9use crate::{
10 calendar::CalendarDate,
11 constant::{DayOfWeek, MonthOfYear},
12 format::{CalClockFormatter, FormatPattern},
13 time::{CalClock, CalClockZone, CalClockDuration},
14};
15
16use oxedyne_fe2o3_core::prelude::*;
17
18use std::collections::HashMap;
19
20pub mod operations;
21
22#[derive(Debug, Clone)]
23pub struct BatchProcessor {
24 default_zone: CalClockZone,
25 #[allow(dead_code)]
26 string_buffer: String, // pre-allocated
27 formatter: CalClockFormatter,
28}
29
30impl 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 &timestamp 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)]
165pub struct BatchStats {
166 pub operations_processed: u64,
167 pub average_operation_time_nanos: u64,
168 pub cache_hit_ratio: f64,
169}
170
171impl 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)]
182pub struct BatchBuilder {
183 zone: Option<CalClockZone>,
184 formatter: Option<CalClockFormatter>,
185 buffer_size: Option<usize>,
186}
187
188impl 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
224impl Default for BatchBuilder {
225 fn default() -> Self {
226 Self::new()
227 }
228}
229
230#[cfg(test)]
231mod 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}