1use chrono::{DateTime, Duration, Utc};
11use once_cell::sync::Lazy;
12use serde::{Deserialize, Serialize};
13use std::collections::{BTreeMap, HashMap};
14use std::sync::{Arc, RwLock};
15use tracing::{debug, info, warn};
16
17pub type TimeChangeCallback = Arc<dyn Fn(DateTime<Utc>, DateTime<Utc>) + Send + Sync>;
19
20#[derive(Clone)]
22pub struct VirtualClock {
23 current_time: Arc<RwLock<Option<DateTime<Utc>>>>,
25 enabled: Arc<RwLock<bool>>,
27 scale_factor: Arc<RwLock<f64>>,
29 baseline_real_time: Arc<RwLock<Option<DateTime<Utc>>>>,
31 #[cfg_attr(not(test), allow(dead_code))]
33 time_change_callbacks: Arc<RwLock<Vec<TimeChangeCallback>>>,
34}
35
36impl std::fmt::Debug for VirtualClock {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("VirtualClock")
39 .field(
40 "current_time",
41 &*self.current_time.read().unwrap_or_else(|poisoned| poisoned.into_inner()),
42 )
43 .field(
44 "enabled",
45 &*self.enabled.read().unwrap_or_else(|poisoned| poisoned.into_inner()),
46 )
47 .field(
48 "scale_factor",
49 &*self.scale_factor.read().unwrap_or_else(|poisoned| poisoned.into_inner()),
50 )
51 .field(
52 "baseline_real_time",
53 &*self.baseline_real_time.read().unwrap_or_else(|poisoned| poisoned.into_inner()),
54 )
55 .field(
56 "callback_count",
57 &self
58 .time_change_callbacks
59 .read()
60 .unwrap_or_else(|poisoned| poisoned.into_inner())
61 .len(),
62 )
63 .finish()
64 }
65}
66
67impl Default for VirtualClock {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73impl VirtualClock {
74 pub fn new() -> Self {
76 Self {
77 current_time: Arc::new(RwLock::new(None)),
78 enabled: Arc::new(RwLock::new(false)),
79 scale_factor: Arc::new(RwLock::new(1.0)),
80 baseline_real_time: Arc::new(RwLock::new(None)),
81 time_change_callbacks: Arc::new(RwLock::new(Vec::new())),
82 }
83 }
84
85 pub fn on_time_change<F>(&self, callback: F)
89 where
90 F: Fn(DateTime<Utc>, DateTime<Utc>) + Send + Sync + 'static,
91 {
92 let mut callbacks = self
93 .time_change_callbacks
94 .write()
95 .unwrap_or_else(|poisoned| poisoned.into_inner());
96 callbacks.push(Arc::new(callback));
97 }
98
99 fn invoke_time_change_callbacks(&self, old_time: DateTime<Utc>, new_time: DateTime<Utc>) {
101 let callbacks = self
102 .time_change_callbacks
103 .read()
104 .unwrap_or_else(|poisoned| poisoned.into_inner());
105 for callback in callbacks.iter() {
106 callback(old_time, new_time);
107 }
108 }
109
110 pub fn new_at(time: DateTime<Utc>) -> Self {
112 let clock = Self::new();
113 clock.enable_and_set(time);
114 clock
115 }
116
117 pub fn enable_and_set(&self, time: DateTime<Utc>) {
119 let mut current =
120 self.current_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
121 *current = Some(time);
122
123 let mut enabled = self.enabled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
124 *enabled = true;
125
126 let mut baseline =
127 self.baseline_real_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
128 *baseline = Some(Utc::now());
129
130 info!("Time travel enabled at {}", time);
131 }
132
133 pub fn disable(&self) {
135 let mut enabled = self.enabled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
136 *enabled = false;
137
138 let mut current =
139 self.current_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
140 *current = None;
141
142 let mut baseline =
143 self.baseline_real_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
144 *baseline = None;
145
146 info!("Time travel disabled, using real time");
147 }
148
149 pub fn is_enabled(&self) -> bool {
151 *self.enabled.read().unwrap_or_else(|poisoned| poisoned.into_inner())
152 }
153
154 pub fn now(&self) -> DateTime<Utc> {
156 let enabled = *self.enabled.read().unwrap_or_else(|poisoned| poisoned.into_inner());
157
158 if !enabled {
159 return Utc::now();
160 }
161
162 let current = self.current_time.read().unwrap_or_else(|poisoned| poisoned.into_inner());
163 let scale = *self.scale_factor.read().unwrap_or_else(|poisoned| poisoned.into_inner());
164
165 if let Some(virtual_time) = *current {
166 if (scale - 1.0).abs() < f64::EPSILON {
168 return virtual_time;
169 }
170
171 let baseline =
173 self.baseline_real_time.read().unwrap_or_else(|poisoned| poisoned.into_inner());
174 if let Some(baseline_real) = *baseline {
175 let elapsed_real = Utc::now() - baseline_real;
176 let elapsed_scaled =
177 Duration::milliseconds((elapsed_real.num_milliseconds() as f64 * scale) as i64);
178 return virtual_time + elapsed_scaled;
179 }
180
181 virtual_time
182 } else {
183 Utc::now()
184 }
185 }
186
187 pub fn advance(&self, duration: Duration) {
189 let enabled = *self.enabled.read().unwrap_or_else(|poisoned| poisoned.into_inner());
190 if !enabled {
191 warn!("Cannot advance time: time travel is not enabled");
192 return;
193 }
194
195 let mut current =
196 self.current_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
197 if let Some(time) = *current {
198 let old_time = time;
199 let new_time = time + duration;
200 *current = Some(new_time);
201
202 let mut baseline =
204 self.baseline_real_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
205 *baseline = Some(Utc::now());
206
207 drop(current);
209 drop(baseline);
210 self.invoke_time_change_callbacks(old_time, new_time);
211
212 info!("Time advanced by {} to {}", duration, new_time);
213 }
214 }
215
216 pub fn set_scale(&self, factor: f64) {
218 if factor <= 0.0 {
219 warn!("Invalid scale factor: {}, must be positive", factor);
220 return;
221 }
222
223 let mut scale = self.scale_factor.write().unwrap_or_else(|poisoned| poisoned.into_inner());
224 *scale = factor;
225
226 let mut baseline =
228 self.baseline_real_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
229 *baseline = Some(Utc::now());
230
231 info!("Time scale set to {}x", factor);
232 }
233
234 pub fn get_scale(&self) -> f64 {
236 *self.scale_factor.read().unwrap_or_else(|poisoned| poisoned.into_inner())
237 }
238
239 pub fn reset(&self) {
241 self.disable();
242 info!("Time travel reset to real time");
243 }
244
245 pub fn set_time(&self, time: DateTime<Utc>) {
247 let enabled = *self.enabled.read().unwrap_or_else(|poisoned| poisoned.into_inner());
248 if !enabled {
249 self.enable_and_set(time);
250 return;
251 }
252
253 let mut current =
254 self.current_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
255 let old_time = *current;
256 *current = Some(time);
257
258 let mut baseline =
260 self.baseline_real_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
261 *baseline = Some(Utc::now());
262
263 if let Some(old) = old_time {
265 drop(current);
266 drop(baseline);
267 self.invoke_time_change_callbacks(old, time);
268 }
269
270 info!("Virtual time set to {}", time);
271 }
272
273 pub fn status(&self) -> TimeTravelStatus {
275 TimeTravelStatus {
276 enabled: self.is_enabled(),
277 current_time: if self.is_enabled() {
278 Some(self.now())
279 } else {
280 None
281 },
282 scale_factor: self.get_scale(),
283 real_time: Utc::now(),
284 }
285 }
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct TimeTravelStatus {
291 pub enabled: bool,
293 pub current_time: Option<DateTime<Utc>>,
295 pub scale_factor: f64,
297 pub real_time: DateTime<Utc>,
299}
300
301#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct TimeTravelConfig {
305 #[serde(default)]
307 pub enabled: bool,
308 #[cfg_attr(feature = "schema", schemars(with = "Option<String>"))]
310 pub initial_time: Option<DateTime<Utc>>,
311 #[serde(default = "default_scale")]
313 pub scale_factor: f64,
314 #[serde(default = "default_true")]
316 pub enable_scheduling: bool,
317}
318
319fn default_scale() -> f64 {
320 1.0
321}
322
323fn default_true() -> bool {
324 true
325}
326
327impl Default for TimeTravelConfig {
328 fn default() -> Self {
329 Self {
330 enabled: false,
331 initial_time: None,
332 scale_factor: 1.0,
333 enable_scheduling: true,
334 }
335 }
336}
337
338#[derive(Debug, Clone)]
340pub struct ResponseScheduler {
341 clock: Arc<VirtualClock>,
343 scheduled: Arc<RwLock<BTreeMap<DateTime<Utc>, Vec<ScheduledResponse>>>>,
345 named_schedules: Arc<RwLock<HashMap<String, String>>>,
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct ScheduledResponse {
352 pub id: String,
354 pub trigger_time: DateTime<Utc>,
356 pub body: serde_json::Value,
358 #[serde(default = "default_status")]
360 pub status: u16,
361 #[serde(default)]
363 pub headers: HashMap<String, String>,
364 pub name: Option<String>,
366 #[serde(default)]
368 pub repeat: Option<RepeatConfig>,
369}
370
371fn default_status() -> u16 {
372 200
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct RepeatConfig {
378 pub interval: Duration,
380 pub max_count: Option<usize>,
382}
383
384impl ResponseScheduler {
385 pub fn new(clock: Arc<VirtualClock>) -> Self {
387 Self {
388 clock,
389 scheduled: Arc::new(RwLock::new(BTreeMap::new())),
390 named_schedules: Arc::new(RwLock::new(HashMap::new())),
391 }
392 }
393
394 pub fn schedule(&self, response: ScheduledResponse) -> Result<String, String> {
396 let id = if response.id.is_empty() {
397 uuid::Uuid::new_v4().to_string()
398 } else {
399 response.id.clone()
400 };
401
402 let mut scheduled = self.scheduled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
403 scheduled.entry(response.trigger_time).or_default().push(response.clone());
404
405 if let Some(name) = &response.name {
406 let mut named =
407 self.named_schedules.write().unwrap_or_else(|poisoned| poisoned.into_inner());
408 named.insert(name.clone(), id.clone());
409 }
410
411 info!("Scheduled response {} for {}", id, response.trigger_time);
412 Ok(id)
413 }
414
415 pub fn get_due_responses(&self) -> Vec<ScheduledResponse> {
417 let now = self.clock.now();
418 let mut scheduled = self.scheduled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
419 let mut due = Vec::new();
420
421 let times_to_process: Vec<DateTime<Utc>> =
423 scheduled.range(..=now).map(|(time, _)| *time).collect();
424
425 for time in times_to_process {
426 if let Some(responses) = scheduled.remove(&time) {
427 for response in responses {
428 due.push(response.clone());
429
430 if let Some(repeat_config) = &response.repeat {
432 let next_time = time + repeat_config.interval;
433
434 let should_repeat = if let Some(max) = repeat_config.max_count {
436 max > 1
438 } else {
439 true
440 };
441
442 if should_repeat {
443 let mut next_response = response.clone();
444 next_response.trigger_time = next_time;
445 if let Some(ref mut repeat) = next_response.repeat {
446 if let Some(ref mut count) = repeat.max_count {
447 *count -= 1;
448 }
449 }
450
451 scheduled.entry(next_time).or_default().push(next_response);
452 }
453 }
454 }
455 }
456 }
457
458 debug!("Found {} due responses at {}", due.len(), now);
459 due
460 }
461
462 pub fn cancel(&self, id: &str) -> bool {
464 let mut scheduled = self.scheduled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
465
466 for responses in scheduled.values_mut() {
467 if let Some(pos) = responses.iter().position(|r| r.id == id) {
468 responses.remove(pos);
469 info!("Cancelled scheduled response {}", id);
470 return true;
471 }
472 }
473
474 false
475 }
476
477 pub fn clear_all(&self) {
479 let mut scheduled = self.scheduled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
480 scheduled.clear();
481
482 let mut named =
483 self.named_schedules.write().unwrap_or_else(|poisoned| poisoned.into_inner());
484 named.clear();
485
486 info!("Cleared all scheduled responses");
487 }
488
489 pub fn list_scheduled(&self) -> Vec<ScheduledResponse> {
491 let scheduled = self.scheduled.read().unwrap_or_else(|poisoned| poisoned.into_inner());
492 scheduled.values().flat_map(|v| v.iter().cloned()).collect()
493 }
494
495 pub fn count(&self) -> usize {
497 let scheduled = self.scheduled.read().unwrap_or_else(|poisoned| poisoned.into_inner());
498 scheduled.values().map(|v| v.len()).sum()
499 }
500}
501
502#[derive(Debug, Clone, Serialize, Deserialize)]
504pub struct TimeScenario {
505 pub name: String,
507 pub enabled: bool,
509 pub current_time: Option<DateTime<Utc>>,
511 pub scale_factor: f64,
513 #[serde(default)]
515 pub scheduled_responses: Vec<ScheduledResponse>,
516 pub created_at: DateTime<Utc>,
518 #[serde(default)]
520 pub description: Option<String>,
521}
522
523impl TimeScenario {
524 pub fn from_manager(manager: &TimeTravelManager, name: String) -> Self {
526 let status = manager.clock().status();
527 let scheduled = manager.scheduler().list_scheduled();
528
529 Self {
530 name,
531 enabled: status.enabled,
532 current_time: status.current_time,
533 scale_factor: status.scale_factor,
534 scheduled_responses: scheduled,
535 created_at: Utc::now(),
536 description: None,
537 }
538 }
539
540 pub fn apply_to_manager(&self, manager: &TimeTravelManager) {
542 if self.enabled {
543 if let Some(time) = self.current_time {
544 manager.enable_and_set(time);
545 } else {
546 manager.enable_and_set(Utc::now());
547 }
548 manager.set_scale(self.scale_factor);
549 } else {
550 manager.disable();
551 }
552
553 manager.scheduler().clear_all();
555 for response in &self.scheduled_responses {
556 let _ = manager.scheduler().schedule(response.clone());
557 }
558 }
559}
560
561static GLOBAL_CLOCK_REGISTRY: Lazy<Arc<RwLock<Option<Arc<VirtualClock>>>>> =
568 Lazy::new(|| Arc::new(RwLock::new(None)));
569
570pub fn register_global_clock(clock: Arc<VirtualClock>) {
575 let mut registry =
576 GLOBAL_CLOCK_REGISTRY.write().unwrap_or_else(|poisoned| poisoned.into_inner());
577 *registry = Some(clock);
578 info!("Virtual clock registered globally");
579 let _ = mockforge_foundation::clock::set_clock(now);
582}
583
584pub fn unregister_global_clock() {
588 let mut registry =
589 GLOBAL_CLOCK_REGISTRY.write().unwrap_or_else(|poisoned| poisoned.into_inner());
590 *registry = None;
591 info!("Virtual clock unregistered globally");
592}
593
594pub fn get_global_clock() -> Option<Arc<VirtualClock>> {
598 let registry = GLOBAL_CLOCK_REGISTRY.read().unwrap_or_else(|poisoned| poisoned.into_inner());
599 registry.clone()
600}
601
602pub fn now() -> DateTime<Utc> {
609 if let Some(clock) = get_global_clock() {
610 clock.now()
611 } else {
612 Utc::now()
613 }
614}
615
616pub fn is_time_travel_enabled() -> bool {
620 if let Some(clock) = get_global_clock() {
621 clock.is_enabled()
622 } else {
623 false
624 }
625}
626
627pub struct TimeTravelManager {
629 clock: Arc<VirtualClock>,
631 scheduler: Arc<ResponseScheduler>,
633 cron_scheduler: Arc<CronScheduler>,
635}
636
637impl TimeTravelManager {
638 pub fn new(config: TimeTravelConfig) -> Self {
643 let clock = Arc::new(VirtualClock::new());
644
645 if config.enabled {
646 if let Some(initial_time) = config.initial_time {
647 clock.enable_and_set(initial_time);
648 } else {
649 clock.enable_and_set(Utc::now());
650 }
651 clock.set_scale(config.scale_factor);
652 register_global_clock(clock.clone());
654 }
655
656 let scheduler = Arc::new(ResponseScheduler::new(clock.clone()));
657 let cron_scheduler =
658 Arc::new(CronScheduler::new(clock.clone()).with_response_scheduler(scheduler.clone()));
659
660 Self {
661 clock,
662 scheduler,
663 cron_scheduler,
664 }
665 }
666
667 pub fn clock(&self) -> Arc<VirtualClock> {
669 self.clock.clone()
670 }
671
672 pub fn scheduler(&self) -> Arc<ResponseScheduler> {
674 self.scheduler.clone()
675 }
676
677 pub fn cron_scheduler(&self) -> Arc<CronScheduler> {
679 self.cron_scheduler.clone()
680 }
681
682 pub fn now(&self) -> DateTime<Utc> {
684 self.clock.now()
685 }
686
687 pub fn save_scenario(&self, name: String) -> TimeScenario {
689 TimeScenario::from_manager(self, name)
690 }
691
692 pub fn load_scenario(&self, scenario: &TimeScenario) {
694 scenario.apply_to_manager(self);
695 }
696
697 pub fn enable_and_set(&self, time: DateTime<Utc>) {
701 self.clock.enable_and_set(time);
702 register_global_clock(self.clock.clone());
703 }
704
705 pub fn disable(&self) {
709 self.clock.disable();
710 unregister_global_clock();
711 }
712
713 pub fn advance(&self, duration: Duration) {
717 self.clock.advance(duration);
718 }
719
720 pub fn set_time(&self, time: DateTime<Utc>) {
724 self.clock.set_time(time);
725 if self.clock.is_enabled() {
726 register_global_clock(self.clock.clone());
727 }
728 }
729
730 pub fn set_scale(&self, factor: f64) {
734 self.clock.set_scale(factor);
735 }
736}
737
738impl Drop for TimeTravelManager {
739 fn drop(&mut self) {
740 unregister_global_clock();
742 }
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748
749 #[test]
750 fn test_virtual_clock_creation() {
751 let clock = VirtualClock::new();
752 assert!(!clock.is_enabled());
753 }
754
755 #[test]
756 fn test_virtual_clock_enable() {
757 let clock = VirtualClock::new();
758 let test_time = Utc::now();
759 clock.enable_and_set(test_time);
760
761 assert!(clock.is_enabled());
762 let now = clock.now();
763 assert!((now - test_time).num_seconds().abs() < 1);
764 }
765
766 #[test]
767 fn test_virtual_clock_advance() {
768 let clock = VirtualClock::new();
769 let test_time = Utc::now();
770 clock.enable_and_set(test_time);
771
772 clock.advance(Duration::hours(2));
773 let now = clock.now();
774
775 assert!((now - test_time - Duration::hours(2)).num_seconds().abs() < 1);
776 }
777
778 #[test]
779 fn test_virtual_clock_scale() {
780 let clock = VirtualClock::new();
781 clock.set_scale(2.0);
782 assert_eq!(clock.get_scale(), 2.0);
783 }
784
785 #[test]
786 fn test_response_scheduler() {
787 let clock = Arc::new(VirtualClock::new());
788 let test_time = Utc::now();
789 clock.enable_and_set(test_time);
790
791 let scheduler = ResponseScheduler::new(clock.clone());
792
793 let response = ScheduledResponse {
794 id: "test-1".to_string(),
795 trigger_time: test_time + Duration::seconds(10),
796 body: serde_json::json!({"message": "Hello"}),
797 status: 200,
798 headers: HashMap::new(),
799 name: Some("test".to_string()),
800 repeat: None,
801 };
802
803 let id = scheduler.schedule(response).unwrap();
804 assert_eq!(id, "test-1");
805 assert_eq!(scheduler.count(), 1);
806 }
807
808 #[test]
809 fn test_scheduled_response_triggering() {
810 let clock = Arc::new(VirtualClock::new());
811 let test_time = Utc::now();
812 clock.enable_and_set(test_time);
813
814 let scheduler = ResponseScheduler::new(clock.clone());
815
816 let response = ScheduledResponse {
817 id: "test-1".to_string(),
818 trigger_time: test_time + Duration::seconds(10),
819 body: serde_json::json!({"message": "Hello"}),
820 status: 200,
821 headers: HashMap::new(),
822 name: None,
823 repeat: None,
824 };
825
826 scheduler.schedule(response).unwrap();
827
828 let due = scheduler.get_due_responses();
830 assert_eq!(due.len(), 0);
831
832 clock.advance(Duration::seconds(15));
834
835 let due = scheduler.get_due_responses();
837 assert_eq!(due.len(), 1);
838 }
839
840 #[test]
841 fn test_time_travel_config() {
842 let config = TimeTravelConfig::default();
843 assert!(!config.enabled);
844 assert_eq!(config.scale_factor, 1.0);
845 assert!(config.enable_scheduling);
846 }
847
848 #[test]
849 fn test_time_travel_manager() {
850 let config = TimeTravelConfig {
851 enabled: true,
852 initial_time: Some(Utc::now()),
853 scale_factor: 1.0,
854 enable_scheduling: true,
855 };
856
857 let manager = TimeTravelManager::new(config);
858 assert!(manager.clock().is_enabled());
859 }
860
861 #[test]
862 fn test_one_month_later_scenario() {
863 let clock = Arc::new(VirtualClock::new());
864 let initial_time = Utc::now();
865 clock.enable_and_set(initial_time);
866
867 clock.advance(Duration::days(30));
869
870 let final_time = clock.now();
871 let elapsed = final_time - initial_time;
872
873 assert!(elapsed.num_days() >= 29 && elapsed.num_days() <= 31);
875 }
876
877 #[test]
878 fn test_scenario_save_and_load() {
879 let config = TimeTravelConfig {
880 enabled: true,
881 initial_time: Some(Utc::now()),
882 scale_factor: 2.0,
883 enable_scheduling: true,
884 };
885
886 let manager = TimeTravelManager::new(config);
887
888 manager.clock().advance(Duration::hours(24));
890
891 let scenario = manager.save_scenario("test-scenario".to_string());
893 assert_eq!(scenario.name, "test-scenario");
894 assert!(scenario.enabled);
895 assert_eq!(scenario.scale_factor, 2.0);
896 assert!(scenario.current_time.is_some());
897
898 let new_config = TimeTravelConfig::default();
900 let new_manager = TimeTravelManager::new(new_config);
901
902 new_manager.load_scenario(&scenario);
904
905 assert!(new_manager.clock().is_enabled());
907 assert_eq!(new_manager.clock().get_scale(), 2.0);
908 if let Some(saved_time) = scenario.current_time {
909 let loaded_time = new_manager.clock().now();
910 assert!((loaded_time - saved_time).num_seconds().abs() < 1);
912 }
913 }
914
915 #[test]
916 fn test_duration_parsing_month_year() {
917 let clock = Arc::new(VirtualClock::new());
919 let initial_time = Utc::now();
920 clock.enable_and_set(initial_time);
921
922 clock.advance(Duration::days(30));
924 let after_month = clock.now();
925 let month_elapsed = after_month - initial_time;
926 assert!(month_elapsed.num_days() >= 29 && month_elapsed.num_days() <= 31);
927
928 clock.set_time(initial_time);
930 clock.advance(Duration::days(365));
931 let after_year = clock.now();
932 let year_elapsed = after_year - initial_time;
933 assert!(year_elapsed.num_days() >= 364 && year_elapsed.num_days() <= 366);
934 }
935}
936
937pub mod cron;
939
940pub use cron::{CronJob, CronJobAction, CronScheduler};