packages feed

o-clock 0.0.0 → 0.1.0

raw patch · 19 files changed

+763/−272 lines, 19 filesdep +Globdep +doctestsetup-changednew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: Glob, doctest

API changes (from Hackage documentation)

- Time.Formatting: class Series (units :: [Rat])
- Time.Formatting: instance (Time.Units.KnownRatName unit, Time.Formatting.Series units) => Time.Formatting.Series (unit : units)
- Time.Formatting: instance Time.Formatting.Series '[]
- Time.Formatting: seriesF :: forall (someUnit :: Rat). (Series units, KnownRatName someUnit) => Time someUnit -> String
- Time.Formatting: unitsF :: forall unit. KnownRatName unit => Time unit -> String
- Time.TimeStamp: TimeStamp :: Rational -> TimeStamp
- Time.TimeStamp: instance GHC.Classes.Eq Time.TimeStamp.TimeStamp
- Time.TimeStamp: instance GHC.Classes.Ord Time.TimeStamp.TimeStamp
- Time.TimeStamp: instance GHC.Enum.Enum Time.TimeStamp.TimeStamp
- Time.TimeStamp: instance GHC.Num.Num Time.TimeStamp.TimeStamp
- Time.TimeStamp: instance GHC.Read.Read Time.TimeStamp.TimeStamp
- Time.TimeStamp: instance GHC.Real.Fractional Time.TimeStamp.TimeStamp
- Time.TimeStamp: instance GHC.Real.Real Time.TimeStamp.TimeStamp
- Time.TimeStamp: instance GHC.Real.RealFrac Time.TimeStamp.TimeStamp
- Time.TimeStamp: instance GHC.Show.Show Time.TimeStamp.TimeStamp
- Time.TimeStamp: newtype TimeStamp
- Time.TimeStamp: timeAdd :: forall (unit :: Rat). KnownRat unit => Time unit -> Time unit -> Time unit
- Time.TimeStamp: timeDiff :: forall (unit :: Rat). KnownRat unit => TimeStamp -> TimeStamp -> (Ordering, Time unit)
- Time.TimeStamp: timeDiv :: forall (unit :: Rat). KnownRat unit => Time unit -> Time unit -> RatioNat
- Time.TimeStamp: timeMul :: forall (unit :: Rat). KnownRat unit => RatioNat -> Time unit -> Time unit
- Time.Units: (+:) :: forall (unitResult :: Rat) (unitLeft :: Rat). KnownDivRat unitLeft unitResult => Time unitLeft -> Time unitResult -> Time unitResult
- Time.Units: type AllTimes = '[Fortnight, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond, Picosecond]
+ Time.Series: class SeriesF (units :: [Rat])
+ Time.Series: class SeriesP (units :: [Rat])
+ Time.Series: instance (Time.Units.KnownRatName unit, Time.Series.SeriesF (nextUnit : units)) => Time.Series.SeriesF (unit : nextUnit : units)
+ Time.Series: instance (Time.Units.KnownRatName unit, Time.Series.SeriesP (nextUnit : units)) => Time.Series.SeriesP (unit : nextUnit : units)
+ Time.Series: instance Time.Series.SeriesF '[]
+ Time.Series: instance Time.Series.SeriesP '[]
+ Time.Series: instance Time.Units.KnownRatName unit => Time.Series.SeriesF '[unit]
+ Time.Series: instance Time.Units.KnownRatName unit => Time.Series.SeriesP '[unit]
+ Time.Series: seriesF :: forall (someUnit :: Rat). (SeriesF units, KnownRatName someUnit) => Time someUnit -> String
+ Time.Series: seriesP :: forall (someUnit :: Rat). (SeriesP units, KnownRatName someUnit) => String -> Maybe (Time someUnit)
+ Time.Series: type AllTimes = '[Fortnight, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond, Picosecond]
+ Time.Series: unitsF :: forall unit. KnownRatName unit => Time unit -> String
+ Time.Series: unitsP :: forall unit. KnownRatName unit => String -> Maybe (Time unit)
+ Time.Timestamp: (*:*) :: forall (unit :: Rat). KnownRat unit => RatioNat -> Time unit -> Time unit
+ Time.Timestamp: (+:+) :: forall (unitResult :: Rat) (unitLeft :: Rat). KnownDivRat unitLeft unitResult => Time unitLeft -> Time unitResult -> Time unitResult
+ Time.Timestamp: (-%-) :: forall (unitResult :: Rat) (unitLeft :: Rat). KnownDivRat unitLeft unitResult => Time unitLeft -> Time unitResult -> (Ordering, Time unitResult)
+ Time.Timestamp: (-:-) :: forall (unitResult :: Rat) (unitLeft :: Rat). KnownDivRat unitLeft unitResult => Time unitLeft -> Time unitResult -> Time unitResult
+ Time.Timestamp: (/:/) :: forall (unit :: Rat). KnownRat unit => Time unit -> Time unit -> RatioNat
+ Time.Timestamp: Timestamp :: Rational -> Timestamp
+ Time.Timestamp: fromUnixTime :: Real a => a -> Timestamp
+ Time.Timestamp: infix 6 -%-
+ Time.Timestamp: infix 7 /:/
+ Time.Timestamp: infixl 6 -:-
+ Time.Timestamp: infixr 7 *:*
+ Time.Timestamp: instance GHC.Classes.Eq Time.Timestamp.Timestamp
+ Time.Timestamp: instance GHC.Classes.Ord Time.Timestamp.Timestamp
+ Time.Timestamp: instance GHC.Read.Read Time.Timestamp.Timestamp
+ Time.Timestamp: instance GHC.Show.Show Time.Timestamp.Timestamp
+ Time.Timestamp: newtype Timestamp
+ Time.Timestamp: timeAdd :: forall (unit :: Rat). KnownDivRat unit Second => Time unit -> Timestamp -> Timestamp
+ Time.Timestamp: timeDiff :: forall (unit :: Rat). KnownDivRat Second unit => Timestamp -> Timestamp -> (Ordering, Time unit)
+ Time.Timestamp: timeDiv :: forall (unit :: Rat). KnownRat unit => Time unit -> Time unit -> RatioNat
+ Time.Timestamp: timeMul :: forall (unit :: Rat). KnownRat unit => RatioNat -> Time unit -> Time unit
+ Time.Units: toNum :: forall (unitTo :: Rat) n (unit :: Rat). (KnownDivRat unit unitTo, Num n) => Time unit -> n
- Time.Units: day :: Natural -> Time Day
+ Time.Units: day :: RatioNat -> Time Day
- Time.Units: fortnight :: Natural -> Time Fortnight
+ Time.Units: fortnight :: RatioNat -> Time Fortnight
- Time.Units: hour :: Natural -> Time Hour
+ Time.Units: hour :: RatioNat -> Time Hour
- Time.Units: mcs :: Natural -> Time Microsecond
+ Time.Units: mcs :: RatioNat -> Time Microsecond
- Time.Units: minute :: Natural -> Time Minute
+ Time.Units: minute :: RatioNat -> Time Minute
- Time.Units: ms :: Natural -> Time Millisecond
+ Time.Units: ms :: RatioNat -> Time Millisecond
- Time.Units: ns :: Natural -> Time Nanosecond
+ Time.Units: ns :: RatioNat -> Time Nanosecond
- Time.Units: ps :: Natural -> Time Picosecond
+ Time.Units: ps :: RatioNat -> Time Picosecond
- Time.Units: sec :: Natural -> Time Second
+ Time.Units: sec :: RatioNat -> Time Second
- Time.Units: time :: Natural -> Time unit
+ Time.Units: time :: RatioNat -> Time unit
- Time.Units: week :: Natural -> Time Week
+ Time.Units: week :: RatioNat -> Time Week

Files

CHANGELOG.md view
@@ -3,6 +3,47 @@ o'clock uses [PVP Versioning][1]. The change log is available [on GitHub][2]. +0.1.0+=====++* [#85](https://github.com/serokell/o-clock/issues/85):+  Add `fromUnixTime` function.+* [#71](https://github.com/serokell/o-clock/issues/71):+  Add `toNum` function.+* [#64](https://github.com/serokell/o-clock/issues/64):+  Add property tests for `unitsP . unitsF ≡ id`+* [#63](https://github.com/serokell/o-clock/issues/63):+  Rename `Formatting` module to `Series`.+  Add `SeriesP` class for parsing time.+* [#81](https://github.com/serokell/o-clock/issues/81):+  Rename `TimeStamp` to `Timestamp`.+* [#60](https://github.com/serokell/o-clock/issues/60):+  Show fractional as the last argument in the result of `seriesF`.+* [#76](https://github.com/serokell/o-clock/issues/76):+  Remove useless instances of `TimeStamp`. Make TimeStamp always deal with+  `Second`s internally.+* [#61](https://github.com/serokell/o-clock/issues/61):+  Change `Show` and `Read` instances for `Time` to use+  mixed fractions.+* [#72](https://github.com/serokell/o-clock/issues/72):+  Move `+:+` and `-:-` to `TimeStamp` module.+  Make operators `*:*` and `/:/` for `timeMul` and `timeDiv`.+  Add `-%-` operator. Change `timeAdd` function to work with `TimeStamp`.+* [#56](https://github.com/serokell/o-clock/issues/56):+  Add `doctest` to documentation.+* [#62](https://github.com/serokell/o-clock/issues/62):+  Add `.ghci` file. Make time creation helpers work with+  `RatioNat` instead of `Naturals`.+  Rename `+:` to `+:+` add `-:-`.+* [#46](https://github.com/serokell/o-clock/issues/46):+  Introduce `...` type to create custom time unit lists in+  provided bounds.+* [#51](https://github.com/serokell/o-clock/issues/51):+  Add `IsDescending` type family to check lists of time units+  in `seriesF` function on right order+* [#45](https://github.com/serokell/o-clock/issues/45):+  Fix behavior of 0 time passed to `seriesF`.+ 0.0.0 ===== 
README.lhs view
@@ -2,6 +2,7 @@  [![Hackage](https://img.shields.io/hackage/v/o-clock.svg)](https://hackage.haskell.org/package/o-clock) [![Build status](https://travis-ci.org/serokell/o-clock.svg?branch=master)](https://travis-ci.org/serokell/o-clock)+[![Stackage Nightly](http://stackage.org/package/o-clock/badge/nightly)](http://stackage.org/nightly/package/o-clock) [![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/serokell/o-clock/blob/master/LICENSE)  ## Overview@@ -129,11 +130,18 @@ After that we can simply print the output we wanted.  Thought we have special function for this kind of formatting purposes `seriesF`.-So the same result can be gained with the usage of it. Check it out:+So the similar result (but not rounded) can be gained with the usage of it. Check it out:  ```haskell main :: IO () main = do-    putStrLn $ formatHours 140-    putStrLn $ seriesF @'[WorkWeek, WorkDay] $ hour 140+    putStrLn $ "The result:   " ++ formatHours 140+    putStrLn $ "With seriesF: " ++ (seriesF @'[WorkWeek, WorkDay] $ hour 140)+```++And the output will be++```haskell ignore+The result:   3ww2wd+With seriesF: 3ww2+1/2wd ```
README.md view
@@ -2,6 +2,7 @@  [![Hackage](https://img.shields.io/hackage/v/o-clock.svg)](https://hackage.haskell.org/package/o-clock) [![Build status](https://travis-ci.org/serokell/o-clock.svg?branch=master)](https://travis-ci.org/serokell/o-clock)+[![Stackage Nightly](http://stackage.org/package/o-clock/badge/nightly)](http://stackage.org/nightly/package/o-clock) [![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/serokell/o-clock/blob/master/LICENSE)  ## Overview@@ -129,11 +130,18 @@ After that we can simply print the output we wanted.  Thought we have special function for this kind of formatting purposes `seriesF`.-So the same result can be gained with the usage of it. Check it out:+So the similar result (but not rounded) can be gained with the usage of it. Check it out:  ```haskell main :: IO () main = do-    putStrLn $ formatHours 140-    putStrLn $ seriesF @'[WorkWeek, WorkDay] $ hour 140+    putStrLn $ "The result:   " ++ formatHours 140+    putStrLn $ "With seriesF: " ++ (seriesF @'[WorkWeek, WorkDay] $ hour 140)+```++And the output will be++```haskell ignore+The result:   3ww2wd+With seriesF: 3ww2+1/2wd ```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
o-clock.cabal view
@@ -1,5 +1,5 @@ name:                o-clock-version:             0.0.0+version:             0.1.0 synopsis:            Type-safe time library. description:         See README.md for details. homepage:            https://github.com/serokell/o-clock@@ -24,9 +24,9 @@ library   hs-source-dirs:      src   exposed-modules:     Time-                         Time.Formatting                          Time.Rational-                         Time.TimeStamp+                         Time.Series+                         Time.Timestamp                          Time.Units   ghc-options:         -Wall   build-depends:       base         >= 4.10  && < 5@@ -35,6 +35,9 @@   default-language:    Haskell2010   default-extensions:  OverloadedStrings                        RecordWildCards+                       ScopedTypeVariables+                       TypeApplications+                       TypeFamilies  executable play-o-clock   main-is:             Playground.hs@@ -51,7 +54,7 @@   main-is:             Spec.hs    other-modules:       Test.Time.Property-                       Test.Time.TimeStamp+                       Test.Time.Timestamp                        Test.Time.TypeSpec                        Test.Time.Units @@ -68,6 +71,17 @@   default-extensions:  OverloadedStrings                        RecordWildCards +test-suite o-clock-doctest+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Doctest.hs++  build-tool-depends:  doctest:doctest+  build-depends:       base    >= 4.10 && < 5+                     , doctest >= 0.13+                     , Glob    >= 0.9+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N+  default-language:    Haskell2010  test-suite readme-test   type:                exitcode-stdio-1.0
src/Time.hs view
@@ -4,13 +4,13 @@ -- can be found here: <https://github.com/serokell/o-clock#readme>  module Time-    ( module Time.Formatting-    , module Time.Rational-    , module Time.TimeStamp+    ( module Time.Rational+    , module Time.Series+    , module Time.Timestamp     , module Time.Units     ) where -import Time.Formatting import Time.Rational-import Time.TimeStamp+import Time.Series+import Time.Timestamp import Time.Units
− src/Time/Formatting.hs
@@ -1,78 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes  #-}-{-# LANGUAGE CPP                  #-}-{-# LANGUAGE DataKinds            #-}-{-# LANGUAGE FlexibleContexts     #-}-{-# LANGUAGE InstanceSigs         #-}-{-# LANGUAGE Rank2Types           #-}-{-# LANGUAGE ScopedTypeVariables  #-}-{-# LANGUAGE TypeApplications     #-}-{-# LANGUAGE TypeFamilies         #-}-{-# LANGUAGE TypeOperators        #-}-{-# LANGUAGE UndecidableInstances #-}--{- | This module introduces function to format time in desired way.--__Examples__-->>> seriesF @'[Day, Hour, Minute, Second] (minute 4000)-"2d18h40m"-->>> seriesF @'[Day, Minute, Second] (minute 4000)-"2d1120m"-->>> seriesF @'[Hour, Minute, Second] (sec 3601)-"1h1s"-->>>  seriesF @'[Hour, Second, Millisecond] (Time @Minute $ 3 % 2)-"90s"---}--module Time.Formatting-       ( Series (..)-       , unitsF-       ) where--import Time.Rational (Rat)-#if ( __GLASGOW_HASKELL__ >= 804 )-import Time.Rational (withRuntimeDivRat)-#endif-import Time.Units (AllTimes, KnownRatName, Time, floorUnit, toUnit)---- | Class for time formatting.-class Series (units :: [Rat]) where-    seriesF :: forall (someUnit :: Rat) . KnownRatName someUnit-            => Time someUnit-            -> String--instance Series ('[] :: [Rat]) where-    seriesF :: Time someUnit -> String-    seriesF _ = ""--instance (KnownRatName unit, Series units)-    => Series (unit ': units :: [Rat]) where-    seriesF :: forall (someUnit :: Rat) . KnownRatName someUnit-            => Time someUnit-            -> String-#if ( __GLASGOW_HASKELL__ >= 804 )-    seriesF t = let newUnit = withRuntimeDivRat @someUnit @unit $ toUnit @unit t-#else-    seriesF t = let newUnit = toUnit @unit t-#endif-                    format  = floorUnit newUnit-                    timeStr = case floor newUnit :: Int of-                                   0 -> ""-                                   _ -> show format-                in timeStr ++ seriesF @units @unit (newUnit - format)--{- | Similar to 'seriesF', but formats using all time units of the library.-->>> unitsF $ fortnight 5-"5fn"-->>> unitsF $ minute 4000-"2d18h40m"---}-unitsF :: forall unit . KnownRatName unit => Time unit -> String-unitsF = seriesF @AllTimes
src/Time/Rational.hs view
@@ -5,9 +5,6 @@ {-# LANGUAGE FlexibleContexts     #-} {-# LANGUAGE FlexibleInstances    #-} {-# LANGUAGE Rank2Types           #-}-{-# LANGUAGE ScopedTypeVariables  #-}-{-# LANGUAGE TypeApplications     #-}-{-# LANGUAGE TypeFamilies         #-} {-# LANGUAGE TypeOperators        #-} {-# LANGUAGE UndecidableInstances #-} @@ -27,6 +24,7 @@        , Gcd        , Normalize        , DivRat+       , type (>=%) #endif          -- Utilities@@ -44,7 +42,7 @@ import GHC.Natural (Natural) import GHC.Real (Ratio ((:%))) #if ( __GLASGOW_HASKELL__ >= 804 )-import GHC.TypeNats (Div, Mod)+import GHC.TypeNats (Div, Mod, type (<=?)) #endif import GHC.TypeNats (KnownNat, Nat, natVal) #if ( __GLASGOW_HASKELL__ >= 804 )@@ -184,6 +182,27 @@ -} type family Normalize (r :: Rat) :: Rat  where     Normalize (a :% b) = (a `Div` Gcd a b) :% (b `Div` Gcd a b)+++{- | Comparison of type-level rationals, as a function.++>>> :kind! (1 :% 42) >=% (5 :% 42)+(1 :% 42) >=% (5 :% 42) :: Bool+= 'False++>>> :kind! (5 :% 42) >=% (1 :% 42)+(5 :% 42) >=% (1 :% 42) :: Bool+= 'True++>>> :kind! (42 :% 1) >=% (42 :% 1)+(42 :% 1) >=% (42 :% 1) :: Bool+= 'True++-}+infix 4 >=%+type family (m :: Rat) >=% (n :: Rat) :: Bool where+    (a :% b) >=% (c :% d) = c * b <=? a * d+ #endif  -- | Rational numbers, with numerator and denominator of 'Natural' type.
+ src/Time/Series.hs view
@@ -0,0 +1,272 @@+{-# LANGUAGE AllowAmbiguousTypes  #-}+{-# LANGUAGE CPP                  #-}+{-# LANGUAGE DataKinds            #-}+{-# LANGUAGE FlexibleContexts     #-}+{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE InstanceSigs         #-}+{-# LANGUAGE Rank2Types           #-}+{-# LANGUAGE TypeOperators        #-}+{-# LANGUAGE UndecidableInstances #-}++-- | This module introduces function to format and parse time in desired way.++module Time.Series+       ( AllTimes+#if ( __GLASGOW_HASKELL__ >= 804 )+       , type (...)+#endif+         -- * Formatting+       , SeriesF (..)+       , unitsF++         -- * Parsing+       , SeriesP (..)+       , unitsP+       ) where++import Data.Char (isDigit, isLetter)+import Text.Read (readMaybe)+#if ( __GLASGOW_HASKELL__ >= 804 )+import Data.Kind (Constraint)+import Data.Type.Bool (type (&&), If)+import Data.Type.Equality (type (==))+import GHC.TypeLits (TypeError, ErrorMessage (Text))++import Time.Rational (type (>=%), withRuntimeDivRat)+#endif+import Time.Rational (Rat)+import Time.Units (Day, Fortnight, Hour, KnownRatName, Microsecond, Millisecond, Minute, Nanosecond,+                   Picosecond, Second, Time (..), Week, floorUnit, toUnit)++-- $setup+-- >>> import Time.Units (Time (..), fortnight, hour, minute, ms, sec)+-- >>> import Time.Timestamp ((+:+))+-- >>> import GHC.Real ((%))++++-- | Type-level list that consist of all times.+type AllTimes =+  '[ Fortnight, Week, Day, Hour, Minute, Second+   , Millisecond , Microsecond, Nanosecond, Picosecond+   ]++#if ( __GLASGOW_HASKELL__ >= 804 )+{- | Creates the list of time units in descending order by provided+the highest and the lowest bound of the desired list.+Throws the error when time units are not in the right order.++__Usage example:__++>>> seriesF @(Hour ... Second) $ hour 3 +:+ minute 5 +:+ sec 3 +:+ ms 123+"3h5m3+123/1000s"++-}+type family (from :: Rat) ... (to :: Rat) :: [Rat] where+    from ... to = If (IsDescending '[from, to])+                     (TakeWhileNot to (DropWhileNot from AllTimes))+                     (TypeError ('Text "Units should be in descending order"))++-- Drops wile not the required time unit in 'AllTimes'.+type family DropWhileNot (from :: Rat) (units :: [Rat]) :: [Rat] where+    DropWhileNot x '[] = '[]+    DropWhileNot x (u ': units) = If (u == x) (u ': units) (DropWhileNot x units)++-- Takes while not equal to the provided bound.+type family TakeWhileNot (to :: Rat) (units :: [Rat]) :: [Rat] where+    TakeWhileNot x '[] = '[]+    TakeWhileNot x (u ': units) = If (u == x) '[u] (u ': TakeWhileNot x units)++-- | Type family for verification of the descending order of the given+-- list of time units.+type family IsDescending (units :: [Rat]) :: Bool where+    IsDescending ('[])     = 'True+    IsDescending ('[unit]) = 'True+    IsDescending (unit1 ': unit2 ': units) =+        (unit1 >=% unit2) && (IsDescending (unit2 ': units))++type family DescendingConstraint (b :: Bool) :: Constraint where+    DescendingConstraint 'True  = ()  -- empty constraint; always satisfiable+    DescendingConstraint 'False = TypeError ('Text "List of units should be in descending order")+#endif++{- | Class for time formatting.++__Examples__++>>> seriesF @'[Day, Hour, Minute, Second] (minute 4000)+"2d18h40m"++>>> seriesF @'[Day, Minute, Second] (minute 4000)+"2d1120m"++>>> seriesF @'[Hour, Minute, Second] (sec 3601)+"1h1s"++>>>  seriesF @'[Hour, Second, Millisecond] (Time @Minute $ 3 % 2)+"90s"++>>> seriesF @'[Hour, Second] (minute 0)+"0h"++>>> seriesF @'[Hour, Minute, Second] (Time @Day (2 % 7))+"6h51m25+5/7s"++The received list should be in descending order. It would be verified at compile-time.+Example of the error from @ghci@:++#if ( __GLASGOW_HASKELL__ >= 804 )+>>> seriesF @'[Millisecond, Second] (minute 42)+...+    • List of units should be in descending order+    • In the expression: seriesF @'[Millisecond, Second] (minute 42)+      In an equation for ‘it’:+          it = seriesF @'[Millisecond, Second] (minute 42)+...+#endif++-}+class SeriesF (units :: [Rat]) where+    seriesF :: forall (someUnit :: Rat) . KnownRatName someUnit+            => Time someUnit+            -> String++instance SeriesF ('[] :: [Rat]) where+    seriesF :: Time someUnit -> String+    seriesF _ = ""++instance (KnownRatName unit) => SeriesF ('[unit] :: [Rat]) where+    seriesF :: forall (someUnit :: Rat) . KnownRatName someUnit+            => Time someUnit -> String+    seriesF t =+#if ( __GLASGOW_HASKELL__ >= 804 )+        let newTime = withRuntimeDivRat @someUnit @unit $ toUnit @unit t+#else+        let newTime = toUnit @unit t+#endif+        in show newTime++instance ( KnownRatName unit+         , SeriesF (nextUnit : units)+#if ( __GLASGOW_HASKELL__ >= 804 )+         , DescendingConstraint (IsDescending (unit ': nextUnit ': units))+#endif+         )+    => SeriesF (unit ': nextUnit ': units :: [Rat]) where+    seriesF :: forall (someUnit :: Rat) . KnownRatName someUnit+            => Time someUnit -> String+#if ( __GLASGOW_HASKELL__ >= 804 )+    seriesF t = let newUnit = withRuntimeDivRat @someUnit @unit $ toUnit @unit t+#else+    seriesF t = let newUnit = toUnit @unit t+#endif+                    flooredNewUnit = floorUnit newUnit+                    timeStr = case flooredNewUnit of+                                   0 -> ""+                                   _ -> show flooredNewUnit+                    nextUnit = newUnit - flooredNewUnit+                in if nextUnit == 0+                   then show newUnit+                   else timeStr ++ seriesF @(nextUnit ': units) @unit nextUnit++{- | Similar to 'seriesF', but formats using all time units of the library.++>>> unitsF $ fortnight 5+"5fn"++>>> unitsF $ minute 4000+"2d18h40m"++-}+unitsF :: forall unit . KnownRatName unit => Time unit -> String+unitsF = seriesF @AllTimes++{- | Class for time parsing.++Empty string on input will be parsed as 0 time of the required time unit:++>>> seriesP @'[Hour, Minute, Second] @Second ""+Just (0s)++__Examples__++>>> seriesP @'[Day, Hour, Minute, Second] @Minute "2d18h40m"+Just (4000m)++>>> seriesP @'[Day, Minute, Second] @Minute "2d1120m"+Just (4000m)++>>> seriesP @'[Hour, Minute, Second] @Second "1h1s"+Just (3601s)++>>> seriesP @'[Hour, Second, Millisecond] @Minute "90s"+Just (1+1/2m)++>>> seriesP @'[Hour, Second] @Second "11ns"+Nothing++>>> seriesP @'[Hour, Minute] @Minute "1+1/2h"+Nothing++>>> seriesP @'[Hour, Minute] @Minute "1+1/2m"+Just (1+1/2m)++>>> seriesP @'[Hour, Minute] @Minute "1h1+1/2m"+Just (61+1/2m)++__Note:__ The received list should be in descending order. It would be verified at compile-time.++-}+class SeriesP (units :: [Rat]) where+    seriesP :: forall (someUnit :: Rat) . KnownRatName someUnit+            => String -> Maybe (Time someUnit)++instance SeriesP '[] where+    seriesP _ = Nothing++instance (KnownRatName unit) => SeriesP '[unit] where+    seriesP :: forall (someUnit :: Rat) . KnownRatName someUnit+            => String -> Maybe (Time someUnit)+    seriesP ""  = Just $ Time 0+    seriesP str = readMaybeTime @unit str++instance ( KnownRatName unit+         , SeriesP (nextUnit : units)+#if ( __GLASGOW_HASKELL__ >= 804 )+         , DescendingConstraint (IsDescending (unit ': nextUnit ': units))+#endif+         )+         => SeriesP (unit ': nextUnit ': units :: [Rat]) where+    seriesP :: forall (someUnit :: Rat) . KnownRatName someUnit+            => String -> Maybe (Time someUnit)+    seriesP ""  = Just $ Time 0+    seriesP str = let (num, rest)  = span isDigit str+                      (u, nextStr) = span isLetter rest+                      maybeT = readMaybeTime @unit $ num ++ u+                  in case maybeT of+                         Nothing -> seriesP @(nextUnit ': units) str+                         Just t  -> (t +) <$> (seriesP @(nextUnit ': units) nextStr)++{- | Similar to 'seriesP', but parses using all time units of the library.++>>> unitsP @Second "1m"+Just (60s)++>>> unitsP @Minute "2d18h40m"+Just (4000m)++-}+unitsP :: forall unit . KnownRatName unit => String -> Maybe (Time unit)+unitsP = seriesP @AllTimes @unit++----------------------------------------------------------------------------+-- Util+----------------------------------------------------------------------------++readMaybeTime :: forall (unit :: Rat) (someUnit :: Rat) . (KnownRatName unit, KnownRatName someUnit)+              => String -> Maybe (Time someUnit)+readMaybeTime str =+#if ( __GLASGOW_HASKELL__ >= 804 )+    withRuntimeDivRat @unit @someUnit $+#endif+        toUnit @someUnit <$> (readMaybe @(Time unit) str)
− src/Time/TimeStamp.hs
@@ -1,59 +0,0 @@-{-# LANGUAGE DataKinds                  #-}-{-# LANGUAGE ExplicitForAll             #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ScopedTypeVariables        #-}-{-# LANGUAGE TypeFamilies               #-}---- | This module introduces 'TimeStamp' data type--- and corresponding functions for operations with time.--module Time.TimeStamp-       ( TimeStamp (..)-       , timeDiff-       , timeAdd-       , timeMul-       , timeDiv-       ) where--import Time.Rational (KnownRat, Rat, RatioNat)-import Time.Units (Time (..))---- | Similar to 'Time' but has no units and can be negative.-newtype TimeStamp = TimeStamp Rational-    deriving (Show, Read, Num, Eq, Ord, Enum, Fractional, Real, RealFrac)----- | Returns the result of comparison of two 'Timestamp's and--- the 'Time' of that difference of given time unit.-timeDiff :: forall (unit :: Rat) . KnownRat unit-         => TimeStamp-         -> TimeStamp-         -> (Ordering, Time unit)-timeDiff (TimeStamp a) (TimeStamp b) =-    let order = compare a b-        d = fromRational $ case order of-                EQ -> 0-                GT -> a - b-                LT -> b - a-    in (order, d)---- | Returns the result of addition of two 'Time' elements.-timeAdd :: forall (unit :: Rat) . KnownRat unit-        => Time unit-        -> Time unit-        -> Time unit-timeAdd = (+)---- | Returns the result of multiplication of two 'Time' elements.-timeMul :: forall (unit :: Rat) . KnownRat unit-        => RatioNat-        -> Time unit-        -> Time unit-timeMul n (Time t) = Time (n * t)---- | Returns the result of division of two 'Time' elements.-timeDiv :: forall (unit :: Rat) . KnownRat unit-        => Time unit-        -> Time unit-        -> RatioNat-timeDiv (Time t1) (Time t2) = t1 / t2
+ src/Time/Timestamp.hs view
@@ -0,0 +1,176 @@+{-# LANGUAGE DataKinds                  #-}+{-# LANGUAGE ExplicitForAll             #-}+{-# LANGUAGE FlexibleContexts           #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++-- | This module introduces 'Timestamp' data type+-- and corresponding functions for operations with time.++module Time.Timestamp+       ( Timestamp (..)+       , fromUnixTime+       , timeDiff+       , timeAdd+       , timeMul+       , (*:*)+       , timeDiv+       , (/:/)++         -- * Other operators+       , (+:+)+       , (-:-)+       , (-%-)++       ) where++import Time.Rational (KnownDivRat, KnownRat, Rat, RatioNat)+import Time.Units (Second, Time (..), sec, toUnit)++-- $setup+-- >>> import Time.Units (Minute, Second, minute, ms, sec)++-- | Similar to 'Time' but has no units and can be negative.+newtype Timestamp = Timestamp Rational+    deriving (Show, Read, Eq, Ord)++-- | Converts unix time to 'Timestamp'.+fromUnixTime :: Real a => a -> Timestamp+fromUnixTime = Timestamp . toRational++{- | Returns the result of comparison of two 'Timestamp's and+the 'Time' of that difference of given time unit.++>>> timeDiff @Second (Timestamp 4) (Timestamp 2)+(GT,2s)++>>>timeDiff @Minute (Timestamp 4) (Timestamp 2)+(GT,1/30m)++>>> timeDiff @Second (Timestamp 2) (Timestamp 4)+(LT,2s)++>>> timeDiff @Minute (Timestamp 2) (Timestamp 4)+(LT,1/30m)++-}+timeDiff :: forall (unit :: Rat) . KnownDivRat Second unit+         => Timestamp+         -> Timestamp+         -> (Ordering, Time unit)+timeDiff (Timestamp a) (Timestamp b) =+    let (order, r) = ratDiff a b+    in (order, toUnit $ sec $ fromRational r)++{- | Returns the result of addition of 'Time' with 'Timestamp' elements.++>>> sec 5 `timeAdd` (Timestamp 4)+Timestamp (9 % 1)++>>> minute 1 `timeAdd` (Timestamp 5)+Timestamp (65 % 1)++-}+timeAdd :: forall (unit :: Rat) . KnownDivRat unit Second+        => Time unit+        -> Timestamp+        -> Timestamp+timeAdd t (Timestamp ts) = Timestamp (toRational (unTime $ toUnit @Second t) + ts)++-- | Returns the result of multiplication of two 'Time' elements.+timeMul :: forall (unit :: Rat) . KnownRat unit+        => RatioNat+        -> Time unit+        -> Time unit+timeMul n (Time t) = Time (n * t)++{- | Operator version of 'timeMul'.++>>> 3 *:* sec 5+15s++>>> 2 *:* 3 *:* sec 5+30s++>>> 3 *:* 5 *:* 7 :: Time Second+105s++>>> ms 2000 +:+ 2 *:* sec 3+8s++-}+infixr 7 *:*+(*:*) :: forall (unit :: Rat) . KnownRat unit+      => RatioNat -> Time unit -> Time unit+(*:*) = timeMul++-- | Returns the result of division of two 'Time' elements.+timeDiv :: forall (unit :: Rat) . KnownRat unit+        => Time unit+        -> Time unit+        -> RatioNat+timeDiv (Time t1) (Time t2) = t1 / t2++{- | Operator version of 'timeDiv'.++>>> sec 15 /:/ sec 3+5 % 1++-}+infix 7 /:/+(/:/) :: forall (unit :: Rat) . KnownRat unit+      => Time unit -> Time unit -> RatioNat+(/:/) = timeDiv++-- | Sums times of different units.+--+-- >>> minute 1 +:+ sec 1+-- 61s+--+infixl 6 +:++(+:+) :: forall (unitResult :: Rat) (unitLeft :: Rat) . KnownDivRat unitLeft unitResult+      => Time unitLeft+      -> Time unitResult+      -> Time unitResult+t1 +:+ t2 = toUnit t1 + t2+{-# INLINE (+:+) #-}++-- | Substracts times of different units.+--+-- >>> minute 1 -:- sec 1+-- 59s+--+infixl 6 -:-+(-:-) :: forall (unitResult :: Rat) (unitLeft :: Rat) . KnownDivRat unitLeft unitResult+      => Time unitLeft+      -> Time unitResult+      -> Time unitResult+t1 -:- t2 = toUnit t1 - t2+{-# INLINE (-:-) #-}++{- | Similar to '-:-' but more safe and returns order in pair with resulting time difference.++>>> sec 5 -%- sec 3+(GT,2s)++>>> sec 5 -%- sec 6+(LT,1s)++-}+infix 6 -%-+(-%-) :: forall (unitResult :: Rat) (unitLeft :: Rat) . KnownDivRat unitLeft unitResult+      => Time unitLeft+      -> Time unitResult+      -> (Ordering, Time unitResult)+t1 -%- (Time t2Rat) =+    let (Time t1Rat) = toUnit @unitResult t1+        (order, rat) = ratDiff (toRational t1Rat) (toRational t2Rat)+    in (order, Time $ fromRational rat)++ratDiff :: Rational -> Rational -> (Ordering, Rational)+ratDiff r1 r2 =+    let order = compare r1 r2+        diff  = case order of+                     LT -> r2 - r1+                     GT -> r1 - r2+                     EQ -> 0+    in (order, diff)
src/Time/Units.hs view
@@ -7,9 +7,6 @@ {-# LANGUAGE FlexibleContexts           #-} {-# LANGUAGE InstanceSigs               #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ScopedTypeVariables        #-}-{-# LANGUAGE TypeApplications           #-}-{-# LANGUAGE TypeFamilies               #-} {-# LANGUAGE TypeOperators              #-} {-# LANGUAGE UndecidableInstances       #-} @@ -32,8 +29,6 @@        , Week        , Fortnight -       , AllTimes-        , UnitName        , KnownUnitName        , KnownRatName@@ -42,6 +37,7 @@         -- ** Creation helpers        , time        , floorUnit+       , toNum         , sec        , ms@@ -55,8 +51,6 @@        , week        , fortnight -       , (+:)-         -- ** Functions        , toUnit        , threadDelay@@ -73,9 +67,9 @@ import GHC.Natural (Natural) import GHC.Prim (coerce) import GHC.Read (Read (readPrec))-import GHC.Real (Ratio ((:%)), denominator, numerator, (%))+import GHC.Real (denominator, numerator, (%)) import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)-import Text.ParserCombinators.ReadP (ReadP, char, munch1, option, pfail)+import Text.ParserCombinators.ReadP (ReadP, char, munch1, option, pfail, (+++)) import Text.ParserCombinators.ReadPrec (ReadPrec, lift)  #if ( __GLASGOW_HASKELL__ >= 804 )@@ -126,13 +120,6 @@ newtype Time (rat :: Rat) = Time { unTime :: RatioNat }     deriving (Eq, Ord, Enum, Real, RealFrac, Generic) --- | Type-level list that consist of all times.-type AllTimes =-  '[ Fortnight, Week, Day, Hour, Minute, Second-   , Millisecond , Microsecond, Nanosecond, Picosecond-   ]-- -- | Type family for prettier 'show' of time units. type family UnitName (unit :: Rat) :: Symbol @@ -159,11 +146,25 @@ unitNameVal = symbolVal (Proxy @(UnitName unit))  instance KnownUnitName unit => Show (Time unit) where-    show (Time rat) = let numeratorStr   = show (numerator rat)-                          denominatorStr = case denominator rat of-                                                1 -> ""-                                                n -> '/' : show n-                      in numeratorStr ++ denominatorStr ++ unitNameVal @unit+    showsPrec p (Time t) = showParen (p > 6)+                              $ showsMixed t+                              . showString (unitNameVal @unit)+      where+        showsMixed 0 = showString "0"+        showsMixed rat =+          let (n,d) = (numerator rat, denominator rat)+              (q,r) = n `quotRem` d+              op = if q == 0 || r == 0 then "" else "+"+              quotStr = if q == 0+                          then id -- NB id === showString ""+                          else shows q+              remStr = if r == 0+                         then id+                         else shows r+                            . showString "/"+                            . shows d+          in+              quotStr . showString op . remStr  instance KnownUnitName unit => Read (Time unit) where     readPrec :: ReadPrec (Time unit)@@ -172,11 +173,19 @@         readP :: ReadP (Time unit)         readP = do             let naturalP = read <$> munch1 isDigit-            n <- naturalP-            m <- option 1 (char '/' *> naturalP)+            -- If a '+' is parsed as part of a mixed fraction, the other parts+            -- are no longer optional.  This separation is required to prevent+            -- e.g. "3+2" successfully parsing.+            let fullMixedExpr = (,,) <$> (naturalP <* char '+')+                                     <*> (naturalP <* char '/')+                                     <*> naturalP+            let improperExpr = (,,) 0 <$> naturalP+                                      <*> option 1 (char '/' *> naturalP)+            (q,r,d) <- fullMixedExpr +++ improperExpr+            let n = (q * d + r)             timeUnitStr <- munch1 isLetter             unless (timeUnitStr == unitNameVal @unit) pfail-            pure $ Time (n % m)+            pure $ Time (n % d)  -- | Has the same behavior as derived instance, but '*' operator -- throws the runtime error with 'error'.@@ -205,87 +214,87 @@ ----------------------------------------------------------------------------  -- | Creates 'Time' of some type from given 'Natural'.-time :: Natural -> Time unit-time n = Time (n :% 1)+time :: RatioNat -> Time unit+time n = Time n {-# INLINE time #-}  -- | Creates 'Second' from given 'Natural'. -- -- >>> sec 42--- 42s :: Time Second-sec :: Natural -> Time Second+-- 42s+sec :: RatioNat -> Time Second sec = time {-# INLINE sec #-}  -- | Creates 'Millisecond' from given 'Natural'. -- -- >>> ms 42--- 42ms :: Time Millisecond-ms :: Natural -> Time Millisecond+-- 42ms+ms :: RatioNat -> Time Millisecond ms = time {-# INLINE ms #-}  -- | Creates 'Microsecond' from given 'Natural'. -- -- >>> mcs 42--- 42mcs :: Time Microsecond-mcs :: Natural -> Time Microsecond+-- 42mcs+mcs :: RatioNat -> Time Microsecond mcs = time {-# INLINE mcs #-}  -- | Creates 'Nanosecond' from given 'Natural'. -- -- >>> ns 42--- 42ns :: Time Nanosecond-ns :: Natural -> Time Nanosecond+-- 42ns+ns :: RatioNat -> Time Nanosecond ns = time {-# INLINE ns #-}  -- | Creates 'Picosecond' from given 'Natural'. -- -- >>> ps 42--- 42ps :: Time Picosecond-ps :: Natural -> Time Picosecond+-- 42ps+ps :: RatioNat -> Time Picosecond ps = time {-# INLINE ps #-}  -- | Creates 'Minute' from given 'Natural'. -- -- >>> minute 42--- 42m :: Time Minute-minute :: Natural -> Time Minute+-- 42m+minute :: RatioNat -> Time Minute minute = time {-# INLINE minute #-}  -- | Creates 'Hour' from given 'Natural'. -- -- >>> hour 42--- 42h :: Time Hour-hour :: Natural -> Time Hour+-- 42h+hour :: RatioNat -> Time Hour hour = time {-# INLINE hour #-}  -- | Creates 'Day' from given 'Natural'. -- -- >>> day 42--- 42d :: Time Day-day :: Natural -> Time Day+-- 42d+day :: RatioNat -> Time Day day = time {-# INLINE day #-}  -- | Creates 'Week' from given 'Natural'. ----- >>> sec 42--- 42w :: Time Week-week :: Natural -> Time Week+-- >>> week 42+-- 42w+week :: RatioNat -> Time Week week = time {-# INLINE week #-}  -- | Creates 'Fortnight' from given 'Natural'. -- -- >>> fortnight 42--- 42fn :: Time Fortnight-fortnight :: Natural -> Time Fortnight+-- 42fn+fortnight :: RatioNat -> Time Fortnight fortnight = time {-# INLINE fortnight #-} @@ -302,20 +311,39 @@  -} floorUnit :: forall (unit :: Rat) . Time unit -> Time unit-floorUnit = time . floor+floorUnit = time . fromIntegral @Natural . floor --- | Sums times of different units.------ >>> minute 1 +: sec 1--- 61s----(+:) :: forall (unitResult :: Rat) (unitLeft :: Rat) . KnownDivRat unitLeft unitResult-     => Time unitLeft-     -> Time unitResult-     -> Time unitResult-t1 +: t2 = toUnit t1 + t2-{-# INLINE (+:) #-}+{- | Convert time to the 'Num' in given units. +For example, instead of writing++@+foo :: POSIXTime+foo = 10800  -- 3 hours+@++one can write more safe implementation:++@+foo = toNum @Second $ hour 3+@++__Examples:__++>>> toNum @Second @Natural $ hour 3+10800++>>> toNum @Minute @Int $ hour 3+180++>>> toNum @Hour @Natural $ hour 3+3++-}+toNum :: forall (unitTo :: Rat) n (unit :: Rat) . (KnownDivRat unit unitTo, Num n)+      => Time unit -> n+toNum = fromIntegral @Natural . floor . toUnit @unitTo+ ---------------------------------------------------------------------------- -- Functional ----------------------------------------------------------------------------@@ -329,7 +357,7 @@ 7/1000s  >>> toUnit @Week (Time @Day 45)-45/7w+6+3/7w  >>> toUnit @Second @Minute 3 180s@@ -352,9 +380,11 @@  any time-unit and operates in any MonadIO.  ->>> threadDelay $ sec 2->>> threadDelay (2 :: Time Second)->>> threadDelay @Second 2+@+__>>> threadDelay $ sec 2__+__>>> threadDelay (2 :: Time Second)__+__>>> threadDelay @Second 2__+@  -} threadDelay :: forall (unit :: Rat) m . (KnownDivRat unit Microsecond, MonadIO m)@@ -367,8 +397,11 @@ -- program in the given time unit. -- The precision of this result is implementation-dependent. ----- >>> getCPUTime @Second+-- @+-- __>>> getCPUTime @Second__ -- 1064046949/1000000000s+-- @+-- getCPUTime :: forall (unit :: Rat) m . (KnownDivRat Picosecond unit, MonadIO m)            => m (Time unit) getCPUTime = toUnit . ps . fromInteger <$> liftIO CPUTime.getCPUTime@@ -377,15 +410,21 @@ {- | Similar to 'Timeout.timeout' but receiving any time unit instead of number of microseconds. ->>> timeout (sec 1) (putStrLn "Hello O'Clock")+@+__>>> timeout (sec 1) (putStrLn "Hello O'Clock")__ Hello O'Clock Just ()+@ ->>> timeout (ps 1) (putStrLn "Hello O'Clock")+@+__>>> timeout (ps 1) (putStrLn "Hello O'Clock")__ Nothing+@ ->>> timeout (mcs 1) (putStrLn "Hello O'Clock")+@+__>>> timeout (mcs 1) (putStrLn "Hello O'Clock")__ HellNothing+@  -} timeout :: forall (unit :: Rat) m a . (MonadIO m, KnownDivRat unit Microsecond)
+ test/Doctest.hs view
@@ -0,0 +1,12 @@+module Main (main) where++import System.FilePath.Glob (glob)+import Test.DocTest (doctest)++main :: IO ()+main = do+    sourceFiles <- glob "src/**/*.hs"+    doctest $ "-XScopedTypeVariables" : "-XRecordWildCards"+            : "-XOverloadedStrings"   : "-XDataKinds"+            : "-XTypeFamilies"        : "-XTypeOperators"+            : "-XTypeApplications"    : sourceFiles
test/Spec.hs view
@@ -3,7 +3,7 @@ import Test.Tasty (defaultMain, testGroup)  import Test.Time.Property (hedgehogTestTrees)-import Test.Time.TimeStamp (timeStampTestTree)+import Test.Time.Timestamp (timeStampTestTree) import Test.Time.TypeSpec (runTypeSpecTests) import Test.Time.Units (unitsTestTree) @@ -15,7 +15,7 @@     -- * toUnit tests     -- * read tests     unitTests <- unitsTestTree-    -- TimeStamp tests+    -- Timestamp tests     tsTests   <- timeStampTestTree      let allTests = testGroup "O'Clock" $ [unitTests, tsTests] ++ hedgehogTestTrees
test/Test/Time/Property.hs view
@@ -19,7 +19,7 @@  import Time (Day, Fortnight, Hour, KnownRat, KnownRatName, Microsecond,              Millisecond, Minute, Nanosecond, Picosecond, Rat, RatioNat, Second,-             Time (..), Week, toUnit)+             Time (..), Week, toUnit, unitsF, unitsP) #if ( __GLASGOW_HASKELL__ >= 804 ) import Time (withRuntimeDivRat) #endif@@ -28,7 +28,7 @@ import qualified Hedgehog.Range as Range  hedgehogTestTrees :: [TestTree]-hedgehogTestTrees = [readShowTestTree, toUnitTestTree]+hedgehogTestTrees = [readShowTestTree, toUnitTestTree, seriesTestTree]  readShowTestTree :: TestTree readShowTestTree = testProperty "Hedgehog read . show == id" prop_readShowUnit@@ -36,6 +36,9 @@ toUnitTestTree :: TestTree toUnitTestTree = testProperty "Hedgehog toUnit @to @from . toUnit @from @to ≡ id' property" prop_toUnit +seriesTestTree :: TestTree+seriesTestTree = testProperty "Hedgehog unitsP . unitsF ≡ id" prop_series+ -- | Existential data type for 'Unit's. data AnyTime =  forall (unit :: Rat) . (KnownRatName unit)              => MkAnyTime (Time unit)@@ -78,6 +81,14 @@ #endif                       toUnit (toUnit @unitTo t) === t +-- | Verifier for @ seriesP . seriesF @.+verifySeries :: forall m . (MonadTest m) => AnyTime -> m ()+verifySeries (MkAnyTime anyT) = checkSeries anyT+  where+    checkSeries :: forall (unit :: Rat) . KnownRatName unit+                => Time unit -> m ()+    checkSeries t = unitsP @unit (unitsF t) === Just t+ -- | Generates random natural number up to 10^20. -- it receives the lower bound so that it wouldn't be possible -- to get 0 for denominator.@@ -108,3 +119,6 @@     t1 <- genAnyTime     t2 <- genAnyTime     verifyToUnit t1 t2++prop_series :: Property+prop_series = property $ genAnyTime >>= verifySeries
− test/Test/Time/TimeStamp.hs
@@ -1,39 +0,0 @@-{-# LANGUAGE DataKinds          #-}-{-# LANGUAGE ExplicitNamespaces #-}-{-# LANGUAGE TypeApplications   #-}-{-# LANGUAGE TypeOperators      #-}--module Test.Time.TimeStamp-       ( timeStampTestTree-       ) where--import Control.Exception (evaluate)-import Test.Tasty (TestTree)-import Test.Tasty.Hspec (Spec, anyException, describe, it, shouldBe, shouldThrow, testSpec)--import Time (Day, Hour, Microsecond, Picosecond, Second, TimeStamp (..), Week, timeAdd, timeDiff,-             timeDiv, timeMul)--timeStampTestTree :: IO TestTree-timeStampTestTree = testSpec "TimeStamp and time operations" spec_TimeStamp---spec_TimeStamp :: Spec-spec_TimeStamp = do-    describe "TimeDiff" $ do-        it "1 is less than 5, diff is 4 seconds" $-            timeDiff @Second (TimeStamp 1) (TimeStamp 5) `shouldBe` (LT, 4)-        it "100 is greater that 11, diff is 89 Days" $-            timeDiff @Day (TimeStamp 100) (TimeStamp 11) `shouldBe` (GT, 89)-        it "42 is equal to 42, diff is 0 Weeks" $-            timeDiff @Week (TimeStamp 42) (TimeStamp 42) `shouldBe` (EQ, 0)-        it "3 hours + 7 hours is 10"  $-            timeAdd @Hour 3 7 `shouldBe` 10-        it "twice 21 mcs is 42 mcs"  $-            timeMul @Microsecond 2 21 `shouldBe` 42-        it "zero x 42 s is zero"  $-            timeMul @Second 0 42 `shouldBe` 0-        it "84 picoseconds divide by 2 is 42"  $-            timeDiv @Picosecond 84 2 `shouldBe` 42-        it "fails when trying to divide by zero"  $-            evaluate (timeDiv @Second 42 0) `shouldThrow` anyException
+ test/Test/Time/Timestamp.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE DataKinds          #-}+{-# LANGUAGE ExplicitNamespaces #-}+{-# LANGUAGE TypeApplications   #-}+{-# LANGUAGE TypeOperators      #-}++module Test.Time.Timestamp+       ( timeStampTestTree+       ) where++import Control.Exception (evaluate)+import Test.Tasty (TestTree)+import Test.Tasty.Hspec (Spec, anyException, describe, it, shouldBe, shouldThrow, testSpec)++import Time (Hour, Microsecond, Minute, Picosecond, Second, Timestamp (..), Week, timeAdd, timeDiff,+             timeDiv, timeMul)++timeStampTestTree :: IO TestTree+timeStampTestTree = testSpec "Timestamp and time operations" spec_Timestamp+++spec_Timestamp :: Spec+spec_Timestamp = do+    describe "TimeDiff" $ do+        it "1 is less than 5, diff is 4 seconds" $+            timeDiff @Second (Timestamp 1) (Timestamp 5) `shouldBe` (LT, 4)+        it "100 is greater that 40, diff is 60 sec == 1 min" $+            timeDiff @Minute (Timestamp 100) (Timestamp 40) `shouldBe` (GT, 1)+        it "42 is equal to 42, diff is 0 Weeks" $+            timeDiff @Week (Timestamp 42) (Timestamp 42) `shouldBe` (EQ, 0)+        it "3 hours offset 7 is 10"  $+            timeAdd @Hour 3 (Timestamp 7) `shouldBe` (Timestamp 10807)+        it "twice 21 mcs is 42 mcs"  $+            timeMul @Microsecond 2 21 `shouldBe` 42+        it "zero x 42 s is zero"  $+            timeMul @Second 0 42 `shouldBe` 0+        it "84 picoseconds divide by 2 is 42"  $+            timeDiv @Picosecond 84 2 `shouldBe` 42+        it "fails when trying to divide by zero"  $+            evaluate (timeDiv @Second 42 0) `shouldThrow` anyException
test/Test/Time/TypeSpec.hs view
@@ -12,7 +12,7 @@  import Time.Rational ((:%)) #if ( __GLASGOW_HASKELL__ >= 804 )-import Time.Rational (type (/), Gcd, Normalize)+import Time.Rational (type (/), type (>=%), Gcd, Normalize) #endif import Time.Units (Day, Fortnight, Hour, Microsecond, Millisecond, Minute,                    Nanosecond, Picosecond, Second, UnitName, Week)@@ -23,6 +23,7 @@     print typeSpec_Gcd     print typeSpec_Normalize     print typeSpec_DivRat+    print typeSpec_Compare #endif     print typeSpec_UnitCalculation     print typeSpec_UnitNames@@ -90,6 +91,20 @@      -*- It "5%6 / 25%3 = 1%10" ((5 / 6) / (25 / 3) `Is` (1 :% 10))  typeSpec_DivRat = Valid++typeSpec_Compare ::++  ">=%"+  ######++    "Comparing"+    ~~~~~~~~~~~~+         It "2%7 >=% 2%7 = True"   (((2 :% 7)  >=% (2 :% 7))  `Is` 'True)+     -*- It "2%7 >=% 7%2 = False"  (((2 :% 7)  >=% (7 :% 2))  `Is` 'False)+     -*- It "5%42 >=% 1%42 = True" (((5 :% 42) >=% (1 :% 42)) `Is` 'True)++typeSpec_Compare = Valid+ #endif  typeSpec_UnitCalculation ::
test/Test/Time/Units.hs view
@@ -14,7 +14,7 @@  import Time (Day, Hour, Microsecond, Millisecond, Minute, Picosecond, Second, Time (..), Week, day,              floorUnit, fortnight, hour, mcs, minute, ms, ns, ps, sec, seriesF, toUnit, unitsF,-             week, (+:))+             week, (+:+))  unitsTestTree :: IO TestTree unitsTestTree = testSpec "Units" spec_Units@@ -45,6 +45,14 @@             read @(Time Second) "7/2s" `shouldBe` Time (7 :% 2)         it "fails when '-4s' is expected as seconds" $             evaluate (read @(Time Second) "-4s") `shouldThrow` anyException+        it "parses '25+5/7s' as 180/7 seconds" $+            read @(Time Second) "25+5/7s" `shouldBe` Time (180 :% 7)+        it "fails when '3+2s' is expected as seconds" $+            evaluate (read @(Time Second) "3+2s") `shouldThrow` anyException+        it "fails when '+3s' is expected as seconds" $+            evaluate (read @(Time Second) "+3s") `shouldThrow` anyException+        it "fails when '/3s' is expected as seconds" $+            evaluate (read @(Time Second) "/3s") `shouldThrow` anyException         it "parses '14/2h' as 7 hours" $             read @(Time Hour) "14/2h" `shouldBe` 7         it "fails when '14/2h' expected as 7 seconds" $@@ -76,9 +84,9 @@             unitsF (minute 4000) `shouldBe` "2d18h40m"         it "42 fortnights should be formatted like 42fn" $             unitsF (fortnight 42) `shouldBe` "42fn"-        it "empty when receive zero time" $-            unitsF (Time @Hour 0) `shouldBe` ""+        it "the first zero time unit when receive zero time" $+            unitsF (Time @Hour 0) `shouldBe` "0fn"         it "sums all time units" $-            unitsF (  fortnight 1 +: week 1 +: day 1 +: hour 1 +: minute 1-                   +: sec 1 +: ms 1 +: mcs 1 +: ns 1 +: ps 1+            unitsF (  fortnight 1 +:+ week 1 +:+ day 1 +:+ hour 1 +:+ minute 1+                  +:+ sec 1 +:+ ms 1 +:+ mcs 1 +:+ ns 1 +:+ ps 1                    ) `shouldBe` "1fn1w1d1h1m1s1ms1mcs1ns1ps"