@@ -70,8 +70,6 @@ module Hledger.Data.Dates (
7070 daysSpan ,
7171 latestSpanContaining ,
7272 smartdate ,
73- splitSpan ,
74- spansFromBoundaries ,
7573 groupByDateSpan ,
7674 fixSmartDate ,
7775 fixSmartDateStr ,
@@ -80,9 +78,22 @@ module Hledger.Data.Dates (
8078 yearp ,
8179 daysInSpan ,
8280
83- tests_Dates
84- , intervalBoundaryBefore )
85- where
81+ -- Temp exports
82+ startofyear ,
83+ startofquarter ,
84+ startofmonth ,
85+ startofweek ,
86+ nextday ,
87+ nextweek ,
88+ nextmonthandday ,
89+ nextnthdayofmonth ,
90+ prevNthWeekdayOfMonth ,
91+ nthdayofweekcontaining ,
92+ addGregorianMonthsToMonthday ,
93+ advanceToNthWeekday ,
94+ nextNthWeekdayOfMonth ,
95+ isEmptySpan
96+ ) where
8697
8798import Prelude hiding (Applicative (.. ))
8899import Control.Applicative (Applicative (.. ))
@@ -188,76 +199,6 @@ spanYears (DateSpan ma mb) = mapMaybe (fmap (first3 . toGregorian. fromEFDay)) [
188199spansSpan :: [DateSpan ] -> DateSpan
189200spansSpan spans = DateSpan (spanStartDate =<< headMay spans) (spanEndDate =<< lastMay spans)
190201
191- -- | Split a DateSpan into consecutive exact spans of the specified Interval.
192- -- If no interval is specified, the original span is returned.
193- -- If the original span is the null date span, ie unbounded, the null date span is returned.
194- -- If the original span is empty, eg if the end date is <= the start date, no spans are returned.
195- --
196- -- ==== Date adjustment
197- -- Some intervals respect the "adjust" flag (years, quarters, months, weeks, every Nth weekday
198- -- of month seem to be the ones that need it). This will move the start date earlier, if needed,
199- -- to the previous natural interval boundary (first of year, first of quarter, first of month,
200- -- monday, previous Nth weekday of month). Related: #1982 #2218
201- --
202- -- The end date is always moved later if needed to the next natural interval boundary,
203- -- so that the last period is the same length as the others.
204- --
205- -- ==== Examples
206- -- >>> let t i y1 m1 d1 y2 m2 d2 = splitSpan True i $ DateSpan (Just $ Flex $ fromGregorian y1 m1 d1) (Just $ Flex $ fromGregorian y2 m2 d2)
207- -- >>> t NoInterval 2008 01 01 2009 01 01
208- -- [DateSpan 2008]
209- -- >>> t (Quarters 1) 2008 01 01 2009 01 01
210- -- [DateSpan 2008Q1,DateSpan 2008Q2,DateSpan 2008Q3,DateSpan 2008Q4]
211- -- >>> splitSpan True (Quarters 1) nulldatespan
212- -- [DateSpan ..]
213- -- >>> t (Days 1) 2008 01 01 2008 01 01 -- an empty datespan
214- -- []
215- -- >>> t (Quarters 1) 2008 01 01 2008 01 01
216- -- []
217- -- >>> t (Months 1) 2008 01 01 2008 04 01
218- -- [DateSpan 2008-01,DateSpan 2008-02,DateSpan 2008-03]
219- -- >>> t (Months 2) 2008 01 01 2008 04 01
220- -- [DateSpan 2008-01-01..2008-02-29,DateSpan 2008-03-01..2008-04-30]
221- -- >>> t (Weeks 1) 2008 01 01 2008 01 15
222- -- [DateSpan 2008-W01,DateSpan 2008-W02,DateSpan 2008-W03]
223- -- >>> t (Weeks 2) 2008 01 01 2008 01 15
224- -- [DateSpan 2007-12-31..2008-01-13,DateSpan 2008-01-14..2008-01-27]
225- -- >>> t (MonthDay 2) 2008 01 01 2008 04 01
226- -- [DateSpan 2008-01-02..2008-02-01,DateSpan 2008-02-02..2008-03-01,DateSpan 2008-03-02..2008-04-01]
227- -- >>> t (NthWeekdayOfMonth 2 4) 2011 01 01 2011 02 15
228- -- [DateSpan 2010-12-09..2011-01-12,DateSpan 2011-01-13..2011-02-09,DateSpan 2011-02-10..2011-03-09]
229- -- >>> t (DaysOfWeek [2]) 2011 01 01 2011 01 15
230- -- [DateSpan 2010-12-28..2011-01-03,DateSpan 2011-01-04..2011-01-10,DateSpan 2011-01-11..2011-01-17]
231- -- >>> t (MonthAndDay 11 29) 2012 10 01 2013 10 15
232- -- [DateSpan 2012-11-29..2013-11-28]
233- --
234- splitSpan :: Bool -> Interval -> DateSpan -> [DateSpan ]
235- splitSpan _ _ (DateSpan Nothing Nothing ) = [DateSpan Nothing Nothing ]
236- splitSpan _ _ ds | isEmptySpan ds = []
237- splitSpan _ _ ds@ (DateSpan (Just s) (Just e)) | s == e = [ds]
238- splitSpan _ NoInterval ds = [ds]
239- splitSpan _ (Days n) ds = splitspan id addDays n ds
240- splitSpan adjust (Weeks n) ds = splitspan (if adjust then startofweek else id ) addDays (7 * n) ds
241- splitSpan adjust (Months n) ds = splitspan (if adjust then startofmonth else id ) addGregorianMonthsClip n ds
242- splitSpan adjust (Quarters n) ds = splitspan (if adjust then startofquarter else id ) addGregorianMonthsClip (3 * n) ds
243- splitSpan adjust (Years n) ds = splitspan (if adjust then startofyear else id ) addGregorianYearsClip n ds
244- splitSpan adjust (NthWeekdayOfMonth n wd) ds = splitspan (if adjust then prevstart else nextstart) advancemonths 1 ds
245- where
246- prevstart = prevNthWeekdayOfMonth n wd
247- nextstart = nextNthWeekdayOfMonth n wd
248- advancemonths 0 = id
249- advancemonths m = advanceToNthWeekday n wd . startofmonth . addGregorianMonthsClip m
250- splitSpan _ (MonthDay dom) ds = splitspan (nextnthdayofmonth dom) (addGregorianMonthsToMonthday dom) 1 ds
251- splitSpan _ (MonthAndDay m d) ds = splitspan (nextmonthandday m d) (addGregorianYearsClip) 1 ds
252- splitSpan _ (DaysOfWeek [] ) ds = [ds]
253- splitSpan _ (DaysOfWeek days@ (n: _)) ds = spansFromBoundaries e bdrys
254- where
255- (s, e) = dateSpanSplitLimits (nthdayofweekcontaining n) nextday ds
256- -- can't show this when debugging, it'll hang:
257- bdrys = concatMap (flip map starts . addDays) [0 ,7 .. ]
258- -- The first representative of each weekday
259- starts = map (\ d -> addDays (toInteger $ d - n) $ nthdayofweekcontaining n s) days
260-
261202-- Like addGregorianMonthsClip, add one month to the given date, clipping when needed
262203-- to fit it within the next month's length. But also, keep a target day of month in mind,
263204-- and revert to that or as close to it as possible in subsequent longer months.
@@ -267,31 +208,6 @@ addGregorianMonthsToMonthday dom n d =
267208 let (y,m,_) = toGregorian $ addGregorianMonthsClip n d
268209 in fromGregorian y m dom
269210
270- -- Split the given span into exact spans using the provided helper functions:
271- --
272- -- 1. The start function is used to adjust the provided span's start date to get the first sub-span's start date.
273- --
274- -- 2. The next function is used to calculate subsequent sub-spans' start dates, possibly with stride increased by a multiplier.
275- -- It should handle spans of varying length, eg when splitting on "every 31st of month",
276- -- it adjusts to 28/29/30 in short months but returns to 31 in the long months.
277- --
278- splitspan :: (Day -> Day ) -> (Integer -> Day -> Day ) -> Int -> DateSpan -> [DateSpan ]
279- splitspan start next mult ds = spansFromBoundaries e bdrys
280- where
281- (s, e) = dateSpanSplitLimits start (next (toInteger mult)) ds
282- bdrys = mapM (next . toInteger ) [0 ,mult.. ] $ start s
283-
284- -- | Fill in missing start/end dates for calculating 'splitSpan'.
285- dateSpanSplitLimits :: (Day -> Day ) -> (Day -> Day ) -> DateSpan -> (Day , Day )
286- dateSpanSplitLimits start _ (DateSpan (Just s) (Just e)) = (start $ fromEFDay s, fromEFDay e)
287- dateSpanSplitLimits start next (DateSpan (Just s) Nothing ) = (start $ fromEFDay s, next $ start $ fromEFDay s)
288- dateSpanSplitLimits start next (DateSpan Nothing (Just e)) = (start $ fromEFDay e, next $ start $ fromEFDay e)
289- dateSpanSplitLimits _ _ (DateSpan Nothing Nothing ) = error' " dateSpanSplitLimits: should not be nulldatespan" -- PARTIAL: This case should have been handled in splitSpan
290-
291- -- | Construct a list of exact 'DateSpan's from a list of boundaries, which fit within a given range.
292- spansFromBoundaries :: Day -> [Day ] -> [DateSpan ]
293- spansFromBoundaries e bdrys = zipWith (DateSpan `on` (Just . Exact )) (takeWhile (< e) bdrys) $ drop 1 bdrys
294-
295211-- | Count the days in a DateSpan, or if it is open-ended return Nothing.
296212daysInSpan :: DateSpan -> Maybe Integer
297213daysInSpan (DateSpan (Just d1) (Just d2)) = Just $ diffDays (fromEFDay d2) (fromEFDay d1)
@@ -669,14 +585,6 @@ thisyear = startofyear
669585nextyear = startofyear . addGregorianYearsClip 1
670586startofyear day = fromGregorian y 1 1 where (y,_,_) = toGregorian day
671587
672- -- Get the natural start for the given interval that falls on or before the given day,
673- -- when applicable. Works for Weeks, Months, Quarters, Years, eg.
674- intervalBoundaryBefore :: Interval -> Day -> Day
675- intervalBoundaryBefore i d =
676- case splitSpan True i (DateSpan (Just $ Exact d) (Just $ Exact $ addDays 1 d)) of
677- (DateSpan (Just start) _: _) -> fromEFDay start
678- _ -> d
679-
680588-- | Find the next occurrence of the specified month and day of month, on or after the given date.
681589-- The month should be 1-12 and the day of month should be 1-31, or an error will be raised.
682590--
@@ -1263,45 +1171,3 @@ emptydatespan = DateSpan (Just $ Exact $ addDays 1 nulldate) (Just $ Exact nulld
12631171
12641172nulldate :: Day
12651173nulldate = fromGregorian 0 1 1
1266-
1267-
1268- -- tests
1269-
1270- tests_Dates = testGroup " Dates"
1271- [ testCase " weekday" $ do
1272- splitSpan False (DaysOfWeek [1 .. 5 ]) (DateSpan (Just $ Exact $ fromGregorian 2021 07 01 ) (Just $ Exact $ fromGregorian 2021 07 08 ))
1273- @?= [ (DateSpan (Just $ Exact $ fromGregorian 2021 06 28 ) (Just $ Exact $ fromGregorian 2021 06 29 ))
1274- , (DateSpan (Just $ Exact $ fromGregorian 2021 06 29 ) (Just $ Exact $ fromGregorian 2021 06 30 ))
1275- , (DateSpan (Just $ Exact $ fromGregorian 2021 06 30 ) (Just $ Exact $ fromGregorian 2021 07 01 ))
1276- , (DateSpan (Just $ Exact $ fromGregorian 2021 07 01 ) (Just $ Exact $ fromGregorian 2021 07 02 ))
1277- , (DateSpan (Just $ Exact $ fromGregorian 2021 07 02 ) (Just $ Exact $ fromGregorian 2021 07 05 ))
1278- -- next week
1279- , (DateSpan (Just $ Exact $ fromGregorian 2021 07 05 ) (Just $ Exact $ fromGregorian 2021 07 06 ))
1280- , (DateSpan (Just $ Exact $ fromGregorian 2021 07 06 ) (Just $ Exact $ fromGregorian 2021 07 07 ))
1281- , (DateSpan (Just $ Exact $ fromGregorian 2021 07 07 ) (Just $ Exact $ fromGregorian 2021 07 08 ))
1282- ]
1283-
1284- splitSpan False (DaysOfWeek [1 , 5 ]) (DateSpan (Just $ Exact $ fromGregorian 2021 07 01 ) (Just $ Exact $ fromGregorian 2021 07 08 ))
1285- @?= [ (DateSpan (Just $ Exact $ fromGregorian 2021 06 28 ) (Just $ Exact $ fromGregorian 2021 07 02 ))
1286- , (DateSpan (Just $ Exact $ fromGregorian 2021 07 02 ) (Just $ Exact $ fromGregorian 2021 07 05 ))
1287- -- next week
1288- , (DateSpan (Just $ Exact $ fromGregorian 2021 07 05 ) (Just $ Exact $ fromGregorian 2021 07 09 ))
1289- ]
1290-
1291- , testCase " match dayOfWeek" $ do
1292- let dayofweek n = splitspan (nthdayofweekcontaining n) (\ w -> (if w == 0 then id else applyN (n- 1 ) nextday . applyN (fromInteger w) nextweek)) 1
1293- matchdow ds day = splitSpan False (DaysOfWeek [day]) ds @?= dayofweek day ds
1294- ys2021 = fromGregorian 2021 01 01
1295- ye2021 = fromGregorian 2021 12 31
1296- ys2022 = fromGregorian 2022 01 01
1297- mapM_ (matchdow (DateSpan (Just $ Exact ys2021) (Just $ Exact ye2021))) [1 .. 7 ]
1298- mapM_ (matchdow (DateSpan (Just $ Exact ys2021) (Just $ Exact ys2022))) [1 .. 7 ]
1299- mapM_ (matchdow (DateSpan (Just $ Exact ye2021) (Just $ Exact ys2022))) [1 .. 7 ]
1300-
1301- mapM_ (matchdow (DateSpan (Just $ Exact ye2021) Nothing )) [1 .. 7 ]
1302- mapM_ (matchdow (DateSpan (Just $ Exact ys2022) Nothing )) [1 .. 7 ]
1303-
1304- mapM_ (matchdow (DateSpan Nothing (Just $ Exact ye2021))) [1 .. 7 ]
1305- mapM_ (matchdow (DateSpan Nothing (Just $ Exact ys2022))) [1 .. 7 ]
1306-
1307- ]
0 commit comments