    {-|
    A partition of time into contiguous spans, for defining reporting periods.
    -}
    module Hledger.Data.DayPartition
    ( DayPartition
    -- * constructors
    , boundariesToDayPartition
    , boundariesToMaybeDayPartition
    -- * conversions
    , dayPartitionToNonEmpty
    , dayPartitionToList
    , dayPartitionToDateSpans
    , dayPartitionToPeriodData
    , maybeDayPartitionToDateSpans
    -- * operations
    , unionDayPartitions
    , dayPartitionStartEnd
    , dayPartitionFind
    , splitSpan
    , intervalBoundaryBefore
    -- * tests
    , tests_DayPartition
    ) where

    import Data.List.NonEmpty (NonEmpty(..))
    import qualified Data.List.NonEmpty as NE
    import Data.Map qualified as M
    import Data.Time (Day (..), addDays, addGregorianMonthsClip, addGregorianYearsClip, fromGregorian)

    import Hledger.Data.Dates
    import Hledger.Data.PeriodData
    import Hledger.Data.Types
    import Hledger.Utils

    -- | A partition of time into one or more contiguous periods,
    -- plus a historical period that precedes them.
    -- Note 'DayPartition' does not store per-period data - only the periods' start/end dates.

    -- Each period is at least one day in length.
    -- The historical period is open ended, with no start date.
    -- The last period has an end date, but note some queries (like 'dayPartitionFind') ignore that, acting as if the last period is open ended.
    -- Only smart constructors are exported, so that a DayPartition always satisfies these invariants.
    --
    -- This is implemented as a newtype wrapper around 'PeriodData Day', which is a map from date to date.
    -- The map's keys are the period start dates, and the values are the corresponding period end dates.
    -- Note unlike 'DateSpan', which stores exclusive end dates ( @[start, end)@ ),
    -- here both start and end dates are inclusive ( @[start, end]@ ).
    --
    newtype DayPartition = DayPartition { DayPartition -> PeriodData Day
    dayPartitionToPeriodData :: PeriodData Day } deriving (DayPartition -> DayPartition -> Bool
    (DayPartition -> DayPartition -> Bool)
    -> (DayPartition -> DayPartition -> Bool) -> Eq DayPartition
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: DayPartition -> DayPartition -> Bool
    == :: DayPartition -> DayPartition -> Bool
    $c/= :: DayPartition -> DayPartition -> Bool
    /= :: DayPartition -> DayPartition -> Bool
    Eq, Eq DayPartition
    Eq DayPartition =>
    (DayPartition -> DayPartition -> Ordering)
    -> (DayPartition -> DayPartition -> Bool)
    -> (DayPartition -> DayPartition -> Bool)
    -> (DayPartition -> DayPartition -> Bool)
    -> (DayPartition -> DayPartition -> Bool)
    -> (DayPartition -> DayPartition -> DayPartition)
    -> (DayPartition -> DayPartition -> DayPartition)
    -> Ord DayPartition
    DayPartition -> DayPartition -> Bool
    DayPartition -> DayPartition -> Ordering
    DayPartition -> DayPartition -> DayPartition
    forall a.
    Eq a =>
    (a -> a -> Ordering)
    -> (a -> a -> Bool)
    -> (a -> a -> Bool)
    -> (a -> a -> Bool)
    -> (a -> a -> Bool)
    -> (a -> a -> a)
    -> (a -> a -> a)
    -> Ord a
    $ccompare :: DayPartition -> DayPartition -> Ordering
    compare :: DayPartition -> DayPartition -> Ordering
    $c< :: DayPartition -> DayPartition -> Bool
    < :: DayPartition -> DayPartition -> Bool
    $c<= :: DayPartition -> DayPartition -> Bool
    <= :: DayPartition -> DayPartition -> Bool
    $c> :: DayPartition -> DayPartition -> Bool
    > :: DayPartition -> DayPartition -> Bool
    $c>= :: DayPartition -> DayPartition -> Bool
    >= :: DayPartition -> DayPartition -> Bool
    $cmax :: DayPartition -> DayPartition -> DayPartition
    max :: DayPartition -> DayPartition -> DayPartition
    $cmin :: DayPartition -> DayPartition -> DayPartition
    min :: DayPartition -> DayPartition -> DayPartition
    Ord, Int -> DayPartition -> ShowS
    [DayPartition] -> ShowS
    DayPartition -> String
    (Int -> DayPartition -> ShowS)
    -> (DayPartition -> String)
    -> ([DayPartition] -> ShowS)
    -> Show DayPartition
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> DayPartition -> ShowS
    showsPrec :: Int -> DayPartition -> ShowS
    $cshow :: DayPartition -> String
    show :: DayPartition -> String
    $cshowList :: [DayPartition] -> ShowS
    showList :: [DayPartition] -> ShowS
    Show)

    -- constructors:

    -- | Construct a 'DayPartition' from a non-empty list of period boundary dates (start dates plus a final exclusive end date).
    --
    -- >>> boundariesToDayPartition (fromGregorian 2025 01 01 :| [fromGregorian 2025 02 01])
    -- DayPartition {dayPartitionToPeriodData = PeriodData{ pdpre = 2024-12-31, pdperiods = fromList [(2025-01-01,2025-01-31)]}}
    --
    boundariesToDayPartition :: NonEmpty Day -> DayPartition
    boundariesToDayPartition :: NonEmpty Day -> DayPartition
    boundariesToDayPartition NonEmpty Day
    xs = PeriodData Day -> DayPartition
    DayPartition (PeriodData Day -> DayPartition)
    -> ([(Day, Day)] -> PeriodData Day) -> [(Day, Day)] -> DayPartition
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Day -> [(Day, Day)] -> PeriodData Day
    forall a. a -> [(Day, a)] -> PeriodData a
    periodDataFromList (Integer -> Day -> Day
    addDays (-Integer
    1) Day
    b) ([(Day, Day)] -> DayPartition) -> [(Day, Day)] -> DayPartition
    forall a b. (a -> b) -> a -> b
    $ case [Day]
    bs of
        []  -> [(Day
    b, Day
    b)]  -- If only one boundary is supplied, it ends on the same day
        Day
    _:[Day]
    _ -> [Day] -> [Day] -> [(Day, Day)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip (Day
    bDay -> [Day] -> [Day]
    forall a. a -> [a] -> [a]
    :[Day]
    bs) ([Day] -> [(Day, Day)]) -> [Day] -> [(Day, Day)]
    forall a b. (a -> b) -> a -> b
    $ (Day -> Day) -> [Day] -> [Day]
    forall a b. (a -> b) -> [a] -> [b]
    map (Integer -> Day -> Day
    addDays (-Integer
    1)) [Day]
    bs  -- Guaranteed non-empty
      where Day
    b:|[Day]
    bs = NonEmpty Day -> NonEmpty Day
    forall a. Eq a => NonEmpty a -> NonEmpty a
    NE.nub (NonEmpty Day -> NonEmpty Day) -> NonEmpty Day -> NonEmpty Day
    forall a b. (a -> b) -> a -> b
    $ NonEmpty Day -> NonEmpty Day
    forall a. Ord a => NonEmpty a -> NonEmpty a
    NE.sort NonEmpty Day
    xs

    -- | Construct a 'DayPartition' from a list of period boundary dates (start dates plus a final exclusive end date),
    -- if it's a non-empty list.
    boundariesToMaybeDayPartition :: [Day] -> Maybe DayPartition
    boundariesToMaybeDayPartition :: [Day] -> Maybe DayPartition
    boundariesToMaybeDayPartition = (NonEmpty Day -> DayPartition)
    -> Maybe (NonEmpty Day) -> Maybe DayPartition
    forall a b. (a -> b) -> Maybe a -> Maybe b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap NonEmpty Day -> DayPartition
    boundariesToDayPartition (Maybe (NonEmpty Day) -> Maybe DayPartition)
    -> ([Day] -> Maybe (NonEmpty Day)) -> [Day] -> Maybe DayPartition
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [Day] -> Maybe (NonEmpty Day)
    forall a. [a] -> Maybe (NonEmpty a)
    NE.nonEmpty

    -- conversions:

    -- | Convert 'DayPartition' to a non-empty list of period start and end dates (both inclusive).
    -- Each end date will be one day before the next period's start date.
    dayPartitionToNonEmpty :: DayPartition -> NonEmpty (Day, Day)
    dayPartitionToNonEmpty :: DayPartition -> NonEmpty (Day, Day)
    dayPartitionToNonEmpty (DayPartition PeriodData Day
    xs) = [(Day, Day)] -> NonEmpty (Day, Day)
    forall a. HasCallStack => [a] -> NonEmpty a
    NE.fromList ([(Day, Day)] -> NonEmpty (Day, Day))
    -> ((Day, [(Day, Day)]) -> [(Day, Day)])
    -> (Day, [(Day, Day)])
    -> NonEmpty (Day, Day)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Day, [(Day, Day)]) -> [(Day, Day)]
    forall a b. (a, b) -> b
    snd ((Day, [(Day, Day)]) -> NonEmpty (Day, Day))
    -> (Day, [(Day, Day)]) -> NonEmpty (Day, Day)
    forall a b. (a -> b) -> a -> b
    $ PeriodData Day -> (Day, [(Day, Day)])
    forall a. PeriodData a -> (a, [(Day, a)])
    periodDataToList PeriodData Day
    xs  -- Constructors guarantee this is non-empty

    -- | Convert 'DayPartition' to a list (which will always be non-empty) of period start and end dates (both inclusive).
    -- Each end date will be one day before the next period's start date.
    dayPartitionToList :: DayPartition -> [(Day, Day)]
    dayPartitionToList :: DayPartition -> [(Day, Day)]
    dayPartitionToList = NonEmpty (Day, Day) -> [(Day, Day)]
    forall a. NonEmpty a -> [a]
    NE.toList (NonEmpty (Day, Day) -> [(Day, Day)])
    -> (DayPartition -> NonEmpty (Day, Day))
    -> DayPartition
    -> [(Day, Day)]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . DayPartition -> NonEmpty (Day, Day)
    dayPartitionToNonEmpty

    -- | Convert 'DayPartition' to a list of 'DateSpan's.
    -- Each span will end one day before the next span begins
    -- (the span's exclusive end date will be equal to the next span's start date).
    dayPartitionToDateSpans :: DayPartition -> [DateSpan]
    dayPartitionToDateSpans :: DayPartition -> [DateSpan]
    dayPartitionToDateSpans = ((Day, Day) -> DateSpan) -> [(Day, Day)] -> [DateSpan]
    forall a b. (a -> b) -> [a] -> [b]
    map (Day, Day) -> DateSpan
    toDateSpan ([(Day, Day)] -> [DateSpan])
    -> (DayPartition -> [(Day, Day)]) -> DayPartition -> [DateSpan]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . DayPartition -> [(Day, Day)]
    dayPartitionToList
      where
        toDateSpan :: (Day, Day) -> DateSpan
    toDateSpan (Day
    s, Day
    e) = Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan (Day -> Maybe EFDay
    toEFDay Day
    s) (Day -> Maybe EFDay
    toEFDay (Day -> Maybe EFDay) -> Day -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Integer -> Day -> Day
    addDays Integer
    1 Day
    e)
        toEFDay :: Day -> Maybe EFDay
    toEFDay = EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> (Day -> EFDay) -> Day -> Maybe EFDay
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Day -> EFDay
    Exact

    -- Convert a 'Maybe DayPartition' to a list of one or more 'DateSpans'.
    -- Each span will end one day before the next span begins
    -- (the span's exclusive end date will be equal to the next span's start date).
    -- If given Nothing, it returns a single open-ended span.
    maybeDayPartitionToDateSpans :: Maybe DayPartition -> [DateSpan]
    maybeDayPartitionToDateSpans :: Maybe DayPartition -> [DateSpan]
    maybeDayPartitionToDateSpans = [DateSpan]
    -> (DayPartition -> [DateSpan]) -> Maybe DayPartition -> [DateSpan]
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe [Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan Maybe EFDay
    forall a. Maybe a
    Nothing Maybe EFDay
    forall a. Maybe a
    Nothing] DayPartition -> [DateSpan]
    dayPartitionToDateSpans

    -- operations:

    -- | Check that a DayPartition has been constructed correctly,
    -- with internal invariants satisfied, as well as the external ones described in 'DayPartition'.
    -- Internally, all constructors must guarantee:
    -- 1. The pdperiods map contains at least one key and value.
    -- 2. The value stored in pdpre is one day before pdperiods' smallest key.
    -- 3. Each value stored in pdperiods is one day before the next largest key,
    --    (except for the value associated with the largest key).
    isValidDayPartition :: DayPartition -> Bool
    isValidDayPartition :: DayPartition -> Bool
    isValidDayPartition (DayPartition PeriodData Day
    pd) = case [(Day, Day)]
    ds of
      [] -> Bool
    False
      [(Day, Day)]
    xs -> [Bool] -> Bool
    forall (t :: * -> *). Foldable t => t Bool -> Bool
    and ([Bool] -> Bool) -> [Bool] -> Bool
    forall a b. (a -> b) -> a -> b
    $ ((Day, Day) -> (Day, Day) -> Bool)
    -> [(Day, Day)] -> [(Day, Day)] -> [Bool]
    forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
    zipWith (Day, Day) -> (Day, Day) -> Bool
    forall {a} {b}. (a, Day) -> (Day, b) -> Bool
    isContiguous ((Day
    nulldate, Day
    h) (Day, Day) -> [(Day, Day)] -> [(Day, Day)]
    forall a. a -> [a] -> [a]
    : [(Day, Day)]
    xs) [(Day, Day)]
    xs
     where
      (Day
    h, [(Day, Day)]
    ds) = PeriodData Day -> (Day, [(Day, Day)])
    forall a. PeriodData a -> (a, [(Day, a)])
    periodDataToList PeriodData Day
    pd
      isContiguous :: (a, Day) -> (Day, b) -> Bool
    isContiguous (a
    _, Day
    e) (Day
    s, b
    _) = Integer -> Day -> Day
    addDays Integer
    1 Day
    e Day -> Day -> Bool
    forall a. Eq a => a -> a -> Bool
    == Day
    s

    -- | Return the union of two 'DayPartition's if that is a valid 'DayPartition',
    -- or 'Nothing' otherwise.
    unionDayPartitions :: DayPartition -> DayPartition -> Maybe DayPartition
    unionDayPartitions :: DayPartition -> DayPartition -> Maybe DayPartition
    unionDayPartitions (DayPartition (PeriodData Day
    h Map Day Day
    as)) (DayPartition (PeriodData Day
    h' Map Day Day
    as')) =
      if Map Day Day -> Map Day Day -> Bool
    forall {k} {b}. (Ord k, Eq b) => Map k b -> Map k b -> Bool
    equalIntersection Map Day Day
    as Map Day Day
    as' Bool -> Bool -> Bool
    && DayPartition -> Bool
    isValidDayPartition DayPartition
    union then DayPartition -> Maybe DayPartition
    forall a. a -> Maybe a
    Just DayPartition
    union else Maybe DayPartition
    forall a. Maybe a
    Nothing
     where
      union :: DayPartition
    union = PeriodData Day -> DayPartition
    DayPartition (PeriodData Day -> DayPartition)
    -> (Map Day Day -> PeriodData Day) -> Map Day Day -> DayPartition
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Day -> Map Day Day -> PeriodData Day
    forall a. a -> Map Day a -> PeriodData a
    PeriodData (Day -> Day -> Day
    forall a. Ord a => a -> a -> a
    min Day
    h Day
    h') (Map Day Day -> DayPartition) -> Map Day Day -> DayPartition
    forall a b. (a -> b) -> a -> b
    $ Map Day Day
    as Map Day Day -> Map Day Day -> Map Day Day
    forall a. Semigroup a => a -> a -> a
    <> Map Day Day
    as'
      equalIntersection :: Map k b -> Map k b -> Bool
    equalIntersection Map k b
    x Map k b
    y = Map k Bool -> Bool
    forall (t :: * -> *). Foldable t => t Bool -> Bool
    and (Map k Bool -> Bool) -> Map k Bool -> Bool
    forall a b. (a -> b) -> a -> b
    $ (b -> b -> Bool) -> Map k b -> Map k b -> Map k Bool
    forall k a b c.
    Ord k =>
    (a -> b -> c) -> Map k a -> Map k b -> Map k c
    M.intersectionWith b -> b -> Bool
    forall a. Eq a => a -> a -> Bool
    (==) Map k b
    x Map k b
    y

    -- | Get this DayPartition's overall start date and end date (both inclusive).
    dayPartitionStartEnd :: DayPartition -> (Day, Day)
    dayPartitionStartEnd :: DayPartition -> (Day, Day)
    dayPartitionStartEnd (DayPartition (PeriodData Day
    _ Map Day Day
    ds)) =
      -- Guaranteed not to error because the IntMap is non-empty.
      ((Day, Day) -> Day
    forall a b. (a, b) -> a
    fst ((Day, Day) -> Day) -> (Day, Day) -> Day
    forall a b. (a -> b) -> a -> b
    $ Map Day Day -> (Day, Day)
    forall k a. Map k a -> (k, a)
    M.findMin Map Day Day
    ds, (Day, Day) -> Day
    forall a b. (a, b) -> b
    snd ((Day, Day) -> Day) -> (Day, Day) -> Day
    forall a b. (a -> b) -> a -> b
    $ Map Day Day -> (Day, Day)
    forall k a. Map k a -> (k, a)
    M.findMax Map Day Day
    ds)

    -- | Find the start and end dates of the period within a 'DayPartition' which contains a given day.
    -- If the day is after the end of the last period, it is assumed to be within the last period.
    -- If the day is before the start of the first period (ie, in the historical period),
    -- only the historical period's end date is returned.
    dayPartitionFind :: Day -> DayPartition -> (Maybe Day, Day)
    dayPartitionFind :: Day -> DayPartition -> (Maybe Day, Day)
    dayPartitionFind Day
    d (DayPartition PeriodData Day
    xs) = Day -> PeriodData Day -> (Maybe Day, Day)
    forall a. Day -> PeriodData a -> (Maybe Day, a)
    lookupPeriodDataOrHistorical Day
    d PeriodData Day
    xs

    -- | Split a 'DateSpan' into a 'DayPartition' consisting of consecutive exact
    -- spans of the specified Interval, or `Nothing` if the span is invalid.
    -- If no interval is specified, the original span is returned.
    -- If the original span is the null date span, ie unbounded, `Nothing` is returned.
    -- If the original span is empty, eg if the end date is <= the start date, `Nothing` is returned.
    --
    -- ==== Date adjustment
    -- Some intervals respect the "adjust" flag (years, quarters, months, weeks, every Nth weekday
    -- of month seem to be the ones that need it). This will move the start date earlier, if needed,
    -- to the previous natural interval boundary (first of year, first of quarter, first of month,
    -- monday, previous Nth weekday of month). Related: #1982 #2218
    --
    -- The end date is always moved later if needed to the next natural interval boundary,
    -- so that the last period is the same length as the others.
    --
    -- ==== Examples
    -- >>> let t i y1 m1 d1 y2 m2 d2 = fmap dayPartitionToNonEmpty . splitSpan True i $ DateSpan (Just $ Flex $ fromGregorian y1 m1 d1) (Just $ Flex $ fromGregorian y2 m2 d2)
    -- >>> t NoInterval 2008 01 01 2009 01 01
    -- Just ((2008-01-01,2008-12-31) :| [])
    -- >>> t (Quarters 1) 2008 01 01 2009 01 01
    -- Just ((2008-01-01,2008-03-31) :| [(2008-04-01,2008-06-30),(2008-07-01,2008-09-30),(2008-10-01,2008-12-31)])
    -- >>> splitSpan True (Quarters 1) nulldatespan
    -- Nothing
    -- >>> t (Days 1) 2008 01 01 2008 01 01  -- an empty datespan
    -- Nothing
    -- >>> t (Quarters 1) 2008 01 01 2008 01 01
    -- Nothing
    -- >>> t (Months 1) 2008 01 01 2008 04 01
    -- Just ((2008-01-01,2008-01-31) :| [(2008-02-01,2008-02-29),(2008-03-01,2008-03-31)])
    -- >>> t (Months 2) 2008 01 01 2008 04 01
    -- Just ((2008-01-01,2008-02-29) :| [(2008-03-01,2008-04-30)])
    -- >>> t (Weeks 1) 2008 01 01 2008 01 15
    -- Just ((2007-12-31,2008-01-06) :| [(2008-01-07,2008-01-13),(2008-01-14,2008-01-20)])
    -- >>> t (Weeks 2) 2008 01 01 2008 01 15
    -- Just ((2007-12-31,2008-01-13) :| [(2008-01-14,2008-01-27)])
    -- >>> t (MonthDay 2) 2008 01 01 2008 04 01
    -- Just ((2008-01-02,2008-02-01) :| [(2008-02-02,2008-03-01),(2008-03-02,2008-04-01)])
    -- >>> t (NthWeekdayOfMonth 2 4) 2011 01 01 2011 02 15
    -- Just ((2010-12-09,2011-01-12) :| [(2011-01-13,2011-02-09),(2011-02-10,2011-03-09)])
    -- >>> t (DaysOfWeek [2]) 2011 01 01 2011 01 15
    -- Just ((2010-12-28,2011-01-03) :| [(2011-01-04,2011-01-10),(2011-01-11,2011-01-17)])
    -- >>> t (MonthAndDay 11 29) 2012 10 01 2013 10 15
    -- Just ((2012-11-29,2013-11-28) :| [])
    splitSpan :: Bool -> Interval -> DateSpan -> Maybe DayPartition
    splitSpan :: Bool -> Interval -> DateSpan -> Maybe DayPartition
    splitSpan Bool
    _      Interval
    _                        (DateSpan Maybe EFDay
    Nothing Maybe EFDay
    Nothing) = Maybe DayPartition
    forall a. Maybe a
    Nothing
    splitSpan Bool
    _      Interval
    _                        DateSpan
    ds | DateSpan -> Bool
    isEmptySpan DateSpan
    ds = Maybe DayPartition
    forall a. Maybe a
    Nothing
    splitSpan Bool
    _      Interval
    NoInterval               (DateSpan (Just EFDay
    s) (Just EFDay
    e)) = DayPartition -> Maybe DayPartition
    forall a. a -> Maybe a
    Just (DayPartition -> Maybe DayPartition)
    -> DayPartition -> Maybe DayPartition
    forall a b. (a -> b) -> a -> b
    $ NonEmpty Day -> DayPartition
    boundariesToDayPartition (EFDay -> Day
    fromEFDay EFDay
    s Day -> [Day] -> NonEmpty Day
    forall a. a -> [a] -> NonEmpty a
    :| [EFDay -> Day
    fromEFDay EFDay
    e])
    splitSpan Bool
    _      Interval
    NoInterval               DateSpan
    _  = Maybe DayPartition
    forall a. Maybe a
    Nothing
    splitSpan Bool
    _      (Days Int
    n)                 DateSpan
    ds = (Day -> Day)
    -> (Integer -> Day -> Day) -> Int -> DateSpan -> Maybe DayPartition
    splitspan Day -> Day
    forall a. a -> a
    id Integer -> Day -> Day
    addDays Int
    n DateSpan
    ds
    splitSpan Bool
    adjust (Weeks Int
    n)                DateSpan
    ds = (Day -> Day)
    -> (Integer -> Day -> Day) -> Int -> DateSpan -> Maybe DayPartition
    splitspan (if Bool
    adjust then Day -> Day
    startofweek    else Day -> Day
    forall a. a -> a
    id) Integer -> Day -> Day
    addDays                 (Int
    7Int -> Int -> Int
    forall a. Num a => a -> a -> a
    *Int
    n) DateSpan
    ds
    splitSpan Bool
    adjust (Months Int
    n)               DateSpan
    ds = (Day -> Day)
    -> (Integer -> Day -> Day) -> Int -> DateSpan -> Maybe DayPartition
    splitspan (if Bool
    adjust then Day -> Day
    startofmonth   else Day -> Day
    forall a. a -> a
    id) Integer -> Day -> Day
    addGregorianMonthsClip  Int
    n     DateSpan
    ds
    splitSpan Bool
    adjust (Quarters Int
    n)             DateSpan
    ds = (Day -> Day)
    -> (Integer -> Day -> Day) -> Int -> DateSpan -> Maybe DayPartition
    splitspan (if Bool
    adjust then Day -> Day
    startofquarter else Day -> Day
    forall a. a -> a
    id) Integer -> Day -> Day
    addGregorianMonthsClip  (Int
    3Int -> Int -> Int
    forall a. Num a => a -> a -> a
    *Int
    n) DateSpan
    ds
    splitSpan Bool
    adjust (Years Int
    n)                DateSpan
    ds = (Day -> Day)
    -> (Integer -> Day -> Day) -> Int -> DateSpan -> Maybe DayPartition
    splitspan (if Bool
    adjust then Day -> Day
    startofyear    else Day -> Day
    forall a. a -> a
    id) Integer -> Day -> Day
    addGregorianYearsClip   Int
    n     DateSpan
    ds
    splitSpan Bool
    adjust (NthWeekdayOfMonth Int
    n Int
    wd) DateSpan
    ds = (Day -> Day)
    -> (Integer -> Day -> Day) -> Int -> DateSpan -> Maybe DayPartition
    splitspan (Int -> Int -> Day -> Day
    startWeekdayOfMonth Int
    n Int
    wd)              Integer -> Day -> Day
    advancemonths           Int
    1     DateSpan
    ds
      where
        startWeekdayOfMonth :: Int -> Int -> Day -> Day
    startWeekdayOfMonth = if Bool
    adjust then Int -> Int -> Day -> Day
    prevNthWeekdayOfMonth else Int -> Int -> Day -> Day
    nextNthWeekdayOfMonth
        advancemonths :: Integer -> Day -> Day
    advancemonths Integer
    0 = Day -> Day
    forall a. a -> a
    id
        advancemonths Integer
    m = Int -> Int -> Day -> Day
    advanceToNthWeekday Int
    n Int
    wd (Day -> Day) -> (Day -> Day) -> Day -> Day
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Day -> Day
    startofmonth (Day -> Day) -> (Day -> Day) -> Day -> Day
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Integer -> Day -> Day
    addGregorianMonthsClip Integer
    m
    splitSpan Bool
    _      (MonthDay Int
    dom)           DateSpan
    ds = (Day -> Day)
    -> (Integer -> Day -> Day) -> Int -> DateSpan -> Maybe DayPartition
    splitspan (Int -> Day -> Day
    nextnthdayofmonth Int
    dom) (Int -> Integer -> Day -> Day
    addGregorianMonthsToMonthday Int
    dom) Int
    1 DateSpan
    ds
    splitSpan Bool
    _      (MonthAndDay Int
    m Int
    d)        DateSpan
    ds = (Day -> Day)
    -> (Integer -> Day -> Day) -> Int -> DateSpan -> Maybe DayPartition
    splitspan (Int -> Int -> Day -> Day
    nextmonthandday Int
    m Int
    d)   Integer -> Day -> Day
    addGregorianYearsClip              Int
    1 DateSpan
    ds
    splitSpan Bool
    _      (DaysOfWeek [])          DateSpan
    _  = Maybe DayPartition
    forall a. Maybe a
    Nothing
    splitSpan Bool
    _      (DaysOfWeek days :: [Int]
    days@(Int
    n:[Int]
    _))  DateSpan
    ds = do
        (s, e) <- (Day -> Day) -> (Day -> Day) -> DateSpan -> Maybe (Day, Day)
    dateSpanSplitLimits (Int -> Day -> Day
    nthdayofweekcontaining Int
    n) Day -> Day
    nextday DateSpan
    ds
        let -- can't show this when debugging, it'll hang:
            bdrys = (Integer -> [Day]) -> [Integer] -> [Day]
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap (\Integer
    d -> (Day -> Day) -> [Day] -> [Day]
    forall a b. (a -> b) -> [a] -> [b]
    map (Integer -> Day -> Day
    addDays Integer
    d) [Day]
    starts) [Integer
    0,Integer
    7..]
            -- The first representative of each weekday
            starts = (Int -> Day) -> [Int] -> [Day]
    forall a b. (a -> b) -> [a] -> [b]
    map (\Int
    d -> Integer -> Day -> Day
    addDays (Int -> Integer
    forall a. Integral a => a -> Integer
    toInteger (Int -> Integer) -> Int -> Integer
    forall a b. (a -> b) -> a -> b
    $ Int
    d Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    n) (Day -> Day) -> Day -> Day
    forall a b. (a -> b) -> a -> b
    $ Int -> Day -> Day
    nthdayofweekcontaining Int
    n Day
    s) [Int]
    days
        spansFromBoundaries e bdrys

    -- | Fill in missing start/end dates for calculating 'splitSpan'.
    dateSpanSplitLimits :: (Day -> Day) -> (Day -> Day) -> DateSpan -> Maybe (Day, Day)
    dateSpanSplitLimits :: (Day -> Day) -> (Day -> Day) -> DateSpan -> Maybe (Day, Day)
    dateSpanSplitLimits Day -> Day
    _     Day -> Day
    _    (DateSpan Maybe EFDay
    Nothing   Maybe EFDay
    Nothing) = Maybe (Day, Day)
    forall a. Maybe a
    Nothing
    dateSpanSplitLimits Day -> Day
    _     Day -> Day
    _    DateSpan
    ds | DateSpan -> Bool
    isEmptySpan DateSpan
    ds          = Maybe (Day, Day)
    forall a. Maybe a
    Nothing
    dateSpanSplitLimits Day -> Day
    start Day -> Day
    _    (DateSpan (Just EFDay
    s) (Just EFDay
    e)) = (Day, Day) -> Maybe (Day, Day)
    forall a. a -> Maybe a
    Just (Day -> Day
    start (Day -> Day) -> Day -> Day
    forall a b. (a -> b) -> a -> b
    $ EFDay -> Day
    fromEFDay EFDay
    s, EFDay -> Day
    fromEFDay EFDay
    e)
    dateSpanSplitLimits Day -> Day
    start Day -> Day
    next (DateSpan (Just EFDay
    s) Maybe EFDay
    Nothing)  = (Day, Day) -> Maybe (Day, Day)
    forall a. a -> Maybe a
    Just (Day -> Day
    start (Day -> Day) -> Day -> Day
    forall a b. (a -> b) -> a -> b
    $ EFDay -> Day
    fromEFDay EFDay
    s, Day -> Day
    next (Day -> Day) -> Day -> Day
    forall a b. (a -> b) -> a -> b
    $ Day -> Day
    start (Day -> Day) -> Day -> Day
    forall a b. (a -> b) -> a -> b
    $ EFDay -> Day
    fromEFDay EFDay
    s)
    dateSpanSplitLimits Day -> Day
    start Day -> Day
    next (DateSpan Maybe EFDay
    Nothing  (Just EFDay
    e)) = (Day, Day) -> Maybe (Day, Day)
    forall a. a -> Maybe a
    Just (Day -> Day
    start (Day -> Day) -> Day -> Day
    forall a b. (a -> b) -> a -> b
    $ EFDay -> Day
    fromEFDay EFDay
    e, Day -> Day
    next (Day -> Day) -> Day -> Day
    forall a b. (a -> b) -> a -> b
    $ Day -> Day
    start (Day -> Day) -> Day -> Day
    forall a b. (a -> b) -> a -> b
    $ EFDay -> Day
    fromEFDay EFDay
    e)

    -- Split the given span into exact spans using the provided helper functions:
    --
    -- 1. The start function is used to adjust the provided span's start date to get the first sub-span's start date.
    --
    -- 2. The next function is used to calculate subsequent sub-spans' start dates, possibly with stride increased by a multiplier.
    --    It should handle spans of varying length, eg when splitting on "every 31st of month",
    --    it adjusts to 28/29/30 in short months but returns to 31 in the long months.
    splitspan :: (Day -> Day) -> (Integer -> Day -> Day) -> Int -> DateSpan -> Maybe DayPartition
    splitspan :: (Day -> Day)
    -> (Integer -> Day -> Day) -> Int -> DateSpan -> Maybe DayPartition
    splitspan Day -> Day
    start Integer -> Day -> Day
    next Int
    mult DateSpan
    ds = do
        (s, e) <- (Day -> Day) -> (Day -> Day) -> DateSpan -> Maybe (Day, Day)
    dateSpanSplitLimits Day -> Day
    start (Integer -> Day -> Day
    next (Int -> Integer
    forall a. Integral a => a -> Integer
    toInteger Int
    mult)) DateSpan
    ds
        let bdrys = (Int -> Day -> Day) -> [Int] -> Day -> [Day]
    forall (t :: * -> *) (m :: * -> *) a b.
    (Traversable t, Monad m) =>
    (a -> m b) -> t a -> m (t b)
    forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
    mapM (Integer -> Day -> Day
    next (Integer -> Day -> Day) -> (Int -> Integer) -> Int -> Day -> Day
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Int -> Integer
    forall a. Integral a => a -> Integer
    toInteger) [Int
    0,Int
    mult..] (Day -> [Day]) -> Day -> [Day]
    forall a b. (a -> b) -> a -> b
    $ Day -> Day
    start Day
    s
        spansFromBoundaries e bdrys

    -- | Construct a list of exact 'DateSpan's from a list of boundaries, which fit within a given range.
    spansFromBoundaries :: Day -> [Day] -> Maybe DayPartition
    spansFromBoundaries :: Day -> [Day] -> Maybe DayPartition
    spansFromBoundaries Day
    _ []     = Maybe DayPartition
    forall a. Maybe a
    Nothing
    spansFromBoundaries Day
    e (Day
    x:[Day]
    _)  | Day
    x Day -> Day -> Bool
    forall a. Ord a => a -> a -> Bool
    >= Day
    e = Maybe DayPartition
    forall a. Maybe a
    Nothing
    spansFromBoundaries Day
    e (Day
    x:[Day]
    xs) = DayPartition -> Maybe DayPartition
    forall a. a -> Maybe a
    Just (DayPartition -> Maybe DayPartition)
    -> (NonEmpty Day -> DayPartition)
    -> NonEmpty Day
    -> Maybe DayPartition
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . NonEmpty Day -> DayPartition
    boundariesToDayPartition (NonEmpty Day -> Maybe DayPartition)
    -> NonEmpty Day -> Maybe DayPartition
    forall a b. (a -> b) -> a -> b
    $ (Day -> Bool) -> NonEmpty Day -> NonEmpty Day
    forall a. (a -> Bool) -> NonEmpty a -> NonEmpty a
    takeUntilFailsNE (Day -> Day -> Bool
    forall a. Ord a => a -> a -> Bool
    <Day
    e) (Day
    xDay -> [Day] -> NonEmpty Day
    forall a. a -> [a] -> NonEmpty a
    :|[Day]
    xs)

    -- | Get the natural start for the given interval that falls on or before the given day,
    -- when applicable. Works for Weeks, Months, Quarters, Years, eg.
    intervalBoundaryBefore :: Interval -> Day -> Day
    intervalBoundaryBefore :: Interval -> Day -> Day
    intervalBoundaryBefore Interval
    i Day
    d =
      case DayPartition -> NonEmpty (Day, Day)
    dayPartitionToNonEmpty (DayPartition -> NonEmpty (Day, Day))
    -> Maybe DayPartition -> Maybe (NonEmpty (Day, Day))
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Bool -> Interval -> DateSpan -> Maybe DayPartition
    splitSpan Bool
    True Interval
    i (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    d) (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> (Day -> EFDay) -> Day -> Maybe EFDay
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Day -> EFDay
    Exact (Day -> Maybe EFDay) -> Day -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Integer -> Day -> Day
    addDays Integer
    1 Day
    d)) of
        Just ((Day
    start, Day
    _) :| [(Day, Day)]
    _ ) -> Day
    start
        Maybe (NonEmpty (Day, Day))
    _ -> Day
    d

    -- tests:

    tests_DayPartition :: TestTree
    tests_DayPartition =
      String -> [TestTree] -> TestTree
    testGroup String
    "splitSpan" [
          String -> Assertion -> TestTree
    testCase String
    "weekday" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
            (DayPartition -> NonEmpty (Day, Day))
    -> Maybe DayPartition -> Maybe (NonEmpty (Day, Day))
    forall a b. (a -> b) -> Maybe a -> Maybe b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap DayPartition -> NonEmpty (Day, Day)
    dayPartitionToNonEmpty (Bool -> Interval -> DateSpan -> Maybe DayPartition
    splitSpan Bool
    False ([Int] -> Interval
    DaysOfWeek [Int
    1..Int
    5]) (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact (Day -> EFDay) -> Day -> EFDay
    forall a b. (a -> b) -> a -> b
    $ Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    01) (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact (Day -> EFDay) -> Day -> EFDay
    forall a b. (a -> b) -> a -> b
    $ Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    08)))
              Maybe (NonEmpty (Day, Day))
    -> Maybe (NonEmpty (Day, Day)) -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= NonEmpty (Day, Day) -> Maybe (NonEmpty (Day, Day))
    forall a. a -> Maybe a
    Just ( (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    06 Int
    28, Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    06 Int
    28) (Day, Day) -> [(Day, Day)] -> NonEmpty (Day, Day)
    forall a. a -> [a] -> NonEmpty a
    :|
                       [ (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    06 Int
    29, Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    06 Int
    29)
                       , (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    06 Int
    30, Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    06 Int
    30)
                       , (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    01, Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    01)
                       , (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    02, Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    04)
                       -- next week
                       , (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    05, Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    05)
                       , (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    06, Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    06)
                       , (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    07, Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    07)
                       ])

            (DayPartition -> NonEmpty (Day, Day))
    -> Maybe DayPartition -> Maybe (NonEmpty (Day, Day))
    forall a b. (a -> b) -> Maybe a -> Maybe b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap DayPartition -> NonEmpty (Day, Day)
    dayPartitionToNonEmpty (Bool -> Interval -> DateSpan -> Maybe DayPartition
    splitSpan Bool
    False ([Int] -> Interval
    DaysOfWeek [Int
    1, Int
    5]) (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact (Day -> EFDay) -> Day -> EFDay
    forall a b. (a -> b) -> a -> b
    $ Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    01) (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact (Day -> EFDay) -> Day -> EFDay
    forall a b. (a -> b) -> a -> b
    $ Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    08)))
              Maybe (NonEmpty (Day, Day))
    -> Maybe (NonEmpty (Day, Day)) -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= NonEmpty (Day, Day) -> Maybe (NonEmpty (Day, Day))
    forall a. a -> Maybe a
    Just ( (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    06 Int
    28, Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    01) (Day, Day) -> [(Day, Day)] -> NonEmpty (Day, Day)
    forall a. a -> [a] -> NonEmpty a
    :|
                       [ (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    02, Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    04)
                       -- next week
                       , (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    05, Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    07 Int
    08)
                       ])

        , String -> Assertion -> TestTree
    testCase String
    "match dayOfWeek" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
            let dayofweek :: Int -> DateSpan -> Maybe DayPartition
    dayofweek Int
    n = (Day -> Day)
    -> (Integer -> Day -> Day) -> Int -> DateSpan -> Maybe DayPartition
    splitspan (Int -> Day -> Day
    nthdayofweekcontaining Int
    n) (\Integer
    w -> (if Integer
    w Integer -> Integer -> Bool
    forall a. Eq a => a -> a -> Bool
    == Integer
    0 then Day -> Day
    forall a. a -> a
    id else Int -> (Day -> Day) -> Day -> Day
    forall a. Int -> (a -> a) -> a -> a
    applyN (Int
    nInt -> Int -> Int
    forall a. Num a => a -> a -> a
    -Int
    1) Day -> Day
    nextday (Day -> Day) -> (Day -> Day) -> Day -> Day
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Int -> (Day -> Day) -> Day -> Day
    forall a. Int -> (a -> a) -> a -> a
    applyN (Integer -> Int
    forall a. Num a => Integer -> a
    fromInteger Integer
    w) Day -> Day
    nextweek)) Int
    1
                matchdow :: DateSpan -> Int -> Assertion
    matchdow DateSpan
    ds Int
    day = Bool -> Interval -> DateSpan -> Maybe DayPartition
    splitSpan Bool
    False ([Int] -> Interval
    DaysOfWeek [Int
    day]) DateSpan
    ds Maybe DayPartition -> Maybe DayPartition -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Int -> DateSpan -> Maybe DayPartition
    dayofweek Int
    day DateSpan
    ds
                ys2021 :: Day
    ys2021 = Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    01 Int
    01
                ye2021 :: Day
    ye2021 = Integer -> Int -> Int -> Day
    fromGregorian Integer
    2021 Int
    12 Int
    31
                ys2022 :: Day
    ys2022 = Integer -> Int -> Int -> Day
    fromGregorian Integer
    2022 Int
    01 Int
    01
            (Int -> Assertion) -> [Int] -> Assertion
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ (DateSpan -> Int -> Assertion
    matchdow (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    ys2021) (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    ye2021))) [Int
    1..Int
    7]
            (Int -> Assertion) -> [Int] -> Assertion
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ (DateSpan -> Int -> Assertion
    matchdow (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    ys2021) (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    ys2022))) [Int
    1..Int
    7]
            (Int -> Assertion) -> [Int] -> Assertion
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ (DateSpan -> Int -> Assertion
    matchdow (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    ye2021) (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    ys2022))) [Int
    1..Int
    7]

            (Int -> Assertion) -> [Int] -> Assertion
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ (DateSpan -> Int -> Assertion
    matchdow (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    ye2021) Maybe EFDay
    forall a. Maybe a
    Nothing)) [Int
    1..Int
    7]
            (Int -> Assertion) -> [Int] -> Assertion
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ (DateSpan -> Int -> Assertion
    matchdow (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    ys2022) Maybe EFDay
    forall a. Maybe a
    Nothing)) [Int
    1..Int
    7]

            (Int -> Assertion) -> [Int] -> Assertion
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ (DateSpan -> Int -> Assertion
    matchdow (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan Maybe EFDay
    forall a. Maybe a
    Nothing (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    ye2021))) [Int
    1..Int
    7]
            (Int -> Assertion) -> [Int] -> Assertion
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ (DateSpan -> Int -> Assertion
    matchdow (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan Maybe EFDay
    forall a. Maybe a
    Nothing (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    ys2022))) [Int
    1..Int
    7]

        ]