pretty-relative-time 0.0.0.0 → 0.1.0.0
raw patch · 6 files changed
+339/−195 lines, 6 files
Files
- pretty-relative-time.cabal +16/−16
- src/Text/Time/Pretty.hs +22/−6
- src/Text/Time/Pretty/Constants.hs +17/−5
- src/Text/Time/Pretty/Render.hs +43/−44
- src/Text/Time/Pretty/TimeAgo.hs +128/−69
- test/Text/Time/PrettySpec.hs +113/−55
pretty-relative-time.cabal view
@@ -1,11 +1,13 @@--- This file has been generated from package.yaml by hpack version 0.20.0.+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.2. -- -- see: https://github.com/sol/hpack ----- hash: fcebe4c6280960a414497769601af597fefeeb3c20a57aa3617bf5061b1e81b7+-- hash: adf64d540112ae1c5a3643234034c51572f5f251cba6a9c63ba8a48b8285b629 name: pretty-relative-time-version: 0.0.0.0+version: 0.1.0.0 synopsis: Pretty relative time description: Please see the README on Github at <https://github.com/NorfairKing/pretty-relative-time#readme> category: Time@@ -17,17 +19,22 @@ license: MIT license-file: LICENSE build-type: Simple-cabal-version: >= 1.10- extra-source-files:- ChangeLog.md README.md+ ChangeLog.md source-repository head type: git location: https://github.com/NorfairKing/pretty-relative-time library+ exposed-modules:+ Text.Time.Pretty+ Text.Time.Pretty.Constants+ Text.Time.Pretty.Render+ Text.Time.Pretty.TimeAgo+ other-modules:+ Paths_pretty_relative_time hs-source-dirs: src ghc-options: -Wall@@ -36,18 +43,14 @@ , time , validity , validity-time- exposed-modules:- Text.Time.Pretty- Text.Time.Pretty.Constants- Text.Time.Pretty.Render- Text.Time.Pretty.TimeAgo- other-modules:- Paths_pretty_relative_time default-language: Haskell2010 test-suite pretty-relative-time-test type: exitcode-stdio-1.0 main-is: Spec.hs+ other-modules:+ Text.Time.PrettySpec+ Paths_pretty_relative_time hs-source-dirs: test ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall@@ -61,7 +64,4 @@ , time , validity , validity-time- other-modules:- Text.Time.PrettySpec- Paths_pretty_relative_time default-language: Haskell2010
src/Text/Time/Pretty.hs view
@@ -1,18 +1,25 @@ module Text.Time.Pretty- ( prettyTimeAutoFromNow- , prettyTimeAuto+ ( prettyTimeAutoFromNow+ , prettyTimeAuto+ , prettyDayAutoFromNow+ , prettyDayAuto -- Helper functions- , timeAgo- , timeAgoToDiffTime+ , timeAgo+ , timeAgoToDiffTime+ , daysAgo+ , daysAgoToDays -- * Helper Types- , TimeAgo(..)+ , TimeAgo(..)+ , DaysAgo(..) -- * Rendering- , renderTimeAgoAuto+ , renderDaysAgoAuto+ , renderTimeAgoAuto -- * Constants , picoSecondsPerSecond , secondsPerMinute , minutesPerHour , hoursPerDay+ , daysPerWeek ) where import Data.Time@@ -28,3 +35,12 @@ prettyTimeAuto :: UTCTime -> UTCTime -> String prettyTimeAuto now before = renderTimeAgoAuto $ timeAgo $ diffUTCTime now before++prettyDayAutoFromNow :: Day -> IO String+prettyDayAutoFromNow before = do+ today <- (localDay . zonedTimeToLocalTime) <$> getZonedTime+ pure $ prettyDayAuto today before++prettyDayAuto :: Day -> Day -> String+prettyDayAuto today before = renderDaysAgoAuto $ daysAgo $ diffDays today before+
src/Text/Time/Pretty/Constants.hs view
@@ -1,9 +1,12 @@ module Text.Time.Pretty.Constants- ( picoSecondsPerSecond- , secondsPerMinute- , minutesPerHour- , hoursPerDay- ) where+ ( picoSecondsPerSecond+ , secondsPerMinute+ , minutesPerHour+ , hoursPerDay+ , daysPerWeek+ , approximateDaysPerMonth+ , approximateDaysPerYear+ ) where picoSecondsPerSecond :: Integral a => a picoSecondsPerSecond = 10 ^ (12 :: Integer)@@ -16,3 +19,12 @@ hoursPerDay :: Integral a => a hoursPerDay = 24++daysPerWeek :: Integral a => a+daysPerWeek = 7++approximateDaysPerMonth :: Integral a => a+approximateDaysPerMonth = 30++approximateDaysPerYear :: Integral a => a+approximateDaysPerYear = 365
src/Text/Time/Pretty/Render.hs view
@@ -2,53 +2,52 @@ {-# LANGUAGE RecordWildCards #-} module Text.Time.Pretty.Render- ( renderTimeAgoAuto- ) where+ ( renderDaysAgoAuto+ , renderTimeAgoAuto+ ) where import Text.Time.Pretty.TimeAgo +renderDaysAgoAuto :: DaysAgo -> String+renderDaysAgoAuto DaysAgo {..} =+ case daysAgoSign of+ GT ->+ if | daysAgoYears > 0 -> unwords [plural daysAgoYears "year" "years", "ago"]+ | daysAgoMonths > 0 -> unwords [plural daysAgoMonths "month" "months", "ago"]+ | daysAgoWeeks > 0 -> unwords [plural daysAgoWeeks "week" "weeks", "ago"]+ | daysAgoDays == 1 -> "yesterday"+ | daysAgoDays > 0 -> unwords [plural daysAgoDays "day" "days", "ago"]+ | otherwise -> "today"+ EQ -> "today"+ LT ->+ if | daysAgoYears > 0 -> unwords ["in", plural daysAgoYears "year" "years"]+ | daysAgoMonths > 0 -> unwords ["in", plural daysAgoMonths "month" "months"]+ | daysAgoWeeks > 0 -> unwords ["in", plural daysAgoWeeks "week" "weeks"]+ | daysAgoDays == 1 -> "tomorrow"+ | daysAgoDays > 0 -> unwords ["in", plural daysAgoDays "day" "days"]+ | otherwise -> "today"+ renderTimeAgoAuto :: TimeAgo -> String renderTimeAgoAuto TimeAgo {..} =- case signAgo of- GT ->- if | daysAgo > 0 ->- unwords [show daysAgo, plural daysAgo "day" "days", "ago"]- | hoursAgo > 0 ->- unwords- [show hoursAgo, plural hoursAgo "hour" "hours", "ago"]- | minutesAgo > 0 ->- unwords- [ show minutesAgo- , plural minutesAgo "minute" "minutes"- , "ago"- ]- | secondsAgo > 0 ->- unwords- [ show secondsAgo- , plural secondsAgo "second" "seconds"- , "ago"- ]- | otherwise -> "just now"- EQ -> "just now"- LT ->- if | daysAgo > 0 ->- unwords ["in", show daysAgo, plural daysAgo "day" "days"]- | hoursAgo > 0 ->- unwords ["in", show hoursAgo, plural hoursAgo "hour" "hours"]- | minutesAgo > 0 ->- unwords- [ "in"- , show minutesAgo- , plural minutesAgo "minute" "minutes"- ]- | secondsAgo > 0 ->- unwords- [ "in"- , show secondsAgo- , plural secondsAgo "second" "seconds"- ]- | otherwise -> "just now"+ case timeAgoSign of+ GT ->+ if | daysAgoToDays timeAgoDaysAgo == 1 -> "1 day ago"+ | daysAgoToDays timeAgoDaysAgo > 0 ->+ renderDaysAgoAuto (timeAgoDaysAgo {daysAgoSign = timeAgoSign})+ | timeAgoHours > 0 -> unwords [plural timeAgoHours "hour" "hours", "ago"]+ | timeAgoMinutes > 0 -> unwords [plural timeAgoMinutes "minute" "minutes", "ago"]+ | timeAgoSeconds > 0 -> unwords [plural timeAgoSeconds "second" "seconds", "ago"]+ | otherwise -> "just now"+ EQ -> "just now"+ LT ->+ if | daysAgoToDays timeAgoDaysAgo == 1 -> "in 1 day"+ | daysAgoToDays timeAgoDaysAgo > 0 ->+ renderDaysAgoAuto (timeAgoDaysAgo {daysAgoSign = timeAgoSign})+ | timeAgoHours > 0 -> unwords ["in", plural timeAgoHours "hour" "hours"]+ | timeAgoMinutes > 0 -> unwords ["in", plural timeAgoMinutes "minute" "minutes"]+ | timeAgoSeconds > 0 -> unwords ["in", plural timeAgoSeconds "second" "seconds"]+ | otherwise -> "just now" -plural :: Integral a => a -> String -> String -> String-plural 1 sing _ = sing-plural _ _ plur = plur+plural :: (Show a, Integral a) => a -> String -> String -> String+plural 1 sing _ = "1 " ++ sing+plural n _ plur = show n ++ " " ++ plur
src/Text/Time/Pretty/TimeAgo.hs view
@@ -2,10 +2,13 @@ {-# LANGUAGE RecordWildCards #-} module Text.Time.Pretty.TimeAgo- ( timeAgo- , timeAgoToDiffTime- , TimeAgo(..)- ) where+ ( daysAgo+ , daysAgoToDays+ , DaysAgo(..)+ , timeAgo+ , timeAgoToDiffTime+ , TimeAgo(..)+ ) where import Data.Time import Data.Validity@@ -13,81 +16,137 @@ import Text.Time.Pretty.Constants -data TimeAgo = TimeAgo- { signAgo :: Ordering- , daysAgo :: Integer- , hoursAgo :: Integer- , minutesAgo :: Integer- , secondsAgo :: Integer- , picoSecondsAgo :: Integer- } deriving (Show, Eq, Generic)+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- isValid = isValidByValidating- validate TimeAgo {..} =- mconcat- [ (case signAgo of- EQ ->- and- [ daysAgo == 0- , hoursAgo == 0- , minutesAgo == 0- , secondsAgo == 0- , picoSecondsAgo == 0- ]- _ ->- any- (> 0)- [ daysAgo- , hoursAgo- , minutesAgo- , secondsAgo- , picoSecondsAgo- ]) <?@>- "the sign makes sense"- , (daysAgo >= 0) <?@> "days are positive"- , (hoursAgo < hoursPerDay) <?@> "hours < 24"- , (hoursAgo >= 0) <?@> "hours are positive"- , (minutesAgo < minutesPerHour) <?@> "minutes < 60"- , (minutesAgo >= 0) <?@> "minutes are positive"- , (secondsAgo < secondsPerMinute) <?@> "seconds < 60"- , (secondsAgo >= 0) <?@> "seconds are positive"- , (picoSecondsAgo < picoSecondsPerSecond) <?@> "picoseconds < 1E12"- , (picoSecondsAgo >= 0) <?@> "picoseconds are positive"- ]+ 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- signAgo = compare dt 0- picoSecondsAgo =- totalPicoSecondsAgo - picoSecondsPerSecond * totalSecondsAgo- secondsAgo = totalSecondsAgo - secondsPerMinute * totalMinutesAgo- minutesAgo = totalMinutesAgo - minutesPerHour * totalHoursAgo- hoursAgo = totalHoursAgo - hoursPerDay * totalDaysAgo- daysAgo = totalDaysAgo- totalPicoSecondsAgo =- floor $ absDt * fromIntegral (picoSecondsPerSecond :: Integer)+ 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)+ totalHoursAgo = floor $ absDt / fromIntegral (minutesPerHour * secondsPerMinute :: Integer) totalDaysAgo =- floor $- absDt /- fromIntegral- (hoursPerDay * minutesPerHour * secondsPerMinute :: Integer)+ floor $ absDt / fromIntegral (hoursPerDay * minutesPerHour * secondsPerMinute :: Integer) absDt = abs dt timeAgoToDiffTime :: TimeAgo -> NominalDiffTime timeAgoToDiffTime TimeAgo {..} =- (/ fromIntegral (picoSecondsPerSecond :: Integer)) $- realToFrac $- (case signAgo of- EQ -> const 0- GT -> id- LT -> negate)- (picoSecondsAgo +- picoSecondsPerSecond *- (secondsAgo + 60 * (minutesAgo + 60 * (hoursAgo + 24 * daysAgo))))+ (/ fromIntegral (picoSecondsPerSecond :: Integer)) $+ realToFrac $+ (case timeAgoSign of+ EQ -> const 0+ GT -> id+ LT -> negate)+ (timeAgoPicoSeconds ++ picoSecondsPerSecond *+ (timeAgoSeconds ++ secondsPerMinute *+ (timeAgoMinutes ++ minutesPerHour * (timeAgoHours + hoursPerDay * (daysAgoToDays timeAgoDaysAgo)))))
test/Text/Time/PrettySpec.hs view
@@ -2,8 +2,8 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} module Text.Time.PrettySpec- ( spec- ) where+ ( spec+ ) where import Data.GenValidity.Time () import Test.Hspec@@ -12,62 +12,120 @@ import Text.Time.Pretty +instance GenUnchecked DaysAgo++instance GenValid DaysAgo where+ genValid = do+ sign <- genValid+ case sign of+ EQ -> pure $ DaysAgo EQ 0 0 0 0+ _ ->+ (DaysAgo sign <$> choose (0, 364) <*> choose (0, 29) <*> choose (0, 4) <*> choose (0, 7)) `suchThat`+ isValid+ instance GenUnchecked TimeAgo instance GenValid TimeAgo where- genValid = do- sign <- genValid- case sign of- EQ -> pure $ TimeAgo EQ 0 0 0 0 0- _ ->- (TimeAgo sign <$> choose (0, picoSecondsPerSecond) <*>- choose (0, secondsPerMinute) <*>- choose (0, minutesPerHour) <*>- choose (0, hoursPerDay) <*>- (abs <$> genValid)) `suchThat`- isValid+ genValid = do+ sign <- genValid+ case sign of+ EQ -> pure $ TimeAgo EQ (DaysAgo EQ 0 0 0 0) 0 0 0 0+ _ ->+ (TimeAgo sign <$> genValid <*> choose (0, hoursPerDay) <*> choose (0, minutesPerHour) <*>+ choose (0, secondsPerMinute) <*>+ choose (0, picoSecondsPerSecond)) `suchThat`+ isValid spec :: Spec spec = do- eqSpec @TimeAgo- genValidSpec @TimeAgo- describe "timeAgo" $ do- it "produces valid TimeAgo's" $ producesValidsOnValids timeAgo- it "is the inverse of timeAgoToDiffTime" $- inverseFunctionsOnValid timeAgo timeAgoToDiffTime- describe "timeAgoToDiffTime" $ do- it "produces valid DiffTime's" $ producesValidsOnValids timeAgo- it "is the inverse of timeAgo for just picoseconds" $- inverseFunctionsOnGen- timeAgoToDiffTime- timeAgo- ((TimeAgo GT 0 0 0 0 <$> genValid) `suchThat` isValid)- (const [])- it "is the inverse of timeAgo" $- inverseFunctionsOnValid timeAgoToDiffTime timeAgo- describe "renderTimeAgoAuto" $ do- it "produces valid Strings's" $ producesValidsOnValids renderTimeAgoAuto- it "renders these simple examples well" $ do- renderTimeAgoAuto (TimeAgo GT 5 0 0 0 0) `shouldBe` "5 days ago"- renderTimeAgoAuto (TimeAgo GT 0 6 0 0 0) `shouldBe` "6 hours ago"- renderTimeAgoAuto (TimeAgo GT 0 0 7 0 0) `shouldBe` "7 minutes ago"- renderTimeAgoAuto (TimeAgo GT 0 0 0 8 0) `shouldBe` "8 seconds ago"- renderTimeAgoAuto (TimeAgo GT 0 0 0 0 9) `shouldBe` "just now"- renderTimeAgoAuto (TimeAgo EQ 0 0 0 0 0) `shouldBe` "just now"- renderTimeAgoAuto (TimeAgo LT 5 0 0 0 0) `shouldBe` "in 5 days"- renderTimeAgoAuto (TimeAgo LT 0 6 0 0 0) `shouldBe` "in 6 hours"- renderTimeAgoAuto (TimeAgo LT 0 0 7 0 0) `shouldBe` "in 7 minutes"- renderTimeAgoAuto (TimeAgo LT 0 0 0 8 0) `shouldBe` "in 8 seconds"- renderTimeAgoAuto (TimeAgo LT 0 0 0 0 9) `shouldBe` "just now"- it "handles singular nouns well" $ do- renderTimeAgoAuto (TimeAgo GT 1 0 0 0 0) `shouldBe` "1 day ago"- renderTimeAgoAuto (TimeAgo GT 0 1 0 0 0) `shouldBe` "1 hour ago"- renderTimeAgoAuto (TimeAgo GT 0 0 1 0 0) `shouldBe` "1 minute ago"- renderTimeAgoAuto (TimeAgo GT 0 0 0 1 0) `shouldBe` "1 second ago"- renderTimeAgoAuto (TimeAgo GT 0 0 0 0 1) `shouldBe` "just now"- renderTimeAgoAuto (TimeAgo EQ 0 0 0 0 0) `shouldBe` "just now"- renderTimeAgoAuto (TimeAgo LT 1 0 0 0 0) `shouldBe` "in 1 day"- renderTimeAgoAuto (TimeAgo LT 0 1 0 0 0) `shouldBe` "in 1 hour"- renderTimeAgoAuto (TimeAgo LT 0 0 1 0 0) `shouldBe` "in 1 minute"- renderTimeAgoAuto (TimeAgo LT 0 0 0 1 0) `shouldBe` "in 1 second"- renderTimeAgoAuto (TimeAgo LT 0 0 0 0 1) `shouldBe` "just now"+ eqSpec @DaysAgo+ genValidSpec @DaysAgo+ describe "daysAgo" $ do+ it "produces valid TimeAgos" $ producesValidsOnValids daysAgo+ it "is the inverse of daysAgoToDays" $ inverseFunctionsOnValid daysAgo daysAgoToDays+ describe "daysAgoToDays" $ do+ it "produces valid results" $ producesValidsOnValids daysAgoToDays+ it "is the inverse of daysAgo for just days" $+ inverseFunctionsOnGen+ daysAgoToDays+ daysAgo+ (((\d -> DaysAgo GT 0 d 0 0) <$> genValid) `suchThat` isValid)+ (const [])+ it "is the inverse of daysAgo" $ inverseFunctionsOnValid daysAgoToDays daysAgo+ describe "renderDaysAgoAuto" $ do+ it "produces valid Strings" $ producesValidsOnValids renderDaysAgoAuto+ let i da s = it (unwords ["Renders", show da, "as", show s]) $ renderDaysAgoAuto da `shouldBe` s+ describe "renders these simple examples well" $ do+ i (DaysAgo GT 5 0 0 0) "5 years ago"+ i (DaysAgo GT 0 4 0 0) "4 months ago"+ i (DaysAgo GT 0 0 3 0) "3 weeks ago"+ i (DaysAgo GT 0 0 0 2) "2 days ago"+ i (DaysAgo GT 0 0 0 1) "yesterday"+ i (DaysAgo EQ 0 0 0 0) "today"+ i (DaysAgo LT 0 0 0 1) "tomorrow"+ i (DaysAgo LT 0 0 0 2) "in 2 days"+ i (DaysAgo LT 0 0 3 0) "in 3 weeks"+ i (DaysAgo LT 0 4 0 0) "in 4 months"+ i (DaysAgo LT 5 0 0 0) "in 5 years"+ describe "handles singular nouns well" $ do+ i (DaysAgo GT 1 0 0 0) "1 year ago"+ i (DaysAgo GT 0 1 0 0) "1 month ago"+ i (DaysAgo GT 0 0 1 0) "1 week ago"+ i (DaysAgo GT 0 0 0 1) "yesterday"+ i (DaysAgo LT 0 0 0 1) "tomorrow"+ i (DaysAgo LT 0 0 1 0) "in 1 week"+ i (DaysAgo LT 0 1 0 0) "in 1 month"+ i (DaysAgo LT 1 0 0 0) "in 1 year"+ eqSpec @TimeAgo+ genValidSpec @TimeAgo+ describe "timeAgo" $ do+ it "produces valid TimeAgo's" $ producesValidsOnValids timeAgo+ it "is the inverse of timeAgoToDiffTime" $ inverseFunctionsOnValid timeAgo timeAgoToDiffTime+ describe "timeAgoToDiffTime" $ do+ it "produces valid DiffTime's" $ producesValidsOnValids timeAgoToDiffTime+ it "is the inverse of timeAgo for just picoseconds" $+ inverseFunctionsOnGen+ timeAgoToDiffTime+ timeAgo+ ((TimeAgo GT (DaysAgo EQ 0 0 0 0) 0 0 0 <$> genValid) `suchThat` isValid)+ (const [])+ it "is the inverse of timeAgo" $ inverseFunctionsOnValid timeAgoToDiffTime timeAgo+ describe "renderTimeAgoAuto" $ do+ it "produces valid Strings's" $ producesValidsOnValids renderTimeAgoAuto+ let i ta s = it (unwords ["Renders", show ta, "as", show s]) $ renderTimeAgoAuto ta `shouldBe` s+ describe "renders these simple examples well" $ do+ i (TimeAgo GT (DaysAgo GT 2 0 0 0) 0 0 0 0) "2 years ago"+ i (TimeAgo GT (DaysAgo GT 0 3 0 0) 0 0 0 0) "3 months ago"+ i (TimeAgo GT (DaysAgo GT 0 0 4 0) 0 0 0 0) "4 weeks ago"+ i (TimeAgo GT (DaysAgo GT 0 0 0 5) 0 0 0 0) "5 days ago"+ i (TimeAgo GT (DaysAgo GT 0 0 0 0) 6 0 0 0) "6 hours ago"+ i (TimeAgo GT (DaysAgo GT 0 0 0 0) 0 7 0 0) "7 minutes ago"+ i (TimeAgo GT (DaysAgo GT 0 0 0 0) 0 0 8 0) "8 seconds ago"+ i (TimeAgo GT (DaysAgo GT 0 0 0 0) 0 0 0 9) "just now"+ i (TimeAgo EQ (DaysAgo EQ 0 0 0 0) 0 0 0 0) "just now"+ i (TimeAgo LT (DaysAgo GT 2 0 0 0) 0 0 0 0) "in 2 years"+ i (TimeAgo LT (DaysAgo GT 0 3 0 0) 0 0 0 0) "in 3 months"+ i (TimeAgo LT (DaysAgo GT 0 0 4 0) 0 0 0 0) "in 4 weeks"+ i (TimeAgo LT (DaysAgo GT 0 0 0 5) 0 0 0 0) "in 5 days"+ i (TimeAgo LT (DaysAgo GT 0 0 0 0) 6 0 0 0) "in 6 hours"+ i (TimeAgo LT (DaysAgo GT 0 0 0 0) 0 7 0 0) "in 7 minutes"+ i (TimeAgo LT (DaysAgo GT 0 0 0 0) 0 0 8 0) "in 8 seconds"+ i (TimeAgo LT (DaysAgo GT 0 0 0 0) 0 0 0 9) "just now"+ describe "handles singular nouns well" $ do+ i (TimeAgo GT (DaysAgo GT 1 0 0 0) 0 0 0 0) "1 year ago"+ i (TimeAgo GT (DaysAgo GT 0 1 0 0) 0 0 0 0) "1 month ago"+ i (TimeAgo GT (DaysAgo GT 0 0 1 0) 0 0 0 0) "1 week ago"+ i (TimeAgo GT (DaysAgo GT 0 0 0 1) 0 0 0 0) "1 day ago"+ i (TimeAgo GT (DaysAgo GT 0 0 0 0) 1 0 0 0) "1 hour ago"+ i (TimeAgo GT (DaysAgo GT 0 0 0 0) 0 1 0 0) "1 minute ago"+ i (TimeAgo GT (DaysAgo GT 0 0 0 0) 0 0 1 0) "1 second ago"+ i (TimeAgo GT (DaysAgo GT 0 0 0 0) 0 0 0 1) "just now"+ i (TimeAgo EQ (DaysAgo EQ 0 0 0 0) 0 0 0 0) "just now"+ i (TimeAgo LT (DaysAgo GT 1 0 0 0) 0 0 0 0) "in 1 year"+ i (TimeAgo LT (DaysAgo GT 0 1 0 0) 0 0 0 0) "in 1 month"+ i (TimeAgo LT (DaysAgo GT 0 0 1 0) 0 0 0 0) "in 1 week"+ i (TimeAgo LT (DaysAgo GT 0 0 0 1) 0 0 0 0) "in 1 day"+ i (TimeAgo LT (DaysAgo GT 0 0 0 0) 1 0 0 0) "in 1 hour"+ i (TimeAgo LT (DaysAgo GT 0 0 0 0) 0 1 0 0) "in 1 minute"+ i (TimeAgo LT (DaysAgo GT 0 0 0 0) 0 0 1 0) "in 1 second"+ i (TimeAgo LT (DaysAgo GT 0 0 0 0) 0 0 0 1) "just now"