packages feed

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 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"