periodical 0.3.0

Management of all kinds of time intervals, use it to manage schedules, find overlaps, and more!
Documentation
use super::cut::*;
use crate::intervals::absolute::{AbsInterval, BoundedAbsInterval, EmptiableAbsBoundPair, HalfBoundedAbsInterval};
use crate::intervals::meta::{BoundInclusivity, OpeningDirection};
use crate::intervals::special::{EmptyInterval, UnboundedInterval};

mod cut_type {
    use super::*;

    #[test]
    fn cut_type_past_bound_inclusivity() {
        assert_eq!(
            CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Exclusive).past_bound_inclusivity(),
            BoundInclusivity::Inclusive,
        );
        assert_eq!(
            CutType::new(BoundInclusivity::Exclusive, BoundInclusivity::Inclusive).past_bound_inclusivity(),
            BoundInclusivity::Exclusive,
        );
    }

    #[test]
    fn cut_type_future_bound_inclusivity() {
        assert_eq!(
            CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Exclusive).future_bound_inclusivity(),
            BoundInclusivity::Exclusive,
        );
        assert_eq!(
            CutType::new(BoundInclusivity::Exclusive, BoundInclusivity::Inclusive).future_bound_inclusivity(),
            BoundInclusivity::Inclusive,
        );
    }

    #[test]
    fn cut_type_opposite() {
        assert_eq!(
            CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Exclusive).opposite(),
            CutType::new(BoundInclusivity::Exclusive, BoundInclusivity::Inclusive),
        );
    }

    #[test]
    fn cut_type_from_bound_inclusivity_pair() {
        assert_eq!(
            CutType::from((BoundInclusivity::Inclusive, BoundInclusivity::Exclusive)),
            CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Exclusive),
        );
    }
}

mod cut_result {
    use super::*;

    #[test]
    fn cut_result_is_uncut() {
        assert!(CutResult::<()>::Uncut.is_uncut());
        assert!(!CutResult::Cut((), ()).is_uncut());
    }

    #[test]
    fn cut_result_is_cut() {
        assert!(!CutResult::<()>::Uncut.is_cut());
        assert!(CutResult::Cut((), ()).is_cut());
    }

    #[test]
    fn cut_result_cut_opt() {
        assert_eq!(CutResult::<u8>::Uncut.cut(), None);
        assert_eq!(CutResult::<u8>::Cut(10, 20).cut(), Some((10, 20)));
    }

    #[test]
    fn cut_result_map_cut() {
        assert_eq!(
            CutResult::<u8>::Cut(10, 20).map_cut(|a, b| (a + 10, b + 20)),
            CutResult::Cut(20, 40)
        );
    }
}

mod cut_at {
    use std::error::Error;

    use jiff::{Timestamp, Zoned};

    use super::*;

    #[test]
    fn cut_at_empty_interval() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            <EmptyInterval as Cuttable<Timestamp>>::cut_at(
                &EmptyInterval,
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Inclusive)
            ),
            CutResult::Uncut,
        );

        Ok(())
    }

    #[test]
    fn cut_at_emptiable_abs_bounds_empty() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            EmptiableAbsBoundPair::Empty.cut_at(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Inclusive)
            ),
            CutResult::Uncut,
        );

        Ok(())
    }

    #[test]
    fn cut_at_unbounded_interval_inclusive_inclusive_cut() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            UnboundedInterval.cut_at(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Inclusive)
            ),
            CutResult::Cut(
                AbsInterval::HalfBounded(HalfBoundedAbsInterval::from_time_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Inclusive,
                    OpeningDirection::ToPast,
                )),
                AbsInterval::HalfBounded(HalfBoundedAbsInterval::from_time_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Inclusive,
                    OpeningDirection::ToFuture,
                )),
            ),
        );

        Ok(())
    }

    #[test]
    fn cut_at_unbounded_interval_inclusive_exclusive_cut() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            UnboundedInterval.cut_at(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Exclusive)
            ),
            CutResult::Cut(
                AbsInterval::HalfBounded(HalfBoundedAbsInterval::from_time_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Inclusive,
                    OpeningDirection::ToPast,
                )),
                AbsInterval::HalfBounded(HalfBoundedAbsInterval::from_time_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Exclusive,
                    OpeningDirection::ToFuture,
                )),
            ),
        );

        Ok(())
    }

    #[test]
    fn cut_at_unbounded_interval_exclusive_inclusive_cut() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            UnboundedInterval.cut_at(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Exclusive, BoundInclusivity::Inclusive)
            ),
            CutResult::Cut(
                AbsInterval::HalfBounded(HalfBoundedAbsInterval::from_time_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Exclusive,
                    OpeningDirection::ToPast,
                )),
                AbsInterval::HalfBounded(HalfBoundedAbsInterval::from_time_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Inclusive,
                    OpeningDirection::ToFuture,
                )),
            ),
        );

        Ok(())
    }

    #[test]
    fn cut_at_unbounded_interval_exclusive_exclusive_cut() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            UnboundedInterval.cut_at(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Exclusive, BoundInclusivity::Exclusive)
            ),
            CutResult::Cut(
                AbsInterval::HalfBounded(HalfBoundedAbsInterval::from_time_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Exclusive,
                    OpeningDirection::ToPast,
                )),
                AbsInterval::HalfBounded(HalfBoundedAbsInterval::from_time_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Exclusive,
                    OpeningDirection::ToFuture,
                )),
            ),
        );

        Ok(())
    }

    #[test]
    fn cut_at_half_unbounded_interval() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            HalfBoundedAbsInterval::from_time(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                OpeningDirection::ToFuture
            )
            .cut_at(
                "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Exclusive)
            ),
            CutResult::Cut(
                AbsInterval::Bounded(BoundedAbsInterval::from_times_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Inclusive,
                    "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Inclusive,
                )),
                AbsInterval::HalfBounded(HalfBoundedAbsInterval::from_time_incl(
                    "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Exclusive,
                    OpeningDirection::ToFuture,
                )),
            ),
        );

        Ok(())
    }

    #[test]
    fn cut_at_outside_before_bounded_interval() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            BoundedAbsInterval::from_times(
                "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                "2025-01-03 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp()
            )
            .cut_at(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Inclusive)
            ),
            CutResult::Uncut,
        );

        Ok(())
    }

    #[test]
    fn cut_at_outside_after_bounded_interval() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            BoundedAbsInterval::from_times(
                "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                "2025-01-03 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp()
            )
            .cut_at(
                "2025-01-04 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Inclusive)
            ),
            CutResult::Uncut,
        );

        Ok(())
    }

    #[test]
    fn cut_at_inside_bounded_interval() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            BoundedAbsInterval::from_times(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                "2025-01-03 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp()
            )
            .cut_at(
                "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Exclusive, BoundInclusivity::Exclusive)
            ),
            CutResult::Cut(
                BoundedAbsInterval::from_times_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Inclusive,
                    "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Exclusive,
                ),
                BoundedAbsInterval::from_times_incl(
                    "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Exclusive,
                    "2025-01-03 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Inclusive,
                ),
            ),
        );

        Ok(())
    }

    #[test]
    fn cut_at_inclusive_edge_bounded_interval() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            BoundedAbsInterval::from_times(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
            )
            .cut_at(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Inclusive, BoundInclusivity::Exclusive)
            ),
            CutResult::Cut(
                BoundedAbsInterval::from_times_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Inclusive,
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Inclusive,
                ),
                BoundedAbsInterval::from_times_incl(
                    "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Exclusive,
                    "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                    BoundInclusivity::Inclusive,
                ),
            ),
        );

        Ok(())
    }

    #[test]
    fn cut_at_inclusive_edge_bounded_interval_would_create_illegal_interval() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            BoundedAbsInterval::from_times(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp()
            )
            .cut_at(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Exclusive, BoundInclusivity::Exclusive)
            ),
            CutResult::Uncut,
        );

        Ok(())
    }

    #[test]
    fn cut_at_exclusive_edge_bounded_interval() -> Result<(), Box<dyn Error>> {
        assert_eq!(
            BoundedAbsInterval::from_times_incl(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                BoundInclusivity::Exclusive,
                "2025-01-02 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                BoundInclusivity::Inclusive,
            )
            .cut_at(
                "2025-01-01 00:00:00[Europe/Oslo]".parse::<Zoned>()?.timestamp(),
                CutType::new(BoundInclusivity::Exclusive, BoundInclusivity::Exclusive)
            ),
            CutResult::Uncut,
        );

        Ok(())
    }
}