1use crate::scheduling::{
2 get_unadjusteds, Adjuster, Adjustment, Calendar, Frequency, RollDay, Scheduling,
3};
4use chrono::prelude::*;
5use itertools::iproduct;
6use pyo3::exceptions::PyValueError;
7use pyo3::{pyclass, PyErr};
8use serde::{Deserialize, Serialize};
9
10#[pyclass(module = "rateslib.rs", eq, eq_int)]
12#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub enum StubInference {
14 ShortFront = 0,
16 LongFront = 1,
18 ShortBack = 2,
20 LongBack = 3,
22}
23
24#[pyclass(module = "rateslib.rs", eq)]
33#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
34#[serde(from = "ScheduleDataModel")]
35pub struct Schedule {
36 pub ueffective: NaiveDateTime,
38 pub utermination: NaiveDateTime,
40 pub frequency: Frequency,
42 pub ufront_stub: Option<NaiveDateTime>,
44 pub uback_stub: Option<NaiveDateTime>,
46 pub calendar: Calendar,
48 pub accrual_adjuster: Adjuster,
50 pub payment_adjuster: Adjuster,
52 #[serde(skip)]
54 pub uschedule: Vec<NaiveDateTime>,
55 #[serde(skip)]
57 pub aschedule: Vec<NaiveDateTime>,
58 #[serde(skip)]
60 pub pschedule: Vec<NaiveDateTime>,
61}
62
63#[derive(Deserialize)]
64struct ScheduleDataModel {
65 ueffective: NaiveDateTime,
66 utermination: NaiveDateTime,
67 frequency: Frequency,
68 ufront_stub: Option<NaiveDateTime>,
69 uback_stub: Option<NaiveDateTime>,
70 calendar: Calendar,
71 accrual_adjuster: Adjuster,
72 payment_adjuster: Adjuster,
73}
74
75impl std::convert::From<ScheduleDataModel> for Schedule {
76 fn from(model: ScheduleDataModel) -> Self {
77 Self::try_new_defined(
78 model.ueffective,
79 model.utermination,
80 model.frequency,
81 model.ufront_stub,
82 model.uback_stub,
83 model.calendar,
84 model.accrual_adjuster,
85 model.payment_adjuster,
86 )
87 .expect("Data model for `Schedule` is corrupt or invalid.")
88 }
89}
90
91fn validate_individual_dates(
93 left: &Option<NaiveDateTime>,
94 right: &Option<NaiveDateTime>,
95 accrual_adjuster: &Adjuster,
96 calendar: &Calendar,
97) -> Result<(), PyErr> {
98 match (left, right) {
99 (Some(_left), Some(_right)) => {}
100 _ => return Ok(()),
101 }
102 if left >= right {
103 return Err(PyValueError::new_err(
104 "Dates are invalid since they are repeated.",
105 ));
106 }
107 if accrual_adjuster.adjust(&left.unwrap(), calendar)
108 >= accrual_adjuster.adjust(&right.unwrap(), calendar)
109 {
110 return Err(PyValueError::new_err(
111 "Dates define dead stubs and are invalid",
112 ));
113 }
114 Ok(())
115}
116
117fn validate_date_ordering(
120 ueffective: &NaiveDateTime,
121 ufront_stub: &Option<NaiveDateTime>,
122 uback_stub: &Option<NaiveDateTime>,
123 utermination: &NaiveDateTime,
124 accrual_adjuster: &Adjuster,
125 calendar: &Calendar,
126) -> Result<(), PyErr> {
127 let _ = validate_individual_dates(&Some(*ueffective), ufront_stub, accrual_adjuster, calendar)?;
128 let _ = validate_individual_dates(&Some(*ueffective), uback_stub, accrual_adjuster, calendar)?;
129 let _ = validate_individual_dates(
130 &Some(*ueffective),
131 &Some(*utermination),
132 accrual_adjuster,
133 calendar,
134 )?;
135 let _ = validate_individual_dates(
138 ufront_stub,
139 &Some(*utermination),
140 accrual_adjuster,
141 calendar,
142 )?;
143 let _ =
144 validate_individual_dates(uback_stub, &Some(*utermination), accrual_adjuster, calendar)?;
145 Ok(())
146}
147
148fn validate_is_stub(
150 left: &NaiveDateTime,
151 right: &NaiveDateTime,
152 frequency: &Frequency,
153 front: bool,
154) -> Result<(), PyErr> {
155 if front {
156 if frequency.is_front_stub(left, right) {
157 Ok(())
158 } else {
159 Err(PyValueError::new_err(
160 "Dates intended to define a front stub do not permit a valid stub period.",
161 ))
162 }
163 } else {
164 if frequency.is_back_stub(left, right) {
165 Ok(())
166 } else {
167 Err(PyValueError::new_err(
168 "Dates intended to define a back stub do not permit a valid stub period.",
169 ))
170 }
171 }
172}
173
174impl Schedule {
175 pub fn try_new_defined(
212 ueffective: NaiveDateTime,
213 utermination: NaiveDateTime,
214 frequency: Frequency,
215 ufront_stub: Option<NaiveDateTime>,
216 uback_stub: Option<NaiveDateTime>,
217 calendar: Calendar,
218 accrual_adjuster: Adjuster,
219 payment_adjuster: Adjuster,
220 ) -> Result<Self, PyErr> {
221 let _ = validate_date_ordering(
223 &ueffective,
224 &ufront_stub,
225 &uback_stub,
226 &utermination,
227 &accrual_adjuster,
228 &calendar,
229 )?;
230
231 let uschedule: Vec<NaiveDateTime>;
232
233 match (ufront_stub, uback_stub) {
234 (None, None) => {
235 let uregular = frequency.try_uregular(&ueffective, &utermination);
237 if uregular.is_ok() {
238 uschedule = uregular.unwrap();
240 } else if frequency.is_front_stub(&ueffective, &utermination)
241 || frequency.is_back_stub(&ueffective, &utermination)
242 {
243 uschedule = vec![ueffective, utermination];
245 } else {
246 return Err(PyValueError::new_err("`ueffective`, `utermination` and `frequency` do not define a regular schedule or a single period stub."));
247 }
248 }
249 (Some(regular_start), None) => {
250 let uregular = frequency.try_uregular(®ular_start, &utermination)?;
252 let _ = validate_is_stub(&ueffective, ®ular_start, &frequency, true)?;
253 uschedule = composite_uschedule(
254 &ueffective,
255 &utermination,
256 &ufront_stub,
257 &uback_stub,
258 &uregular,
259 );
260 }
261 (None, Some(regular_end)) => {
262 let uregular = frequency.try_uregular(&ueffective, ®ular_end)?;
264 let _ = validate_is_stub(®ular_end, &utermination, &frequency, false)?;
265 uschedule = composite_uschedule(
266 &ueffective,
267 &utermination,
268 &ufront_stub,
269 &uback_stub,
270 &uregular,
271 );
272 }
273 (Some(regular_start), Some(regular_end)) => {
274 let _ = validate_is_stub(&ueffective, ®ular_start, &frequency, true)?;
275 let _ = validate_is_stub(®ular_end, &utermination, &frequency, false)?;
276 if regular_start == regular_end {
277 uschedule = vec![ueffective, regular_start, utermination];
280 } else {
281 let uregular = frequency.try_uregular(®ular_start, ®ular_end)?;
283 uschedule = composite_uschedule(
284 &ueffective,
285 &utermination,
286 &ufront_stub,
287 &uback_stub,
288 &uregular,
289 );
290 }
291 }
292 }
293
294 let aschedule: Vec<NaiveDateTime> = accrual_adjuster.adjusts(&uschedule, &calendar);
295 let pschedule = payment_adjuster.adjusts(&aschedule, &calendar);
296
297 Ok(Self {
298 ueffective,
299 utermination,
300 frequency,
301 ufront_stub,
302 uback_stub,
303 calendar: calendar.clone(),
304 accrual_adjuster,
305 payment_adjuster,
306 uschedule,
307 aschedule,
308 pschedule,
309 })
310 }
311
312 fn try_new_infer_stub(
322 ueffective: NaiveDateTime,
323 utermination: NaiveDateTime,
324 frequency: Frequency,
325 ufront_stub: Option<NaiveDateTime>,
326 uback_stub: Option<NaiveDateTime>,
327 calendar: Calendar,
328 accrual_adjuster: Adjuster,
329 payment_adjuster: Adjuster,
330 stub_inference: Option<StubInference>,
331 ) -> Result<Self, PyErr> {
332 let temp_schedule = Schedule::try_new_defined(
334 ueffective,
335 utermination,
336 frequency.clone(),
337 ufront_stub,
338 uback_stub,
339 calendar.clone(),
340 accrual_adjuster,
341 payment_adjuster,
342 );
343
344 let _ = validate_stub_dates_and_inference(&ufront_stub, &uback_stub, &stub_inference)?;
346
347 let stubs: (Option<NaiveDateTime>, Option<NaiveDateTime>);
348 if stub_inference.is_none() {
349 return temp_schedule;
350 } else {
351 let (interior_start, interior_end) =
352 match_interior_dates(&ueffective, &ufront_stub, &uback_stub, &utermination);
353 stubs = match stub_inference.unwrap() {
354 StubInference::ShortFront => {
355 if temp_schedule.is_ok() {
356 let test_schedule = temp_schedule.unwrap();
357 if frequency.is_short_front_stub(
358 &test_schedule.uschedule[0],
359 &test_schedule.uschedule[1],
360 ) {
361 return Ok(test_schedule);
362 } }
364 (
365 frequency.try_infer_ufront_stub(&interior_start, &interior_end, true)?,
366 uback_stub,
367 )
368 }
369 StubInference::LongFront => {
370 if temp_schedule.is_ok() {
371 let test_schedule = temp_schedule.unwrap();
372 if frequency.is_long_front_stub(
373 &test_schedule.uschedule[0],
374 &test_schedule.uschedule[1],
375 ) {
376 return Ok(test_schedule);
377 } }
379 (
380 frequency.try_infer_ufront_stub(&interior_start, &interior_end, false)?,
381 uback_stub,
382 )
383 }
384 StubInference::ShortBack => {
385 if temp_schedule.is_ok() {
386 let test_schedule = temp_schedule.unwrap();
387 let n = test_schedule.uschedule.len();
388 if frequency.is_short_back_stub(
389 &test_schedule.uschedule[n - 1],
390 &test_schedule.uschedule[n - 2],
391 ) {
392 return Ok(test_schedule);
393 } }
395 (
396 ufront_stub,
397 frequency.try_infer_uback_stub(&interior_start, &interior_end, true)?,
398 )
399 }
400 StubInference::LongBack => {
401 if temp_schedule.is_ok() {
402 let test_schedule = temp_schedule.unwrap();
403 let n = test_schedule.uschedule.len();
404 if frequency.is_short_back_stub(
405 &test_schedule.uschedule[n - 1],
406 &test_schedule.uschedule[n - 2],
407 ) {
408 return Ok(test_schedule);
409 } }
411 (
412 ufront_stub,
413 frequency.try_infer_uback_stub(&interior_start, &interior_end, false)?,
414 )
415 }
416 }
417 }
418 Self::try_new_defined(
419 ueffective,
420 utermination,
421 frequency,
422 stubs.0,
423 stubs.1,
424 calendar,
425 accrual_adjuster,
426 payment_adjuster,
427 )
428 }
429
430 fn try_new_uschedule_infer_frequency(
440 ueffective: NaiveDateTime,
441 utermination: NaiveDateTime,
442 frequency: Frequency,
443 ufront_stub: Option<NaiveDateTime>,
444 uback_stub: Option<NaiveDateTime>,
445 calendar: Calendar,
446 accrual_adjuster: Adjuster,
447 payment_adjuster: Adjuster,
448 eom: bool,
449 stub_inference: Option<StubInference>,
450 ) -> Result<Self, PyErr> {
451 let (regular_start, regular_end) =
453 match_interior_dates(&ueffective, &ufront_stub, &uback_stub, &utermination);
454
455 let frequencies = frequency.try_vec_from(&vec![regular_start, regular_end])?;
457
458 let uschedules: Vec<Schedule> = frequencies
460 .into_iter()
461 .filter_map(|f| {
462 Schedule::try_new_infer_stub(
463 ueffective,
464 utermination,
465 f,
466 ufront_stub,
467 uback_stub,
468 calendar.clone(),
469 accrual_adjuster,
470 payment_adjuster,
471 stub_inference,
472 )
473 .ok()
474 })
475 .collect();
476
477 if uschedules.len() == 0 {
479 return Err(PyValueError::new_err(
480 "No valid Schedules could be created with given `udates` combinations and `frequency`.",
481 ));
482 }
483
484 let regulars: Vec<Schedule> = uschedules
486 .iter()
487 .cloned()
488 .filter(|schedule| schedule.is_regular())
489 .collect();
490 if regulars.len() != 0 {
491 Ok(filter_schedules_by_eom(regulars, eom))
492 } else {
493 Ok(filter_schedules_by_eom(uschedules, eom))
494 }
495 }
496
497 pub fn try_new_inferred(
517 effective: NaiveDateTime,
518 termination: NaiveDateTime,
519 frequency: Frequency,
520 front_stub: Option<NaiveDateTime>,
521 back_stub: Option<NaiveDateTime>,
522 calendar: Calendar,
523 accrual_adjuster: Adjuster,
524 payment_adjuster: Adjuster,
525 eom: bool,
526 stub_inference: Option<StubInference>,
527 ) -> Result<Schedule, PyErr> {
528 let dates: (
530 Vec<NaiveDateTime>,
531 Vec<Option<NaiveDateTime>>,
532 Vec<Option<NaiveDateTime>>,
533 Vec<NaiveDateTime>,
534 ) = match (front_stub, back_stub) {
535 (None, None) => (
536 get_unadjusteds(&effective, &accrual_adjuster, &calendar),
537 vec![None],
538 vec![None],
539 get_unadjusteds(&termination, &accrual_adjuster, &calendar),
540 ),
541 (Some(d), None) => (
542 vec![effective],
543 get_unadjusteds(&d, &accrual_adjuster, &calendar)
544 .into_iter()
545 .map(Some)
546 .collect(),
547 vec![None],
548 get_unadjusteds(&termination, &accrual_adjuster, &calendar),
549 ),
550 (None, Some(d)) => (
551 get_unadjusteds(&effective, &accrual_adjuster, &calendar),
552 vec![None],
553 get_unadjusteds(&d, &accrual_adjuster, &calendar)
554 .into_iter()
555 .map(Some)
556 .collect(),
557 vec![termination],
558 ),
559 (Some(d), Some(d2)) => (
560 vec![effective],
561 get_unadjusteds(&d, &accrual_adjuster, &calendar)
562 .into_iter()
563 .map(Some)
564 .collect(),
565 get_unadjusteds(&d2, &accrual_adjuster, &calendar)
566 .into_iter()
567 .map(Some)
568 .collect(),
569 vec![termination],
570 ),
571 };
572
573 let combinations = iproduct!(dates.0, dates.1, dates.2, dates.3);
574 let schedules: Vec<Schedule> = combinations
575 .into_iter()
576 .filter_map(|(e, fs, bs, t)| {
577 Schedule::try_new_uschedule_infer_frequency(
578 e,
579 t,
580 frequency.clone(),
581 fs,
582 bs,
583 calendar.clone(),
584 accrual_adjuster,
585 payment_adjuster,
586 eom,
587 stub_inference,
588 )
589 .ok()
590 })
591 .collect();
592
593 if schedules.len() == 0 {
594 Err(PyValueError::new_err(
595 "A Schedule could not be generated from the parameter combinations.",
596 ))
597 } else {
598 let regulars: Vec<Schedule> = schedules
600 .iter()
601 .cloned()
602 .filter(|schedule| schedule.is_regular())
603 .collect();
604 if regulars.len() != 0 {
605 Ok(filter_schedules_by_eom(regulars, eom))
606 } else {
607 Ok(filter_schedules_by_eom(schedules, eom))
608 }
609 }
610 }
611
612 pub fn is_regular(&self) -> bool {
614 let ucheck = self
615 .frequency
616 .try_uregular(&self.ueffective, &self.utermination);
617 if ucheck.is_ok() {
618 ucheck.unwrap() == self.uschedule
619 } else {
620 false
621 }
622 }
623}
624
625fn match_interior_dates(
626 ueffective: &NaiveDateTime,
627 ufront_stub: &Option<NaiveDateTime>,
628 uback_stub: &Option<NaiveDateTime>,
629 utermination: &NaiveDateTime,
630) -> (NaiveDateTime, NaiveDateTime) {
631 match (ufront_stub, uback_stub) {
632 (None, None) => (*ueffective, *utermination),
633 (Some(v), None) => (*v, *utermination),
634 (None, Some(v)) => (*ueffective, *v),
635 (Some(v), Some(w)) => (*v, *w),
636 }
637}
638
639fn validate_stub_dates_and_inference(
641 ufront_stub: &Option<NaiveDateTime>,
642 uback_stub: &Option<NaiveDateTime>,
643 stub_inference: &Option<StubInference>,
644) -> Result<(), PyErr> {
645 match (ufront_stub, uback_stub, stub_inference) {
646 (Some(_v), Some(_w), Some(_f)) => Err(PyValueError::new_err(
647 "Cannot infer stubs if they are explicitly given.",
648 )),
649 (Some(_v), None, Some(val))
650 if matches!(val, StubInference::ShortFront | StubInference::LongFront) =>
651 {
652 Err(PyValueError::new_err(
653 "Cannot infer stubs if they are explicitly given.",
654 ))
655 }
656 (None, Some(_w), Some(val))
657 if matches!(val, StubInference::ShortBack | StubInference::LongBack) =>
658 {
659 Err(PyValueError::new_err(
660 "Cannot infer stubs if they are explicitly given.",
661 ))
662 }
663 _ => Ok(()),
664 }
665}
666
667fn composite_uschedule(
669 ueffective: &NaiveDateTime,
670 utermination: &NaiveDateTime,
671 ufront_stub: &Option<NaiveDateTime>,
672 uback_stub: &Option<NaiveDateTime>,
673 regular_uschedule: &Vec<NaiveDateTime>,
674) -> Vec<NaiveDateTime> {
675 let mut uschedule: Vec<NaiveDateTime> = vec![];
676 match (*ufront_stub, *uback_stub) {
677 (None, None) => {
678 uschedule.extend(regular_uschedule);
679 }
680 (Some(_v), None) => {
681 uschedule.push(*ueffective);
682 uschedule.extend(regular_uschedule);
683 }
684 (None, Some(_v)) => {
685 uschedule.extend(regular_uschedule);
686 uschedule.push(*utermination);
687 }
688 (Some(_v), Some(_w)) => {
689 uschedule.push(*ueffective);
690 uschedule.extend(regular_uschedule);
691 uschedule.push(*utermination);
692 }
693 }
694 uschedule
695}
696
697fn filter_schedules_by_eom(uschedules: Vec<Schedule>, eom: bool) -> Schedule {
698 let original = uschedules[0].clone();
701
702 if !eom {
703 original
705 } else {
706 let possibles: Vec<Schedule> = uschedules
708 .into_iter()
709 .filter(|s| {
710 matches!(
711 s.frequency,
712 Frequency::Months {
713 number: _,
714 roll: Some(RollDay::Day(31))
715 }
716 )
717 })
718 .collect();
719 if possibles.len() >= 1 {
720 possibles[0].clone()
721 } else {
722 original
723 }
724 }
725}
726
727#[cfg(test)]
729mod tests {
730 use super::*;
731 use crate::scheduling::{ndt, Cal, RollDay};
732
733 #[test]
734 fn test_new_uschedule_defined_cases_1_and_2() {
735 let options: Vec<(NaiveDateTime, NaiveDateTime, Vec<NaiveDateTime>)> = vec![
736 (
737 ndt(2000, 1, 1), ndt(2000, 3, 1),
739 vec![ndt(2000, 1, 1), ndt(2000, 2, 1), ndt(2000, 3, 1)],
740 ),
741 (
742 ndt(2000, 1, 1), ndt(2000, 1, 20),
744 vec![ndt(2000, 1, 1), ndt(2000, 1, 20)],
745 ),
746 (
747 ndt(2000, 1, 1), ndt(2000, 2, 15),
749 vec![ndt(2000, 1, 1), ndt(2000, 2, 15)],
750 ),
751 ];
752 for option in options {
753 let result = Schedule::try_new_defined(
754 option.0,
755 option.1,
756 Frequency::Months {
757 number: 1,
758 roll: Some(RollDay::Day(1)),
759 },
760 None,
761 None,
762 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
763 Adjuster::Following {},
764 Adjuster::Following {},
765 );
766 assert_eq!(result.unwrap().uschedule, option.2);
767 }
768 }
769
770 #[test]
771 fn test_new_uschedule_defined_cases_1_and_2_err() {
772 let options: Vec<(NaiveDateTime, NaiveDateTime, Frequency)> = vec![
773 (
774 ndt(2000, 1, 1), ndt(2000, 3, 15),
776 Frequency::Months {
777 number: 1,
778 roll: Some(RollDay::Day(1)),
779 },
780 ),
781 (
782 ndt(2000, 1, 1), ndt(2000, 3, 1),
784 Frequency::Months {
785 number: 1,
786 roll: None,
787 },
788 ),
789 ];
790 for option in options {
791 let result = Schedule::try_new_defined(
792 option.0,
793 option.1,
794 option.2,
795 None,
796 None,
797 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
798 Adjuster::Following {},
799 Adjuster::Following {},
800 );
801 assert!(result.is_err());
802 }
803 }
804
805 #[test]
806 fn test_new_uschedule_defined_cases_4() {
807 let options: Vec<(
808 NaiveDateTime,
809 NaiveDateTime,
810 NaiveDateTime,
811 Vec<NaiveDateTime>,
812 )> = vec![
813 (
814 ndt(2000, 1, 1), ndt(2000, 1, 15),
816 ndt(2000, 2, 10),
817 vec![ndt(2000, 1, 1), ndt(2000, 1, 15), ndt(2000, 2, 10)],
818 ),
819 (
820 ndt(2000, 1, 1), ndt(2000, 1, 15),
822 ndt(2000, 2, 25),
823 vec![ndt(2000, 1, 1), ndt(2000, 1, 15), ndt(2000, 2, 25)],
824 ),
825 (
826 ndt(2000, 1, 1), ndt(2000, 2, 15),
828 ndt(2000, 2, 25),
829 vec![ndt(2000, 1, 1), ndt(2000, 2, 15), ndt(2000, 2, 25)],
830 ),
831 (
832 ndt(2000, 1, 1), ndt(2000, 2, 15),
834 ndt(2000, 3, 20),
835 vec![ndt(2000, 1, 1), ndt(2000, 2, 15), ndt(2000, 3, 20)],
836 ),
837 ];
838 for option in options {
839 let result = Schedule::try_new_defined(
840 option.0,
841 option.2,
842 Frequency::Months {
843 number: 1,
844 roll: Some(RollDay::Day(15)),
845 }, Some(option.1),
847 Some(option.1),
848 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
849 Adjuster::Following {},
850 Adjuster::Following {},
851 );
852 assert_eq!(result.unwrap().uschedule, option.3);
853 }
854 }
855
856 #[test]
857 fn test_new_uschedule_defined_cases_3() {
858 let options: Vec<(
859 NaiveDateTime,
860 Option<NaiveDateTime>,
861 Option<NaiveDateTime>,
862 NaiveDateTime,
863 Vec<NaiveDateTime>,
864 )> = vec![
865 (
866 ndt(2000, 1, 1), Some(ndt(2000, 1, 15)),
868 None,
869 ndt(2000, 3, 15),
870 vec![
871 ndt(2000, 1, 1),
872 ndt(2000, 1, 15),
873 ndt(2000, 2, 15),
874 ndt(2000, 3, 15),
875 ],
876 ),
877 (
878 ndt(2000, 1, 1), Some(ndt(2000, 2, 15)),
880 None,
881 ndt(2000, 4, 15),
882 vec![
883 ndt(2000, 1, 1),
884 ndt(2000, 2, 15),
885 ndt(2000, 3, 15),
886 ndt(2000, 4, 15),
887 ],
888 ),
889 (
890 ndt(2000, 1, 15), None,
892 Some(ndt(2000, 3, 15)),
893 ndt(2000, 4, 10),
894 vec![
895 ndt(2000, 1, 15),
896 ndt(2000, 2, 15),
897 ndt(2000, 3, 15),
898 ndt(2000, 4, 10),
899 ],
900 ),
901 (
902 ndt(2000, 1, 15), None,
904 Some(ndt(2000, 3, 15)),
905 ndt(2000, 4, 25),
906 vec![
907 ndt(2000, 1, 15),
908 ndt(2000, 2, 15),
909 ndt(2000, 3, 15),
910 ndt(2000, 4, 25),
911 ],
912 ),
913 (
914 ndt(2000, 1, 15), None,
916 Some(ndt(2000, 3, 15)),
917 ndt(2000, 5, 15),
918 vec![
919 ndt(2000, 1, 15),
920 ndt(2000, 2, 15),
921 ndt(2000, 3, 15),
922 ndt(2000, 5, 15),
923 ],
924 ),
925 ];
926 for option in options {
927 let result = Schedule::try_new_defined(
928 option.0,
929 option.3,
930 Frequency::Months {
931 number: 1,
932 roll: Some(RollDay::Day(15)),
933 }, option.1,
935 option.2,
936 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
937 Adjuster::Following {},
938 Adjuster::Following {},
939 );
940 assert_eq!(result.unwrap().uschedule, option.4);
941 }
942 }
943
944 #[test]
945 fn test_new_uschedule_defined_cases_3_err() {
946 let options: Vec<(
947 NaiveDateTime,
948 Option<NaiveDateTime>,
949 Option<NaiveDateTime>,
950 NaiveDateTime,
951 )> = vec![
952 (
953 ndt(2000, 1, 1), Some(ndt(2000, 1, 15)),
955 None,
956 ndt(2000, 3, 16),
957 ),
958 (
959 ndt(2000, 1, 1), Some(ndt(2000, 5, 15)),
961 None,
962 ndt(2000, 7, 15),
963 ),
964 (
965 ndt(2000, 1, 13), None,
967 Some(ndt(2000, 3, 15)),
968 ndt(2000, 4, 10),
969 ),
970 (
971 ndt(2000, 1, 15), None,
973 Some(ndt(2000, 3, 15)),
974 ndt(2000, 7, 25),
975 ),
976 (
977 ndt(2000, 1, 15), None,
979 Some(ndt(2000, 3, 15)),
980 ndt(2000, 4, 15),
981 ),
982 ];
983 for option in options {
984 let result = Schedule::try_new_defined(
985 option.0,
986 option.3,
987 Frequency::Months {
988 number: 1,
989 roll: Some(RollDay::Day(15)),
990 }, option.1,
992 option.2,
993 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
994 Adjuster::Following {},
995 Adjuster::Following {},
996 );
997 assert!(result.is_err());
998 }
999 }
1000
1001 #[test]
1002 fn test_new_uschedule_defined_cases_5() {
1003 let options: Vec<(
1004 NaiveDateTime,
1005 Option<NaiveDateTime>,
1006 Option<NaiveDateTime>,
1007 NaiveDateTime,
1008 Vec<NaiveDateTime>,
1009 )> = vec![
1010 (
1011 ndt(2000, 1, 1), Some(ndt(2000, 1, 15)),
1013 Some(ndt(2000, 3, 15)),
1014 ndt(2000, 4, 10),
1015 vec![
1016 ndt(2000, 1, 1),
1017 ndt(2000, 1, 15),
1018 ndt(2000, 2, 15),
1019 ndt(2000, 3, 15),
1020 ndt(2000, 4, 10),
1021 ],
1022 ),
1023 (
1024 ndt(2000, 1, 1), Some(ndt(2000, 1, 15)),
1026 Some(ndt(2000, 3, 15)),
1027 ndt(2000, 4, 25),
1028 vec![
1029 ndt(2000, 1, 1),
1030 ndt(2000, 1, 15),
1031 ndt(2000, 2, 15),
1032 ndt(2000, 3, 15),
1033 ndt(2000, 4, 25),
1034 ],
1035 ),
1036 (
1037 ndt(2000, 1, 1), Some(ndt(2000, 2, 15)),
1039 Some(ndt(2000, 3, 15)),
1040 ndt(2000, 4, 25),
1041 vec![
1042 ndt(2000, 1, 1),
1043 ndt(2000, 2, 15),
1044 ndt(2000, 3, 15),
1045 ndt(2000, 4, 25),
1046 ],
1047 ),
1048 (
1049 ndt(2000, 1, 1), Some(ndt(2000, 2, 15)),
1051 Some(ndt(2000, 3, 15)),
1052 ndt(2000, 4, 10),
1053 vec![
1054 ndt(2000, 1, 1),
1055 ndt(2000, 2, 15),
1056 ndt(2000, 3, 15),
1057 ndt(2000, 4, 10),
1058 ],
1059 ),
1060 ];
1061 for option in options {
1062 let result = Schedule::try_new_defined(
1063 option.0,
1064 option.3,
1065 Frequency::Months {
1066 number: 1,
1067 roll: Some(RollDay::Day(15)),
1068 }, option.1,
1070 option.2,
1071 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1072 Adjuster::Following {},
1073 Adjuster::Following {},
1074 );
1075 assert_eq!(result.unwrap().uschedule, option.4);
1076 }
1077 }
1078
1079 #[test]
1080 fn test_new_uschedule_defined_cases_5_err() {
1081 let options: Vec<(
1082 NaiveDateTime,
1083 Option<NaiveDateTime>,
1084 Option<NaiveDateTime>,
1085 NaiveDateTime,
1086 )> = vec![(
1087 ndt(2000, 1, 1), Some(ndt(2000, 1, 15)),
1089 Some(ndt(2000, 3, 16)),
1090 ndt(2000, 4, 10),
1091 )];
1092 for option in options {
1093 let result = Schedule::try_new_defined(
1094 option.0,
1095 option.3,
1096 Frequency::Months {
1097 number: 1,
1098 roll: Some(RollDay::Day(15)),
1099 }, option.1,
1101 option.2,
1102 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1103 Adjuster::Following {},
1104 Adjuster::Following {},
1105 );
1106 assert!(result.is_err());
1107 }
1108 }
1109
1110 #[test]
1111 fn test_new_uschedule_defined_err() {
1112 let result = Schedule::try_new_defined(
1114 ndt(2000, 1, 1),
1115 ndt(2001, 1, 1),
1116 Frequency::Months {
1117 number: 6,
1118 roll: None,
1119 },
1120 None,
1121 None,
1122 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1123 Adjuster::Actual {},
1124 Adjuster::Actual {},
1125 );
1126 assert!(result.is_err())
1127 }
1128
1129 #[test]
1130 fn test_try_new_uschedule_dead_stubs() {
1131 let s = Schedule::try_new_defined(
1132 ndt(2023, 1, 1),
1133 ndt(2024, 1, 2),
1134 Frequency::Months {
1135 number: 6,
1136 roll: Some(RollDay::Day(2)),
1137 },
1138 Some(ndt(2023, 1, 2)),
1139 None,
1140 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1141 Adjuster::ModifiedFollowing {},
1142 Adjuster::BusDaysLagSettle(1),
1143 );
1144 assert!(s.is_err()); let s = Schedule::try_new_defined(
1147 ndt(2022, 1, 1),
1148 ndt(2023, 1, 2),
1149 Frequency::Months {
1150 number: 6,
1151 roll: Some(RollDay::Day(1)),
1152 },
1153 None,
1154 Some(ndt(2023, 1, 1)),
1155 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1156 Adjuster::ModifiedFollowing {},
1157 Adjuster::BusDaysLagSettle(1),
1158 );
1159 assert!(s.is_err()); }
1161
1162 #[test]
1163 fn test_try_new_uschedule_eom_parameter_selection() {
1164 let s = Schedule::try_new_uschedule_infer_frequency(
1165 ndt(2024, 2, 29),
1166 ndt(2024, 11, 30),
1167 Frequency::Months {
1168 number: 3,
1169 roll: None,
1170 },
1171 None,
1172 None,
1173 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1174 Adjuster::ModifiedFollowing {},
1175 Adjuster::BusDaysLagSettle(1),
1176 true,
1177 None,
1178 )
1179 .unwrap();
1180 assert_eq!(
1181 s.frequency,
1182 Frequency::Months {
1183 number: 3,
1184 roll: Some(RollDay::Day(31))
1185 }
1186 );
1187
1188 let s = Schedule::try_new_uschedule_infer_frequency(
1189 ndt(2024, 2, 29),
1190 ndt(2024, 11, 30),
1191 Frequency::Months {
1192 number: 3,
1193 roll: None,
1194 },
1195 None,
1196 None,
1197 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1198 Adjuster::ModifiedFollowing {},
1199 Adjuster::BusDaysLagSettle(1),
1200 false,
1201 None,
1202 )
1203 .unwrap();
1204 assert_eq!(
1205 s.frequency,
1206 Frequency::Months {
1207 number: 3,
1208 roll: Some(RollDay::Day(30))
1209 }
1210 );
1211
1212 let s = Schedule::try_new_uschedule_infer_frequency(
1213 ndt(2024, 2, 29),
1214 ndt(2024, 11, 29),
1215 Frequency::Months {
1216 number: 3,
1217 roll: None,
1218 },
1219 None,
1220 None,
1221 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1222 Adjuster::ModifiedFollowing {},
1223 Adjuster::BusDaysLagSettle(1),
1224 true,
1225 None,
1226 )
1227 .unwrap();
1228 assert_eq!(
1229 s.frequency,
1230 Frequency::Months {
1231 number: 3,
1232 roll: Some(RollDay::Day(29))
1233 }
1234 );
1235 }
1236
1237 #[test]
1238 fn test_try_new_uschedule_inferred_fails() {
1239 assert_eq!(
1241 true,
1242 Schedule::try_new_infer_stub(
1243 ndt(2000, 1, 1),
1244 ndt(2000, 2, 1),
1245 Frequency::CalDays { number: 100 },
1246 Some(ndt(2000, 1, 10)),
1247 Some(ndt(2000, 1, 16)),
1248 Calendar::Cal(Cal::new(vec![], vec![])),
1249 Adjuster::ModifiedFollowing {},
1250 Adjuster::BusDaysLagSettle(1),
1251 Some(StubInference::ShortBack)
1252 )
1253 .is_err()
1254 );
1255
1256 assert_eq!(
1258 true,
1259 Schedule::try_new_infer_stub(
1260 ndt(2000, 1, 1),
1261 ndt(2000, 2, 1),
1262 Frequency::CalDays { number: 100 },
1263 None,
1264 Some(ndt(2000, 1, 16)),
1265 Calendar::Cal(Cal::new(vec![], vec![])),
1266 Adjuster::ModifiedFollowing {},
1267 Adjuster::BusDaysLagSettle(1),
1268 Some(StubInference::ShortBack)
1269 )
1270 .is_err()
1271 );
1272
1273 assert_eq!(
1275 true,
1276 Schedule::try_new_infer_stub(
1277 ndt(2000, 1, 1),
1278 ndt(2000, 2, 1),
1279 Frequency::CalDays { number: 100 },
1280 None,
1281 Some(ndt(2000, 1, 16)),
1282 Calendar::Cal(Cal::new(vec![], vec![])),
1283 Adjuster::ModifiedFollowing {},
1284 Adjuster::BusDaysLagSettle(1),
1285 Some(StubInference::LongBack)
1286 )
1287 .is_err()
1288 );
1289
1290 assert_eq!(
1292 true,
1293 Schedule::try_new_infer_stub(
1294 ndt(2000, 1, 1),
1295 ndt(2000, 2, 1),
1296 Frequency::CalDays { number: 100 },
1297 Some(ndt(2000, 1, 16)),
1298 None,
1299 Calendar::Cal(Cal::new(vec![], vec![])),
1300 Adjuster::ModifiedFollowing {},
1301 Adjuster::BusDaysLagSettle(1),
1302 Some(StubInference::ShortFront)
1303 )
1304 .is_err()
1305 );
1306
1307 assert_eq!(
1309 true,
1310 Schedule::try_new_infer_stub(
1311 ndt(2000, 1, 1),
1312 ndt(2000, 2, 1),
1313 Frequency::CalDays { number: 100 },
1314 Some(ndt(2000, 1, 16)),
1315 None,
1316 Calendar::Cal(Cal::new(vec![], vec![])),
1317 Adjuster::ModifiedFollowing {},
1318 Adjuster::BusDaysLagSettle(1),
1319 Some(StubInference::LongFront)
1320 )
1321 .is_err()
1322 );
1323 }
1324
1325 #[test]
1326 fn test_try_new_schedule_short_period() {
1327 let s = Schedule::try_new_uschedule_infer_frequency(
1329 ndt(2022, 7, 1),
1330 ndt(2022, 10, 1),
1331 Frequency::Months {
1332 number: 12,
1333 roll: None,
1334 },
1335 None,
1336 None,
1337 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1338 Adjuster::ModifiedFollowing {},
1339 Adjuster::BusDaysLagSettle(1),
1340 true,
1341 Some(StubInference::ShortFront),
1342 )
1343 .expect("short period");
1344 assert_eq!(s.uschedule, vec![ndt(2022, 7, 1), ndt(2022, 10, 1)]);
1345 }
1346
1347 #[test]
1348 fn test_try_new_schedule_infer_frequency_imm() {
1349 let s = Schedule::try_new_uschedule_infer_frequency(
1351 ndt(2025, 3, 19),
1352 ndt(2025, 9, 17),
1353 Frequency::Months {
1354 number: 3,
1355 roll: None,
1356 },
1357 None,
1358 None,
1359 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1360 Adjuster::ModifiedFollowing {},
1361 Adjuster::BusDaysLagSettle(1),
1362 true,
1363 None,
1364 )
1365 .expect("short period");
1366 assert_eq!(
1367 s.frequency,
1368 Frequency::Months {
1369 number: 3,
1370 roll: Some(RollDay::IMM())
1371 }
1372 );
1373 }
1374
1375 #[test]
1376 fn test_is_regular() {
1377 let s = Schedule::try_new_uschedule_infer_frequency(
1378 ndt(2025, 3, 19),
1379 ndt(2025, 9, 19),
1380 Frequency::Months {
1381 number: 3,
1382 roll: Some(RollDay::Day(19)),
1383 },
1384 None,
1385 None,
1386 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1387 Adjuster::ModifiedFollowing {},
1388 Adjuster::BusDaysLagSettle(1),
1389 true,
1390 None,
1391 )
1392 .expect("regular");
1393 assert!(s.is_regular());
1394
1395 let s = Schedule::try_new_uschedule_infer_frequency(
1396 ndt(2025, 3, 19),
1397 ndt(2025, 9, 25),
1398 Frequency::Months {
1399 number: 3,
1400 roll: Some(RollDay::Day(19)),
1401 },
1402 None,
1403 None,
1404 Calendar::Cal(Cal::new(vec![], vec![5, 6])),
1405 Adjuster::ModifiedFollowing {},
1406 Adjuster::BusDaysLagSettle(1),
1407 true,
1408 Some(StubInference::ShortBack),
1409 )
1410 .expect("regular");
1411 assert!(!s.is_regular());
1412 }
1413
1414 #[test]
1415 fn test_front_stub_inference() {
1416 let s = Schedule::try_new_inferred(
1417 ndt(2022, 1, 1),
1418 ndt(2022, 6, 1),
1419 Frequency::Months {
1420 number: 3,
1421 roll: None,
1422 },
1423 None,
1424 None,
1425 Calendar::Cal(Cal::new(vec![], vec![])),
1426 Adjuster::ModifiedFollowing {},
1427 Adjuster::BusDaysLagSettle(2),
1428 false,
1429 Some(StubInference::ShortFront),
1430 )
1431 .expect("schedule is valid");
1432 assert_eq!(
1433 s.uschedule,
1434 vec![ndt(2022, 1, 1), ndt(2022, 3, 1), ndt(2022, 6, 1)]
1435 );
1436 }
1437}