Skip to main content

protest_extras/generators/
datetime.rs

1//! DateTime-related generators (std::time only)
2//!
3//! Provides generators for:
4//! - Unix timestamps (i64)
5//! - Duration values
6//! - SystemTime ranges
7//!
8//! All generators use std library only (no chrono dependency).
9
10use protest::{Generator, GeneratorConfig};
11use rand::Rng;
12use std::time::{Duration, SystemTime, UNIX_EPOCH};
13
14// ============================================================================
15// Timestamp Generator
16// ============================================================================
17
18/// Generator for Unix timestamps (seconds since epoch)
19///
20/// Generates i64 timestamps that can be converted to SystemTime or used with datetime libraries
21#[derive(Debug, Clone)]
22pub struct TimestampGenerator {
23    min: i64,
24    max: i64,
25}
26
27impl TimestampGenerator {
28    /// Create a new timestamp generator with bounds
29    pub fn new(min: i64, max: i64) -> Self {
30        Self { min, max }
31    }
32
33    /// Create a timestamp generator for recent dates (last 10 years to now)
34    pub fn recent() -> Self {
35        let now = SystemTime::now()
36            .duration_since(UNIX_EPOCH)
37            .unwrap()
38            .as_secs() as i64;
39        let ten_years_ago = now - (10 * 365 * 24 * 60 * 60);
40        Self {
41            min: ten_years_ago,
42            max: now,
43        }
44    }
45
46    /// Create a timestamp generator for future dates (now to 10 years from now)
47    pub fn future() -> Self {
48        let now = SystemTime::now()
49            .duration_since(UNIX_EPOCH)
50            .unwrap()
51            .as_secs() as i64;
52        let ten_years_later = now + (10 * 365 * 24 * 60 * 60);
53        Self {
54            min: now,
55            max: ten_years_later,
56        }
57    }
58}
59
60impl Generator<i64> for TimestampGenerator {
61    fn generate(&self, rng: &mut dyn rand::RngCore, _config: &GeneratorConfig) -> i64 {
62        rng.r#gen_range(self.min..=self.max)
63    }
64
65    fn shrink(&self, value: &i64) -> Box<dyn Iterator<Item = i64>> {
66        let mut shrinks = Vec::new();
67
68        // Try epoch (0)
69        if *value > 0 && self.min <= 0 {
70            shrinks.push(0);
71        }
72
73        // Try min
74        if *value > self.min {
75            shrinks.push(self.min);
76        }
77
78        // Try half
79        if *value > self.min {
80            let half = (*value + self.min) / 2;
81            if half >= self.min {
82                shrinks.push(half);
83            }
84        }
85
86        Box::new(shrinks.into_iter())
87    }
88}
89
90// ============================================================================
91// Duration Generator
92// ============================================================================
93
94/// Generator for Duration values
95#[derive(Debug, Clone)]
96pub struct DurationGenerator {
97    min_secs: u64,
98    max_secs: u64,
99}
100
101impl DurationGenerator {
102    /// Create a new duration generator with bounds in seconds
103    pub fn new(min_secs: u64, max_secs: u64) -> Self {
104        Self { min_secs, max_secs }
105    }
106
107    /// Create a duration generator for milliseconds (0-1000ms)
108    pub fn milliseconds() -> Self {
109        Self {
110            min_secs: 0,
111            max_secs: 1,
112        }
113    }
114
115    /// Create a duration generator for seconds (0-60s)
116    pub fn seconds() -> Self {
117        Self {
118            min_secs: 0,
119            max_secs: 60,
120        }
121    }
122
123    /// Create a duration generator for minutes (0-60 minutes)
124    pub fn minutes() -> Self {
125        Self {
126            min_secs: 0,
127            max_secs: 60 * 60,
128        }
129    }
130
131    /// Create a duration generator for hours (0-24 hours)
132    pub fn hours() -> Self {
133        Self {
134            min_secs: 0,
135            max_secs: 24 * 60 * 60,
136        }
137    }
138}
139
140impl Generator<Duration> for DurationGenerator {
141    fn generate(&self, rng: &mut dyn rand::RngCore, _config: &GeneratorConfig) -> Duration {
142        let secs = rng.r#gen_range(self.min_secs..=self.max_secs);
143        let nanos = rng.r#gen_range(0..1_000_000_000);
144        Duration::new(secs, nanos)
145    }
146
147    fn shrink(&self, value: &Duration) -> Box<dyn Iterator<Item = Duration>> {
148        let mut shrinks = Vec::new();
149
150        // Try zero
151        if value.as_secs() > 0 || value.subsec_nanos() > 0 {
152            shrinks.push(Duration::ZERO);
153        }
154
155        // Try min duration
156        let min_dur = Duration::from_secs(self.min_secs);
157        if *value > min_dur {
158            shrinks.push(min_dur);
159        }
160
161        // Try half the duration
162        if value.as_secs() > 0 {
163            shrinks.push(Duration::from_secs(value.as_secs() / 2));
164        }
165
166        // Try removing subsec nanos
167        if value.subsec_nanos() > 0 {
168            shrinks.push(Duration::from_secs(value.as_secs()));
169        }
170
171        Box::new(shrinks.into_iter())
172    }
173}
174
175// ============================================================================
176// SystemTime Generator
177// ============================================================================
178
179/// Generator for SystemTime values
180#[derive(Debug, Clone)]
181pub struct SystemTimeGenerator {
182    start: SystemTime,
183    max_offset_secs: u64,
184}
185
186impl SystemTimeGenerator {
187    /// Create a new SystemTime generator
188    ///
189    /// Generates times from `start` to `start + max_offset_secs`
190    pub fn new(start: SystemTime, max_offset_secs: u64) -> Self {
191        Self {
192            start,
193            max_offset_secs,
194        }
195    }
196
197    /// Create a generator for times around now (± 1 year)
198    pub fn around_now() -> Self {
199        let now = SystemTime::now();
200        let one_year = 365 * 24 * 60 * 60;
201
202        // Start from 1 year ago
203        let start = now - Duration::from_secs(one_year);
204
205        Self {
206            start,
207            max_offset_secs: one_year * 2, // ±1 year = 2 years total
208        }
209    }
210
211    /// Create a generator starting from Unix epoch
212    pub fn from_epoch(max_offset_secs: u64) -> Self {
213        Self {
214            start: UNIX_EPOCH,
215            max_offset_secs,
216        }
217    }
218}
219
220impl Generator<SystemTime> for SystemTimeGenerator {
221    fn generate(&self, rng: &mut dyn rand::RngCore, _config: &GeneratorConfig) -> SystemTime {
222        let offset_secs = rng.r#gen_range(0..=self.max_offset_secs);
223        self.start + Duration::from_secs(offset_secs)
224    }
225
226    fn shrink(&self, value: &SystemTime) -> Box<dyn Iterator<Item = SystemTime>> {
227        let mut shrinks = Vec::new();
228
229        // Try start time
230        if *value > self.start {
231            shrinks.push(self.start);
232        }
233
234        // Try halfway between start and value
235        if let Ok(duration_since_start) = value.duration_since(self.start) {
236            let half_secs = duration_since_start.as_secs() / 2;
237            if half_secs > 0 {
238                shrinks.push(self.start + Duration::from_secs(half_secs));
239            }
240        }
241
242        Box::new(shrinks.into_iter())
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use rand::thread_rng;
250
251    #[test]
252    fn test_timestamp_generator() {
253        let generator = TimestampGenerator::new(0, 1_000_000);
254        let mut rng = thread_rng();
255        let config = GeneratorConfig::default();
256
257        for _ in 0..10 {
258            let ts = generator.generate(&mut rng, &config);
259            assert!((0..=1_000_000).contains(&ts));
260        }
261    }
262
263    #[test]
264    fn test_timestamp_recent() {
265        let generator = TimestampGenerator::recent();
266        let mut rng = thread_rng();
267        let config = GeneratorConfig::default();
268
269        let now = SystemTime::now()
270            .duration_since(UNIX_EPOCH)
271            .unwrap()
272            .as_secs() as i64;
273
274        for _ in 0..10 {
275            let ts = generator.generate(&mut rng, &config);
276            assert!(ts <= now);
277            assert!(ts >= now - (10 * 365 * 24 * 60 * 60));
278        }
279    }
280
281    #[test]
282    fn test_duration_generator() {
283        let generator = DurationGenerator::new(0, 100);
284        let mut rng = thread_rng();
285        let config = GeneratorConfig::default();
286
287        for _ in 0..10 {
288            let dur = generator.generate(&mut rng, &config);
289            assert!(dur.as_secs() <= 100);
290        }
291    }
292
293    #[test]
294    fn test_duration_presets() {
295        let mut rng = thread_rng();
296        let config = GeneratorConfig::default();
297
298        let generator = DurationGenerator::seconds();
299        let dur = generator.generate(&mut rng, &config);
300        assert!(dur.as_secs() <= 60);
301
302        let generator = DurationGenerator::minutes();
303        let dur = generator.generate(&mut rng, &config);
304        assert!(dur.as_secs() <= 3600);
305    }
306
307    #[test]
308    fn test_system_time_generator() {
309        let start = UNIX_EPOCH;
310        let generator = SystemTimeGenerator::new(start, 1_000_000);
311        let mut rng = thread_rng();
312        let config = GeneratorConfig::default();
313
314        for _ in 0..10 {
315            let time = generator.generate(&mut rng, &config);
316            assert!(time >= start);
317
318            let duration = time.duration_since(start).unwrap();
319            assert!(duration.as_secs() <= 1_000_000);
320        }
321    }
322
323    #[test]
324    fn test_system_time_around_now() {
325        let generator = SystemTimeGenerator::around_now();
326        let mut rng = thread_rng();
327        let config = GeneratorConfig::default();
328
329        let now = SystemTime::now();
330
331        for _ in 0..10 {
332            let time = generator.generate(&mut rng, &config);
333
334            // Should be within reasonable range of now
335            // (allowing for test execution time)
336            if let Ok(duration) = now.duration_since(time) {
337                assert!(duration.as_secs() <= 365 * 24 * 60 * 60 + 10);
338            } else if let Ok(duration) = time.duration_since(now) {
339                assert!(duration.as_secs() <= 365 * 24 * 60 * 60 + 10);
340            }
341        }
342    }
343}