pretty-relative-time-0.3.0.0: src/Text/Time/Pretty/TimeAgo.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE RecordWildCards #-}
module Text.Time.Pretty.TimeAgo
( daysAgo,
daysAgoToDays,
DaysAgo (..),
timeAgo,
timeAgoToDiffTime,
TimeAgo (..),
)
where
import Data.Time
import Data.Validity
import GHC.Generics (Generic)
import Text.Time.Pretty.Constants
data DaysAgo = DaysAgo
{ daysAgoSign :: Ordering,
daysAgoYears :: Integer,
daysAgoMonths :: Integer,
daysAgoWeeks :: Integer,
daysAgoDays :: Integer
}
deriving (Show, Eq, Generic)
instance Validity DaysAgo where
validate da@DaysAgo {..} =
mconcat
[ genericValidate da,
check
( case daysAgoSign of
EQ ->
and
[ daysAgoDays == 0,
daysAgoWeeks == 0,
daysAgoMonths == 0,
daysAgoYears == 0
]
_ ->
any
(> 0)
[daysAgoDays, daysAgoWeeks, daysAgoMonths, daysAgoYears]
)
"the sign makes sense",
check (daysAgoYears >= 0) "years are positive",
check
( daysAgoDays + daysPerWeek * daysAgoWeeks
+ approximateDaysPerMonth * daysAgoMonths
< approximateDaysPerYear
)
"days, weeks and months do not sum to a year",
check (daysAgoMonths < 12) "months < 12",
check (daysAgoMonths >= 0) "months are positive",
check
(daysAgoDays + daysPerWeek * daysAgoWeeks < approximateDaysPerMonth)
"days and weeks do not sum to a month",
check (daysAgoWeeks < 5) "weeks < 5",
check (daysAgoWeeks >= 0) "weeks are positive",
check (daysAgoDays < 7) "days < 7",
check (daysAgoDays >= 0) "days are positive"
]
daysAgo :: Integer -> DaysAgo
daysAgo i = DaysAgo {..}
where
totalDays = abs i
daysAgoSign = compare i 0
daysAgoYears = totalDays `div` approximateDaysPerYear
daysLeftAfterYears = totalDays - daysAgoYears * approximateDaysPerYear
daysAgoMonths = daysLeftAfterYears `div` approximateDaysPerMonth
daysLeftAfterMonths =
daysLeftAfterYears - daysAgoMonths * approximateDaysPerMonth
daysAgoWeeks = daysLeftAfterMonths `div` daysPerWeek
daysLeftAfterWeeks = daysLeftAfterMonths - daysAgoWeeks * daysPerWeek
daysAgoDays = daysLeftAfterWeeks
daysAgoToDays :: DaysAgo -> Integer
daysAgoToDays DaysAgo {..} =
( case daysAgoSign of
EQ -> const 0
GT -> id
LT -> negate
)
$ daysAgoDays + daysPerWeek * daysAgoWeeks
+ approximateDaysPerMonth * daysAgoMonths
+ approximateDaysPerYear * daysAgoYears
data TimeAgo = TimeAgo
{ timeAgoSign :: Ordering,
timeAgoDaysAgo :: DaysAgo,
timeAgoHours :: Integer,
timeAgoMinutes :: Integer,
timeAgoSeconds :: Integer,
timeAgoPicoSeconds :: Integer
}
deriving (Show, Eq, Generic)
instance Validity TimeAgo where
validate ta@TimeAgo {..} =
mconcat
[ genericValidate ta,
check
( case timeAgoSign of
EQ ->
and
[ daysAgoToDays timeAgoDaysAgo == 0,
timeAgoHours == 0,
timeAgoMinutes == 0,
timeAgoSeconds == 0,
timeAgoPicoSeconds == 0
]
_ ->
any
(> 0)
[ daysAgoToDays timeAgoDaysAgo,
timeAgoHours,
timeAgoMinutes,
timeAgoSeconds,
timeAgoPicoSeconds
]
)
"the sign makes sense",
check (daysAgoSign timeAgoDaysAgo /= LT) "The days ago are not negative",
check (timeAgoHours < hoursPerDay) "hours < 24",
check (timeAgoHours >= 0) "hours are positive",
check (timeAgoMinutes < minutesPerHour) "minutes < 60",
check (timeAgoMinutes >= 0) "minutes are positive",
check (timeAgoSeconds < secondsPerMinute) "seconds < 60",
check (timeAgoSeconds >= 0) "seconds are positive",
check (timeAgoPicoSeconds < picoSecondsPerSecond) "picoseconds < 1E12",
check (timeAgoPicoSeconds >= 0) "picoseconds are positive"
]
timeAgo :: NominalDiffTime -> TimeAgo
timeAgo dt = TimeAgo {..}
where
timeAgoSign = compare dt 0
timeAgoPicoSeconds =
totalPicoSecondsAgo - picoSecondsPerSecond * totalSecondsAgo
timeAgoSeconds = totalSecondsAgo - secondsPerMinute * totalMinutesAgo
timeAgoMinutes = totalMinutesAgo - minutesPerHour * totalHoursAgo
timeAgoHours = totalHoursAgo - hoursPerDay * totalDaysAgo
timeAgoDaysAgo = daysAgo totalDaysAgo
totalPicoSecondsAgo =
floor $ absDt * fromIntegral (picoSecondsPerSecond :: Integer)
totalSecondsAgo = floor absDt :: Integer
totalMinutesAgo = floor $ absDt / fromIntegral (secondsPerMinute :: Integer)
totalHoursAgo =
floor $
absDt / fromIntegral (minutesPerHour * secondsPerMinute :: Integer)
totalDaysAgo =
floor $
absDt
/ fromIntegral (hoursPerDay * minutesPerHour * secondsPerMinute :: Integer)
absDt = abs dt
timeAgoToDiffTime :: TimeAgo -> NominalDiffTime
timeAgoToDiffTime TimeAgo {..} =
(/ fromIntegral (picoSecondsPerSecond :: Integer)) $
realToFrac $
( case timeAgoSign of
EQ -> const 0
GT -> id
LT -> negate
)
( timeAgoPicoSeconds
+ picoSecondsPerSecond
* ( timeAgoSeconds
+ secondsPerMinute
* ( timeAgoMinutes
+ minutesPerHour
* (timeAgoHours + hoursPerDay * daysAgoToDays timeAgoDaysAgo)
)
)
)