@@ -70,8 +70,6 @@ module Hledger.Data.Dates (
70
70
daysSpan ,
71
71
latestSpanContaining ,
72
72
smartdate ,
73
- splitSpan ,
74
- spansFromBoundaries ,
75
73
groupByDateSpan ,
76
74
fixSmartDate ,
77
75
fixSmartDateStr ,
@@ -80,9 +78,22 @@ module Hledger.Data.Dates (
80
78
yearp ,
81
79
daysInSpan ,
82
80
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
86
97
87
98
import Prelude hiding (Applicative (.. ))
88
99
import Control.Applicative (Applicative (.. ))
@@ -188,76 +199,6 @@ spanYears (DateSpan ma mb) = mapMaybe (fmap (first3 . toGregorian. fromEFDay)) [
188
199
spansSpan :: [DateSpan ] -> DateSpan
189
200
spansSpan spans = DateSpan (spanStartDate =<< headMay spans) (spanEndDate =<< lastMay spans)
190
201
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
-
261
202
-- Like addGregorianMonthsClip, add one month to the given date, clipping when needed
262
203
-- to fit it within the next month's length. But also, keep a target day of month in mind,
263
204
-- and revert to that or as close to it as possible in subsequent longer months.
@@ -267,31 +208,6 @@ addGregorianMonthsToMonthday dom n d =
267
208
let (y,m,_) = toGregorian $ addGregorianMonthsClip n d
268
209
in fromGregorian y m dom
269
210
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
-
295
211
-- | Count the days in a DateSpan, or if it is open-ended return Nothing.
296
212
daysInSpan :: DateSpan -> Maybe Integer
297
213
daysInSpan (DateSpan (Just d1) (Just d2)) = Just $ diffDays (fromEFDay d2) (fromEFDay d1)
@@ -669,14 +585,6 @@ thisyear = startofyear
669
585
nextyear = startofyear . addGregorianYearsClip 1
670
586
startofyear day = fromGregorian y 1 1 where (y,_,_) = toGregorian day
671
587
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
-
680
588
-- | Find the next occurrence of the specified month and day of month, on or after the given date.
681
589
-- The month should be 1-12 and the day of month should be 1-31, or an error will be raised.
682
590
--
@@ -1263,45 +1171,3 @@ emptydatespan = DateSpan (Just $ Exact $ addDays 1 nulldate) (Just $ Exact nulld
1263
1171
1264
1172
nulldate :: Day
1265
1173
nulldate = 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