fuzzy-time-0.3.0.0: src/Data/FuzzyTime/Resolve.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PatternSynonyms #-}
module Data.FuzzyTime.Resolve
( -- * Local time
resolveLocalTimeForwards,
resolveLocalTimeBackwards,
-- * Time Of Day
resolveTimeOfDayForwards,
resolveTimeOfDayBackwards,
normaliseTimeOfDay,
morning,
evening,
-- * Day
resolveDayForwards,
resolveDayBackwards,
-- ** Resolution helpers
nextDayOfMonth,
previousDayOfMonth,
nextDayOfMonthOfYear,
previousDayOfMonthOfYear,
nextDayOfWeek,
previousDayOfWeek,
)
where
import Data.Fixed (Pico, mod')
import Data.FuzzyTime.Types (AmbiguousLocalTime (BothTimeAndDay, OnlyDaySpecified), FuzzyDay (..), FuzzyLocalTime (..), FuzzyTimeOfDay (AtExact, AtHour, AtMinute, Evening, HoursDiff, Midnight, MinutesDiff, Morning, Noon, SameTime, SecondsDiff))
import Data.Time (Day, DayOfWeek, LocalTime (LocalTime), TimeOfDay (TimeOfDay), addDays, midday, midnight, toGregorian)
import Data.Time.Calendar.Month (Month, fromMonthDayValid, fromYearMonthValid, pattern MonthDay)
import Data.Time.Calendar.WeekDate (fromWeekDate, toWeekDate)
import Data.Word (Word8)
resolveLocalTimeForwards :: LocalTime -> FuzzyLocalTime -> Maybe AmbiguousLocalTime
resolveLocalTimeForwards (LocalTime ld ltod) = \case
FuzzyLocalTimeDay fd -> OnlyDaySpecified <$> resolveDayForwards ld fd
FuzzyLocalTimeTimeOfDay ftod -> do
(d, tod) <- resolveTimeOfDayForwardsWithDiff ltod ftod
pure $ BothTimeAndDay $ LocalTime (addDays d ld) tod
FuzzyLocalTimeBoth fd ftod -> do
let withDiff = resolveTimeOfDayForwardsWithDiff ltod ftod
withoutDiff = (,) 0 <$> resolveTimeOfDayForwards ltod ftod
(d, tod) <-
case fd of
Now -> withDiff
Today -> withDiff
_ -> withoutDiff
day <- addDays d <$> resolveDayForwards ld fd
pure $ BothTimeAndDay $ LocalTime day tod
resolveLocalTimeBackwards :: LocalTime -> FuzzyLocalTime -> Maybe AmbiguousLocalTime
resolveLocalTimeBackwards (LocalTime ld ltod) = \case
FuzzyLocalTimeDay fd -> OnlyDaySpecified <$> resolveDayBackwards ld fd
FuzzyLocalTimeTimeOfDay ftod -> do
(d, tod) <- resolveTimeOfDayBackwardsWithDiff ltod ftod
pure $ BothTimeAndDay $ LocalTime (addDays d ld) tod
FuzzyLocalTimeBoth fd ftod -> do
let withDiff = resolveTimeOfDayBackwardsWithDiff ltod ftod
withoutDiff = (,) 0 <$> resolveTimeOfDayBackwards ltod ftod
(d, tod) <-
case fd of
Now -> withDiff
Today -> withDiff
_ -> withoutDiff
day <- addDays d <$> resolveDayBackwards ld fd
pure $ BothTimeAndDay $ LocalTime day tod
resolveTimeOfDayForwards :: TimeOfDay -> FuzzyTimeOfDay -> Maybe TimeOfDay
resolveTimeOfDayForwards tod ftod = snd <$> resolveTimeOfDayForwardsWithDiff tod ftod
resolveTimeOfDayBackwards :: TimeOfDay -> FuzzyTimeOfDay -> Maybe TimeOfDay
resolveTimeOfDayBackwards tod ftod = snd <$> resolveTimeOfDayBackwardsWithDiff tod ftod
resolveTimeOfDayForwardsWithDiff :: TimeOfDay -> FuzzyTimeOfDay -> Maybe (Integer, TimeOfDay)
resolveTimeOfDayForwardsWithDiff tod@(TimeOfDay h m s) ftod =
case ftod of
SameTime -> Just (0, tod)
Noon -> Just $ next midday
Midnight -> Just $ next midnight
Morning -> Just $ next morning
Evening -> Just $ next evening
AtHour h_ -> Just $ next $ TimeOfDay h_ 0 0
AtMinute h_ m_ -> Just $ next $ TimeOfDay h_ m_ 0
AtExact tod_ -> Just $ next tod_
HoursDiff hd -> Just $ normaliseTimeOfDay (h + hd) m s
MinutesDiff md -> Just $ normaliseTimeOfDay h (m + md) s
SecondsDiff sd -> Just $ normaliseTimeOfDay h m (s + sd)
where
next tod_ = (skipIf (>= tod_), tod_)
skipIf p =
if p tod
then 1
else 0
resolveTimeOfDayBackwardsWithDiff :: TimeOfDay -> FuzzyTimeOfDay -> Maybe (Integer, TimeOfDay)
resolveTimeOfDayBackwardsWithDiff tod@(TimeOfDay h m s) ftod =
case ftod of
SameTime -> Just (0, tod)
Noon -> Just $ previous midday
Midnight -> Just $ previous midnight
Morning -> Just $ previous morning
Evening -> Just $ previous evening
AtHour h_ -> Just $ previous $ TimeOfDay h_ 0 0
AtMinute h_ m_ -> Just $ previous $ TimeOfDay h_ m_ 0
AtExact tod_ -> Just $ previous tod_
HoursDiff hd -> Just $ normaliseTimeOfDay (h + hd) m s
MinutesDiff md -> Just $ normaliseTimeOfDay h (m + md) s
SecondsDiff sd -> Just $ normaliseTimeOfDay h m (s + sd)
where
previous tod_ = (skipIf (<= tod_), tod_)
skipIf p =
if p tod
then (-1)
else 0
normaliseTimeOfDay :: Int -> Int -> Pico -> (Integer, TimeOfDay)
normaliseTimeOfDay h m s =
let s' = s `mod'` 60
totalM = m + round (s - s') `div` 60
m' = totalM `mod` 60
totalH = h + (totalM - m') `div` 60
h' = totalH `mod` 24
totalD = (totalH - h') `div` 24
in (fromIntegral totalD, TimeOfDay h' m' s')
morning :: TimeOfDay
morning = TimeOfDay 6 0 0
evening :: TimeOfDay
evening = TimeOfDay 18 0 0
resolveDayForwards :: Day -> FuzzyDay -> Maybe Day
resolveDayForwards d fd =
case fd of
Yesterday -> Just $ addDays (-1) d
Now -> Just d
Today -> Just d
Tomorrow -> Just $ addDays 1 d
OnlyDay di -> nextDayOfMonth d di
DayInMonth mi di -> nextDayOfMonthOfYear d mi di
DiffDays ds -> Just $ addDays (fromIntegral ds) d
DiffWeeks ws -> Just $ addDays (7 * fromIntegral ws) d
DiffMonths ms -> Just $ addDays (30 * fromIntegral ms) d
DayOfTheWeek dow diff -> Just $ addDays (7 * fromIntegral diff) (nextDayOfWeek d dow)
ExactDay d_ -> Just d_
resolveDayBackwards :: Day -> FuzzyDay -> Maybe Day
resolveDayBackwards d fd =
case fd of
Yesterday -> Just $ addDays (-1) d
Now -> Just d
Today -> Just d
Tomorrow -> Just $ addDays 1 d
OnlyDay di -> previousDayOfMonth d di
DayInMonth mi di -> previousDayOfMonthOfYear d mi di
DiffDays ds -> Just $ addDays (fromIntegral ds) d
DiffWeeks ws -> Just $ addDays (7 * fromIntegral ws) d
DiffMonths ms -> Just $ addDays (30 * fromIntegral ms) d
DayOfTheWeek dow diff -> Just $ addDays (7 * fromIntegral diff) (previousDayOfWeek d dow)
ExactDay d_ -> Just d_
nextDayOfMonth :: Day -> Word8 -> Maybe Day
nextDayOfMonth = dayOfMonthHelper nextAfterDay succ
previousDayOfMonth :: Day -> Word8 -> Maybe Day
previousDayOfMonth = dayOfMonthHelper previousBeforeDay pred
dayOfMonthHelper ::
(Day -> Maybe Day -> Maybe Day -> Maybe Day) ->
(Month -> Month) ->
Day ->
Word8 ->
Maybe Day
dayOfMonthHelper chooser changer d wi =
let di :: Int
di = fromIntegral wi
MonthDay thisMonth _ = d
guessThisMonth = fromMonthDayValid thisMonth (fromIntegral wi)
guessOtherMonth = fromMonthDayValid (changer thisMonth) di
in chooser d guessThisMonth guessOtherMonth
nextDayOfMonthOfYear :: Day -> Word8 -> Word8 -> Maybe Day
nextDayOfMonthOfYear = dayOfMonthOfYearHelper nextAfterDay succ
previousDayOfMonthOfYear :: Day -> Word8 -> Word8 -> Maybe Day
previousDayOfMonthOfYear = dayOfMonthOfYearHelper previousBeforeDay pred
dayOfMonthOfYearHelper ::
(Day -> Maybe Day -> Maybe Day -> Maybe Day) ->
(Integer -> Integer) ->
Day ->
Word8 ->
Word8 ->
Maybe Day
dayOfMonthOfYearHelper chooser changer d mw dw =
let mi = fromIntegral mw
di = fromIntegral dw
(y, _, _) = toGregorian d
current =
fromYearMonthValid y mi >>= \m ->
fromMonthDayValid m di
other =
fromYearMonthValid (changer y) mi >>= \m ->
fromMonthDayValid m di
in chooser d current other
nextDayOfWeek :: Day -> DayOfWeek -> Day
nextDayOfWeek = dayOfWeekHelper (\d current after -> if current > d then current else after) (addDays 7)
previousDayOfWeek :: Day -> DayOfWeek -> Day
previousDayOfWeek = dayOfWeekHelper (\d current before -> if current < d then current else before) (addDays (-7))
dayOfWeekHelper ::
(Day -> Day -> Day -> Day) ->
(Day -> Day) ->
Day ->
DayOfWeek ->
Day
dayOfWeekHelper chooser changer day dow =
let (y, woy, _) = toWeekDate day
currentGuess = fromWeekDate y woy (fromEnum dow)
otherGuess = changer currentGuess
in chooser day currentGuess otherGuess
nextAfterDay :: Day -> Maybe Day -> Maybe Day -> Maybe Day
nextAfterDay today beforeGuess afterGuess =
case beforeGuess of
Just d ->
if d > today
then beforeGuess
else afterGuess
Nothing -> afterGuess
previousBeforeDay :: Day -> Maybe Day -> Maybe Day -> Maybe Day
previousBeforeDay today afterGuess beforeGuess =
case afterGuess of
Just d ->
if d < today
then afterGuess
else beforeGuess
Nothing -> beforeGuess