pretty-relative-time-0.2.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)))))