@@ -63,6 +63,7 @@ module Hledger.Data.Dates (
63
63
spanIntersect ,
64
64
spansIntersect ,
65
65
spanDefaultsFrom ,
66
+ spanExtend ,
66
67
spanUnion ,
67
68
spansUnion ,
68
69
daysSpan ,
@@ -314,8 +315,8 @@ groupByDateSpan showempty date colspans =
314
315
where
315
316
groupByCols [] _ = []
316
317
groupByCols (c: cs) [] = if showempty then (c, [] ) : groupByCols cs [] else []
317
- groupByCols (c: cs) ps = (c, map snd matches ) : groupByCols cs later
318
- where (matches, later ) = span ((spanEnd c > ) . Just . fst ) ps
318
+ groupByCols (c: cs) ps = (c, map snd colps ) : groupByCols cs laterps
319
+ where (colps, laterps ) = span ((spanEnd c > ) . Just . fst ) ps
319
320
320
321
beforeStart = maybe (const False ) (>) $ spanStart =<< headMay colspans
321
322
@@ -324,40 +325,82 @@ spansIntersect [] = nulldatespan
324
325
spansIntersect [d] = d
325
326
spansIntersect (d: ds) = d `spanIntersect` (spansIntersect ds)
326
327
328
+ -- | Calculate the union of a number of datespans.
329
+ spansUnion [] = nulldatespan
330
+ spansUnion [d] = d
331
+ spansUnion (d: ds) = d `spanUnion` (spansUnion ds)
332
+
327
333
-- | Calculate the intersection of two datespans.
328
334
--
329
335
-- For non-intersecting spans, gives an empty span beginning on the second's start date:
330
336
-- >>> DateSpan (Just $ Flex $ fromGregorian 2018 01 01) (Just $ Flex $ fromGregorian 2018 01 03) `spanIntersect` DateSpan (Just $ Flex $ fromGregorian 2018 01 03) (Just $ Flex $ fromGregorian 2018 01 05)
331
337
-- DateSpan 2018-01-03..2018-01-02
332
- spanIntersect (DateSpan b1 e1) (DateSpan b2 e2) = DateSpan b e
333
- where
334
- b = latest b1 b2
335
- e = earliest e1 e2
338
+ spanIntersect (DateSpan b1 e1) (DateSpan b2 e2) = DateSpan (laterDefinite b1 b2) (earlierDefinite e1 e2)
336
339
337
340
-- | Fill any unspecified dates in the first span with the dates from
338
- -- the second one. Sort of a one-way spanIntersect.
341
+ -- the second one (if specified there) . Sort of a one-way spanIntersect.
339
342
spanDefaultsFrom (DateSpan a1 b1) (DateSpan a2 b2) = DateSpan a b
340
343
where a = if isJust a1 then a1 else a2
341
344
b = if isJust b1 then b1 else b2
342
345
343
- -- | Calculate the union of a number of datespans.
344
- spansUnion [] = nulldatespan
345
- spansUnion [d] = d
346
- spansUnion (d: ds) = d `spanUnion` (spansUnion ds)
347
-
348
346
-- | Calculate the union of two datespans.
349
- spanUnion (DateSpan b1 e1) (DateSpan b2 e2) = DateSpan b e
350
- where
351
- b = earliest b1 b2
352
- e = latest e1 e2
353
-
354
- latest d Nothing = d
355
- latest Nothing d = d
356
- latest (Just d1) (Just d2) = Just $ max d1 d2
357
-
358
- earliest d Nothing = d
359
- earliest Nothing d = d
360
- earliest (Just d1) (Just d2) = Just $ min d1 d2
347
+ -- If either span is open-ended, the union will be too.
348
+ --
349
+ -- >>> ys2024 = fromGregorian 2024 01 01
350
+ -- >>> ys2025 = fromGregorian 2025 01 01
351
+ -- >>> to2024 = DateSpan Nothing (Just $ Exact ys2024)
352
+ -- >>> in2024 = DateSpan (Just $ Exact ys2024) (Just $ Exact ys2025)
353
+ -- >>> spanUnion to2024 in2024
354
+ -- DateSpan ..2024-12-31
355
+ -- >>> spanUnion in2024 to2024
356
+ -- DateSpan ..2024-12-31
357
+ spanUnion (DateSpan b1 e1) (DateSpan b2 e2) = DateSpan (earlier b1 b2) (later e1 e2)
358
+
359
+ -- | Extend the first span to include any definite end dates of the second.
360
+ -- Unlike spanUnion, open ends in the second are ignored.
361
+ -- If the first span was open-ended, it still will be after being extended.
362
+ --
363
+ -- >>> ys2024 = fromGregorian 2024 01 01
364
+ -- >>> ys2025 = fromGregorian 2025 01 01
365
+ -- >>> to2024 = DateSpan Nothing (Just $ Exact ys2024)
366
+ -- >>> all2024 = DateSpan (Just $ Exact ys2024) (Just $ Exact ys2025)
367
+ -- >>> partof2024 = DateSpan (Just $ Exact $ fromGregorian 2024 03 01) (Just $ Exact $ fromGregorian 2024 09 01)
368
+ -- >>> spanExtend to2024 all2024
369
+ -- DateSpan 2024
370
+ -- >>> spanExtend all2024 to2024
371
+ -- DateSpan 2024
372
+ -- >>> spanExtend partof2024 all2024
373
+ -- DateSpan 2024
374
+ -- >>> spanExtend all2024 partof2024
375
+ -- DateSpan 2024
376
+ --
377
+ spanExtend (DateSpan b1 e1) (DateSpan b2 e2) = DateSpan (earlierDefinite b1 b2) (laterDefinite e1 e2)
378
+
379
+ -- | Pick the earlier of two DateSpan starts, treating Nothing as infinitely early.
380
+ -- An Exact and Flex with the same date are considered equal; the first argument wins.
381
+ earlier :: Maybe EFDay -> Maybe EFDay -> Maybe EFDay
382
+ earlier = min
383
+
384
+ -- | Pick the later of two DateSpan starts, treating Nothing as infinitely late.
385
+ -- An Exact and Flex with the same date are considered equal; the second argument wins.
386
+ later :: Maybe EFDay -> Maybe EFDay -> Maybe EFDay
387
+ later _ Nothing = Nothing
388
+ later Nothing _ = Nothing
389
+ later d1 d2 = max d1 d2
390
+
391
+ -- | Pick the earlier of two DateSpan ends that is a definite date (if any).
392
+ -- An Exact and Flex with the same date are considered equal; the first argument wins.
393
+ earlierDefinite :: Maybe EFDay -> Maybe EFDay -> Maybe EFDay
394
+ earlierDefinite d1 Nothing = d1
395
+ earlierDefinite Nothing d2 = d2
396
+ earlierDefinite d1 d2 = min d1 d2
397
+
398
+ -- | Pick the later of two DateSpan ends that is a definite date (if any).
399
+ -- An Exact and Flex with the same date are considered equal; the second argument wins.
400
+ laterDefinite :: Maybe EFDay -> Maybe EFDay -> Maybe EFDay
401
+ laterDefinite d1 Nothing = d1
402
+ laterDefinite Nothing d2 = d2
403
+ laterDefinite d1 d2 = max d1 d2
361
404
362
405
-- | Calculate the minimal DateSpan containing all of the given Days (in the
363
406
-- usual exclusive-end-date sense: beginning on the earliest, and ending on
0 commit comments