rust-queries-core 1.0.8

Core functionality for rust-queries-builder - type-safe query builder for Rust collections
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
//! DateTime operations for query builder.
//!
//! This module provides datetime comparison and manipulation operations that can be used
//! with the query builder. It supports both `std::time::SystemTime` and optionally
//! `chrono` types when the `datetime` feature is enabled.
//!
//! # Features
//!
//! - Date comparisons (before, after, between)
//! - Time range queries
//! - Date arithmetic (add/subtract days, hours, etc.)
//! - Date extraction (year, month, day, hour, etc.)
//! - Timezone-aware operations (with chrono)
//!
//! # Example
//!
//! ```ignore
//! use rust_queries_core::{Query, datetime::*};
//! use chrono::{DateTime, Utc};
//!
//! #[derive(Keypath)]
//! struct Event {
//!     name: String,
//!     timestamp: DateTime<Utc>,
//! }
//!
//! let events = vec![/* ... */];
//! let recent = Query::new(&events)
//!     .where_(Event::timestamp(), |ts| {
//!         is_after(ts, &Utc::now() - chrono::Duration::days(7))
//!     });
//! ```

use std::time::{SystemTime, Duration, UNIX_EPOCH};

#[cfg(feature = "datetime")]
pub use chrono;

/// Check if a SystemTime is after another SystemTime
pub fn is_after_systemtime(time: &SystemTime, reference: &SystemTime) -> bool {
    time > reference
}

/// Check if a SystemTime is before another SystemTime
pub fn is_before_systemtime(time: &SystemTime, reference: &SystemTime) -> bool {
    time < reference
}

/// Check if a SystemTime is between two SystemTimes (inclusive)
pub fn is_between_systemtime(time: &SystemTime, start: &SystemTime, end: &SystemTime) -> bool {
    time >= start && time <= end
}

/// Check if a SystemTime is within a duration from now
pub fn is_within_duration_systemtime(time: &SystemTime, duration: Duration) -> bool {
    match SystemTime::now().duration_since(UNIX_EPOCH) {
        Ok(now) => {
            if let Ok(time_dur) = time.duration_since(UNIX_EPOCH) {
                let diff = if now > time_dur {
                    now - time_dur
                } else {
                    time_dur - now
                };
                diff <= duration
            } else {
                false
            }
        }
        Err(_) => false,
    }
}

/// Add duration to SystemTime
pub fn add_duration_systemtime(time: &SystemTime, duration: Duration) -> SystemTime {
    *time + duration
}

/// Subtract duration from SystemTime
pub fn subtract_duration_systemtime(time: &SystemTime, duration: Duration) -> SystemTime {
    *time - duration
}

// Chrono-specific operations (only available with datetime feature)
#[cfg(feature = "datetime")]
pub mod chrono_ops {
    use chrono::{DateTime, TimeZone, Datelike, Timelike, Duration};

    /// Check if a DateTime is after another DateTime
    pub fn is_after<Tz: TimeZone>(time: &DateTime<Tz>, reference: &DateTime<Tz>) -> bool
    where
        Tz::Offset: std::fmt::Display,
    {
        time > reference
    }

    /// Check if a DateTime is before another DateTime
    pub fn is_before<Tz: TimeZone>(time: &DateTime<Tz>, reference: &DateTime<Tz>) -> bool
    where
        Tz::Offset: std::fmt::Display,
    {
        time < reference
    }

    /// Check if a DateTime is between two DateTimes (inclusive)
    pub fn is_between<Tz: TimeZone>(
        time: &DateTime<Tz>,
        start: &DateTime<Tz>,
        end: &DateTime<Tz>,
    ) -> bool
    where
        Tz::Offset: std::fmt::Display,
    {
        time >= start && time <= end
    }

    /// Check if a DateTime is today
    pub fn is_today<Tz: TimeZone>(time: &DateTime<Tz>, now: &DateTime<Tz>) -> bool
    where
        Tz::Offset: std::fmt::Display,
    {
        time.date_naive() == now.date_naive()
    }

    /// Check if a DateTime is within a duration from now
    pub fn is_within_duration<Tz: TimeZone>(
        time: &DateTime<Tz>,
        now: &DateTime<Tz>,
        duration: Duration,
    ) -> bool
    where
        Tz::Offset: std::fmt::Display,
    {
        let diff = if time > now {
            time.clone() - now.clone()
        } else {
            now.clone() - time.clone()
        };
        diff <= duration
    }

    /// Check if two DateTimes are on the same day
    pub fn is_same_day<Tz: TimeZone>(time1: &DateTime<Tz>, time2: &DateTime<Tz>) -> bool
    where
        Tz::Offset: std::fmt::Display,
    {
        time1.date_naive() == time2.date_naive()
    }

    /// Check if a DateTime is in the past
    pub fn is_past<Tz: TimeZone>(time: &DateTime<Tz>, now: &DateTime<Tz>) -> bool
    where
        Tz::Offset: std::fmt::Display,
    {
        time < now
    }

    /// Check if a DateTime is in the future
    pub fn is_future<Tz: TimeZone>(time: &DateTime<Tz>, now: &DateTime<Tz>) -> bool
    where
        Tz::Offset: std::fmt::Display,
    {
        time > now
    }

    /// Extract year from DateTime
    pub fn extract_year<Tz: TimeZone>(time: &DateTime<Tz>) -> i32
    where
        Tz::Offset: std::fmt::Display,
    {
        time.year()
    }

    /// Extract month from DateTime (1-12)
    pub fn extract_month<Tz: TimeZone>(time: &DateTime<Tz>) -> u32
    where
        Tz::Offset: std::fmt::Display,
    {
        time.month()
    }

    /// Extract day from DateTime (1-31)
    pub fn extract_day<Tz: TimeZone>(time: &DateTime<Tz>) -> u32
    where
        Tz::Offset: std::fmt::Display,
    {
        time.day()
    }

    /// Extract hour from DateTime (0-23)
    pub fn extract_hour<Tz: TimeZone>(time: &DateTime<Tz>) -> u32
    where
        Tz::Offset: std::fmt::Display,
    {
        time.hour()
    }

    /// Extract minute from DateTime (0-59)
    pub fn extract_minute<Tz: TimeZone>(time: &DateTime<Tz>) -> u32
    where
        Tz::Offset: std::fmt::Display,
    {
        time.minute()
    }

    /// Extract second from DateTime (0-59)
    pub fn extract_second<Tz: TimeZone>(time: &DateTime<Tz>) -> u32
    where
        Tz::Offset: std::fmt::Display,
    {
        time.second()
    }

    /// Get day of week (Monday = 0, Sunday = 6)
    pub fn day_of_week<Tz: TimeZone>(time: &DateTime<Tz>) -> u32
    where
        Tz::Offset: std::fmt::Display,
    {
        time.weekday().num_days_from_monday()
    }

    /// Check if a DateTime is on a weekend (Saturday or Sunday)
    pub fn is_weekend<Tz: TimeZone>(time: &DateTime<Tz>) -> bool
    where
        Tz::Offset: std::fmt::Display,
    {
        let weekday = time.weekday().num_days_from_monday();
        weekday >= 5 // Saturday = 5, Sunday = 6
    }

    /// Check if a DateTime is on a weekday (Monday-Friday)
    pub fn is_weekday<Tz: TimeZone>(time: &DateTime<Tz>) -> bool
    where
        Tz::Offset: std::fmt::Display,
    {
        !is_weekend(time)
    }

    /// Add days to a DateTime
    pub fn add_days<Tz: TimeZone>(time: &DateTime<Tz>, days: i64) -> DateTime<Tz>
    where
        Tz::Offset: std::fmt::Display,
    {
        time.clone() + Duration::days(days)
    }

    /// Add hours to a DateTime
    pub fn add_hours<Tz: TimeZone>(time: &DateTime<Tz>, hours: i64) -> DateTime<Tz>
    where
        Tz::Offset: std::fmt::Display,
    {
        time.clone() + Duration::hours(hours)
    }

    /// Add minutes to a DateTime
    pub fn add_minutes<Tz: TimeZone>(time: &DateTime<Tz>, minutes: i64) -> DateTime<Tz>
    where
        Tz::Offset: std::fmt::Display,
    {
        time.clone() + Duration::minutes(minutes)
    }

    /// Subtract days from a DateTime
    pub fn subtract_days<Tz: TimeZone>(time: &DateTime<Tz>, days: i64) -> DateTime<Tz>
    where
        Tz::Offset: std::fmt::Display,
    {
        time.clone() - Duration::days(days)
    }

    /// Subtract hours from a DateTime
    pub fn subtract_hours<Tz: TimeZone>(time: &DateTime<Tz>, hours: i64) -> DateTime<Tz>
    where
        Tz::Offset: std::fmt::Display,
    {
        time.clone() - Duration::hours(hours)
    }

    /// Subtract minutes from a DateTime
    pub fn subtract_minutes<Tz: TimeZone>(time: &DateTime<Tz>, minutes: i64) -> DateTime<Tz>
    where
        Tz::Offset: std::fmt::Display,
    {
        time.clone() - Duration::minutes(minutes)
    }

    /// Get the start of day (midnight) for a DateTime
    pub fn start_of_day<Tz: TimeZone + Clone>(time: &DateTime<Tz>) -> Option<DateTime<Tz>>
    where
        Tz::Offset: std::fmt::Display,
    {
        time.date_naive()
            .and_hms_opt(0, 0, 0)
            .and_then(|naive| time.timezone().from_local_datetime(&naive).single())
    }

    /// Get the end of day (23:59:59) for a DateTime
    pub fn end_of_day<Tz: TimeZone + Clone>(time: &DateTime<Tz>) -> Option<DateTime<Tz>>
    where
        Tz::Offset: std::fmt::Display,
    {
        time.date_naive()
            .and_hms_opt(23, 59, 59)
            .and_then(|naive| time.timezone().from_local_datetime(&naive).single())
    }

    /// Check if a DateTime falls within business hours (9 AM - 5 PM)
    pub fn is_business_hours<Tz: TimeZone>(time: &DateTime<Tz>) -> bool
    where
        Tz::Offset: std::fmt::Display,
    {
        let hour = time.hour();
        hour >= 9 && hour < 17
    }

    /// Calculate the number of days between two DateTimes
    pub fn days_between<Tz: TimeZone>(time1: &DateTime<Tz>, time2: &DateTime<Tz>) -> i64
    where
        Tz::Offset: std::fmt::Display,
    {
        let diff = if time1 > time2 {
            time1.clone() - time2.clone()
        } else {
            time2.clone() - time1.clone()
        };
        diff.num_days()
    }

    /// Calculate the number of hours between two DateTimes
    pub fn hours_between<Tz: TimeZone>(time1: &DateTime<Tz>, time2: &DateTime<Tz>) -> i64
    where
        Tz::Offset: std::fmt::Display,
    {
        let diff = if time1 > time2 {
            time1.clone() - time2.clone()
        } else {
            time2.clone() - time1.clone()
        };
        diff.num_hours()
    }
}

#[cfg(test)]
#[cfg(feature = "datetime")]
mod tests {
    use super::*;
    use chrono::{Utc, Duration, TimeZone};

    #[test]
    fn test_is_after() {
        let now = Utc::now();
        let future = now + Duration::hours(1);
        let past = now - Duration::hours(1);

        assert!(chrono_ops::is_after(&future, &now));
        assert!(!chrono_ops::is_after(&past, &now));
    }

    #[test]
    fn test_is_between() {
        let start = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
        let end = Utc.with_ymd_and_hms(2024, 12, 31, 23, 59, 59).unwrap();
        let middle = Utc.with_ymd_and_hms(2024, 6, 15, 12, 0, 0).unwrap();
        let before = Utc.with_ymd_and_hms(2023, 1, 1, 0, 0, 0).unwrap();

        assert!(chrono_ops::is_between(&middle, &start, &end));
        assert!(!chrono_ops::is_between(&before, &start, &end));
    }

    #[test]
    fn test_date_extraction() {
        let dt = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 45).unwrap();

        assert_eq!(chrono_ops::extract_year(&dt), 2024);
        assert_eq!(chrono_ops::extract_month(&dt), 3);
        assert_eq!(chrono_ops::extract_day(&dt), 15);
        assert_eq!(chrono_ops::extract_hour(&dt), 14);
        assert_eq!(chrono_ops::extract_minute(&dt), 30);
        assert_eq!(chrono_ops::extract_second(&dt), 45);
    }

    #[test]
    fn test_date_arithmetic() {
        let dt = Utc.with_ymd_and_hms(2024, 3, 15, 12, 0, 0).unwrap();
        let future = chrono_ops::add_days(&dt, 10);
        let past = chrono_ops::subtract_days(&dt, 5);

        assert_eq!(chrono_ops::extract_day(&future), 25);
        assert_eq!(chrono_ops::extract_day(&past), 10);
    }

    #[test]
    fn test_is_weekend() {
        // Saturday, March 16, 2024
        let saturday = Utc.with_ymd_and_hms(2024, 3, 16, 12, 0, 0).unwrap();
        // Monday, March 18, 2024
        let monday = Utc.with_ymd_and_hms(2024, 3, 18, 12, 0, 0).unwrap();

        assert!(chrono_ops::is_weekend(&saturday));
        assert!(!chrono_ops::is_weekend(&monday));
        assert!(chrono_ops::is_weekday(&monday));
    }

    #[test]
    fn test_is_business_hours() {
        let morning = Utc.with_ymd_and_hms(2024, 3, 15, 10, 0, 0).unwrap();
        let evening = Utc.with_ymd_and_hms(2024, 3, 15, 18, 0, 0).unwrap();

        assert!(chrono_ops::is_business_hours(&morning));
        assert!(!chrono_ops::is_business_hours(&evening));
    }
}