{-|
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
    (Day
s, Day
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 :: [Day]
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 :: [Day]
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
    Day -> [Day] -> Maybe DayPartition
spansFromBoundaries Day
e [Day]
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
    (Day
s, Day
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 :: [Day]
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
    Day -> [Day] -> Maybe DayPartition
spansFromBoundaries Day
e [Day]
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]

    ]