packages feed

nanotime 0.1.0 → 0.2.0

raw patch · 3 files changed

+170/−61 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Nanotime: [unTimeDelta] :: TimeDelta -> Word64
- Nanotime: assertingNonNegative :: (HasCallStack, Ord a, Num a, Show a) => a -> a
- Nanotime: diffTimeDelta :: TimeDelta -> TimeDelta -> Maybe TimeDelta
- Nanotime: instance GHC.Generics.Generic Nanotime.MonoTime
- Nanotime: instance GHC.Generics.Generic Nanotime.NtpTime
- Nanotime: instance GHC.Generics.Generic Nanotime.PosixTime
- Nanotime: instance GHC.Generics.Generic Nanotime.TimeDelta
- Nanotime: newtype TimeDelta
+ Nanotime: SignNeg :: Sign
+ Nanotime: SignPos :: Sign
+ Nanotime: [tdMag] :: TimeDelta -> !Word64
+ Nanotime: [tdSign] :: TimeDelta -> !Sign
+ Nanotime: data Sign
+ Nanotime: data TimeDelta
+ Nanotime: instance GHC.Classes.Eq Nanotime.Sign
+ Nanotime: instance GHC.Classes.Ord Nanotime.Sign
+ Nanotime: instance GHC.Enum.Bounded Nanotime.Sign
+ Nanotime: instance GHC.Enum.Enum Nanotime.Sign
+ Nanotime: instance GHC.Show.Show Nanotime.Sign
- Nanotime: TimeDelta :: Word64 -> TimeDelta
+ Nanotime: TimeDelta :: !Sign -> !Word64 -> TimeDelta
- Nanotime: diffTime :: TimeLike t => t -> t -> Maybe TimeDelta
+ Nanotime: diffTime :: TimeLike t => t -> t -> TimeDelta
- Nanotime: timeDeltaFromFracSecs :: (Real a, Show a) => a -> TimeDelta
+ Nanotime: timeDeltaFromFracSecs :: Real a => a -> TimeDelta
- Nanotime: timeDeltaFromNanos :: (Integral a, Show a) => a -> TimeDelta
+ Nanotime: timeDeltaFromNanos :: Integral a => a -> TimeDelta
- Nanotime: timeDeltaToNanos :: TimeDelta -> Word64
+ Nanotime: timeDeltaToNanos :: TimeDelta -> Maybe Word64

Files

nanotime.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           nanotime-version:        0.1.0+version:        0.2.0 synopsis:       a tiny time library description:    Please see the README on GitHub at <https://github.com/ejconlon/nanotime#readme> homepage:       https://github.com/ejconlon/nanotime#readme
src/Nanotime.hs view
@@ -1,10 +1,10 @@ module Nanotime-  ( TimeDelta (..)+  ( Sign (..)+  , TimeDelta (..)   , timeDeltaFromFracSecs   , timeDeltaFromNanos   , timeDeltaToFracSecs   , timeDeltaToNanos-  , diffTimeDelta   , threadDelayDelta   , TimeLike (..)   , awaitDelta@@ -17,100 +17,175 @@   , NtpTime (..)   , posixToNtp   , ntpToPosix-  , assertingNonNegative   ) where  import Control.Concurrent (threadDelay) import Data.Bits (Bits (..)) import Data.Fixed (Fixed (..), Pico)-import Data.Semigroup (Sum (..)) import Data.Time.Clock (nominalDiffTimeToSeconds) import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Word (Word32, Word64) import GHC.Clock (getMonotonicTimeNSec)-import GHC.Generics (Generic) import GHC.Stack (HasCallStack) -assertingNonNegative :: (HasCallStack, Ord a, Num a, Show a) => a -> a-assertingNonNegative a =-  if a < 0-    then error ("Required non-negative value but got " ++ show a)-    else a+-- | Sign (negative or positive) of a magnitude of time difference+data Sign = SignNeg | SignPos+  deriving stock (Eq, Ord, Show, Enum, Bounded) --- | Non-negative time difference in nanoseconds since last event--- Like a 'Nano' (`Fixed E9`) but a machine word.-newtype TimeDelta = TimeDelta {unTimeDelta :: Word64}-  deriving stock (Eq, Show, Ord, Generic, Bounded)-  deriving newtype (Num)-  deriving (Semigroup, Monoid) via (Sum Word64)+-- | Signed time difference in nanoseconds since last event+-- Like a 'Nano' (`Fixed E9`) but using a machine word with explicit sign.+data TimeDelta = TimeDelta+  { tdSign :: !Sign+  , tdMag :: !Word64+  }+  deriving stock (Show) +instance Eq TimeDelta where+  TimeDelta s1 m1 == TimeDelta s2 m2 =+    (m1 == 0 && m2 == 0) || (s1 == s2 && m1 == m2)++instance Ord TimeDelta where+  compare (TimeDelta s1 m1) (TimeDelta s2 m2) =+    if m1 == 0 && m2 == 0+      then EQ+      else case s1 of+        SignPos ->+          case s2 of+            SignPos -> compare m1 m2+            SignNeg -> GT+        SignNeg ->+          case s2 of+            SignPos -> LT+            SignNeg -> compare m2 m1++instance Bounded TimeDelta where+  minBound = TimeDelta SignNeg maxBound+  maxBound = TimeDelta SignPos maxBound++instance Semigroup TimeDelta where+  td1@(TimeDelta s1 m1) <> td2@(TimeDelta s2 m2) =+    if+      | m1 == 0 -> td2+      | m2 == 0 -> td1+      | s1 == s2 -> mkTimeDelta s1 (m1 + m2)+      | otherwise ->+          if m1 >= m2+            then mkTimeDelta s1 (m1 - m2)+            else mkTimeDelta s2 (m2 - m1)++instance Monoid TimeDelta where+  mempty = TimeDelta SignPos 0++instance Num TimeDelta where+  (+) = (<>)+  (*) = error "TimeDelta multiplication has no meaning"+  abs (TimeDelta _ m) = TimeDelta SignPos m+  signum (TimeDelta s m) =+    if m == 0+      then 0+      else case s of+        SignPos -> 1+        SignNeg -> -1+  fromInteger i =+    if i >= 0+      then TimeDelta SignPos (fromInteger i)+      else TimeDelta SignNeg (fromInteger (negate i))+  negate td@(TimeDelta s m) =+    if m == 0 && s == SignPos+      then td+      else case s of+        SignPos -> TimeDelta SignNeg m+        SignNeg -> TimeDelta SignPos m++-- private+mkTimeDelta :: Sign -> Word64 -> TimeDelta+mkTimeDelta s m =+  if m == 0+    then TimeDelta SignPos m+    else TimeDelta s m+ -- | Return a 'TimeDelta' corresponding the the given number of fractional seconds. -- (For example, 1.5 represents one and a half seconds.)-timeDeltaFromFracSecs :: (Real a, Show a) => a -> TimeDelta-timeDeltaFromFracSecs d = TimeDelta (round (1000000000 * toRational (assertingNonNegative d)))+timeDeltaFromFracSecs :: (Real a) => a -> TimeDelta+timeDeltaFromFracSecs d =+  if d >= 0+    then TimeDelta SignPos (round (1000000000 * toRational d))+    else TimeDelta SignNeg (round (1000000000 * toRational (negate d)))  -- | Return a 'TimeDelta' corresponding the the given number of nanoseconds. -- (For example, 1000000000 represends one second.)-timeDeltaFromNanos :: (Integral a, Show a) => a -> TimeDelta-timeDeltaFromNanos = TimeDelta . fromIntegral . assertingNonNegative+timeDeltaFromNanos :: (Integral a) => a -> TimeDelta+timeDeltaFromNanos = fromIntegral -timeDeltaToFracSecs :: (Fractional a) => TimeDelta -> a-timeDeltaToFracSecs (TimeDelta n) = fromIntegral n / 1000000000+-- private+timeDeltaFromDiff :: Word64 -> Word64 -> TimeDelta+timeDeltaFromDiff end start =+  if end >= start+    then TimeDelta SignPos (end - start)+    else TimeDelta SignNeg (start - end) -timeDeltaToNanos :: TimeDelta -> Word64-timeDeltaToNanos = unTimeDelta+-- private+timeDeltaAdd :: Word64 -> TimeDelta -> Word64+timeDeltaAdd t (TimeDelta s m) =+  case s of+    SignPos -> t + m+    SignNeg -> t - m --- | Return the difference of two time deltas-diffTimeDelta-  :: TimeDelta-  -- ^ the "larger" delta-  -> TimeDelta-  -- ^ the "smaller" delta-  -> Maybe TimeDelta-  -- ^ difference between the two (Nothing if negative)-diffTimeDelta (TimeDelta big) (TimeDelta small) =-  if big <= small-    then Nothing-    else Just (TimeDelta (big - small))+timeDeltaToFracSecs :: (Fractional a) => TimeDelta -> a+timeDeltaToFracSecs (TimeDelta s m) =+  let a = fromIntegral m / 1000000000+  in  case s of+        SignPos -> a+        SignNeg -> negate a +timeDeltaToNanos :: TimeDelta -> Maybe Word64+timeDeltaToNanos (TimeDelta s m) =+  case s of+    SignNeg -> Nothing+    SignPos -> Just m+ threadDelayDelta :: TimeDelta -> IO ()-threadDelayDelta (TimeDelta td) = threadDelay (fromIntegral (div td 1000))+threadDelayDelta (TimeDelta s m) =+  case s of+    SignNeg -> pure ()+    SignPos -> threadDelay (fromIntegral (div m 1000))  class (Ord t) => TimeLike t where-  diffTime :: t -> t -> Maybe TimeDelta+  -- | `diffTime end start` computes `end - start`+  diffTime :: t -> t -> TimeDelta++  -- | `addTime start (diffTime end start) == end`   addTime :: t -> TimeDelta -> t+   currentTime :: IO t  awaitDelta :: (TimeLike t) => t -> TimeDelta -> IO t awaitDelta m t = do   let target = addTime m t   cur <- currentTime-  case diffTime target cur of-    Nothing -> pure cur-    Just td -> target <$ threadDelayDelta td+  let td = diffTime target cur+  target <$ threadDelayDelta td  newtype PosixTime = PosixTime {unPosixTime :: Word64}-  deriving stock (Eq, Show, Ord, Generic, Bounded)+  deriving stock (Eq, Show, Ord, Bounded) +-- private e9W :: Word64 e9W = 1000000000 +-- private picoToNanoWord :: Pico -> Word64 picoToNanoWord (MkFixed i) = fromInteger (div i 1000) -picoFromNanoWord :: Word64 -> Pico-picoFromNanoWord j = MkFixed (1000 * toInteger j)- instance TimeLike PosixTime where-  diffTime (PosixTime t2) (PosixTime t1) =-    if t2 <= t1 then Nothing else Just (TimeDelta (t2 - t1))-  addTime (PosixTime t) (TimeDelta d) = PosixTime (t + d)+  diffTime (PosixTime t2) (PosixTime t1) = timeDeltaFromDiff t2 t1+  addTime (PosixTime t) td = PosixTime (timeDeltaAdd t td)   currentTime = fmap (PosixTime . picoToNanoWord . nominalDiffTimeToSeconds) getPOSIXTime  -- | Monotonic time in nanoseconds since some unspecified epoch (see 'getMonotonicTimeNs') newtype MonoTime = MonoTime {unMonoTime :: Word64}-  deriving stock (Eq, Show, Ord, Generic, Bounded)+  deriving stock (Eq, Show, Ord, Bounded)  monoTimeFromFracSecs :: (Real a, Show a) => a -> MonoTime monoTimeFromFracSecs d = MonoTime (round (1000000000 * toRational (assertingNonNegative d)))@@ -125,48 +200,61 @@ monoTimeToNanos = unMonoTime  instance TimeLike MonoTime where-  diffTime (MonoTime t2) (MonoTime t1) =-    if t2 <= t1 then Nothing else Just (TimeDelta (t2 - t1))-  addTime (MonoTime t) (TimeDelta d) = MonoTime (t + d)+  diffTime (MonoTime t2) (MonoTime t1) = timeDeltaFromDiff t2 t1+  addTime (MonoTime t) td = MonoTime (timeDeltaAdd t td)   currentTime = fmap MonoTime getMonotonicTimeNSec  newtype NtpTime = NtpTime {unNtpTime :: Word64}-  deriving stock (Eq, Show, Ord, Generic, Bounded)+  deriving stock (Eq, Show, Ord, Bounded) +-- private nanoWordToSplit :: Word64 -> (Word32, Word32) nanoWordToSplit j =   let whole = div j e9W       part = j - e9W * whole   in  (fromIntegral whole, fromIntegral part) +-- private nanoWordFromSplit :: Word32 -> Word32 -> Word64 nanoWordFromSplit whole part = e9W * fromIntegral whole + fromIntegral part +-- private ntpFromSplit :: Word32 -> Word32 -> NtpTime ntpFromSplit whole part = NtpTime (shiftL (fromIntegral whole) 32 .|. fromIntegral part) +-- private ntpToSplit :: NtpTime -> (Word32, Word32) ntpToSplit (NtpTime k) = (fromIntegral (shiftR k 32), fromIntegral k) +-- private -- Difference in nano seconds between 1/1/1900 and 1/1/1970 -- 1900 is the NTP epoch, 1970 is the unix epoch ntpEpochDiffSeconds :: Word32 ntpEpochDiffSeconds = 2208988800 +-- | Convert 'NtpTime' to 'PosixTime' posixToNtp :: PosixTime -> NtpTime posixToNtp (PosixTime j) =   let (whole, part) = nanoWordToSplit j       whole' = whole + ntpEpochDiffSeconds   in  ntpFromSplit whole' part +-- | Convert 'NtpTime' to 'PosixTime' ntpToPosix :: NtpTime -> PosixTime ntpToPosix k =   let (whole, part) = ntpToSplit k       whole' = whole - ntpEpochDiffSeconds   in  PosixTime (nanoWordFromSplit whole' part) --- Probably best to do time arithmetic directly on PosixTime+-- (Probably best to do time arithmetic directly on PosixTime) instance TimeLike NtpTime where   diffTime n2 n1 = diffTime (ntpToPosix n2) (ntpToPosix n1)   addTime n d = posixToNtp (addTime (ntpToPosix n) d)   currentTime = fmap posixToNtp currentTime++-- private+assertingNonNegative :: (HasCallStack, Ord a, Num a, Show a) => a -> a+assertingNonNegative a =+  if a < 0+    then error ("Required non-negative value but got " ++ show a)+    else a
test/Main.hs view
@@ -3,19 +3,40 @@   ) where +import Data.Word (Word64)+import Nanotime import Test.Tasty (TestTree, defaultMain, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) -testDummy :: TestTree-testDummy = testCase "dummy" $ do-  let actual = (1 + 1) :: Int-      expected = 2 :: Int-  actual @?= expected+nsPerSec :: Word64+nsPerSec = 1000000000 +testDelta :: TestTree+testDelta = testCase "delta" $ do+  TimeDelta SignPos 0 == TimeDelta SignNeg 0 @?= True+  TimeDelta SignPos 1 == TimeDelta SignPos 1 @?= True+  TimeDelta SignPos 1 == TimeDelta SignNeg 1 @?= False+  TimeDelta SignPos 1 == TimeDelta SignPos 2 @?= False+  compare (TimeDelta SignPos 0) (TimeDelta SignNeg 0) @?= EQ+  compare (TimeDelta SignPos 1) (TimeDelta SignPos 1) @?= EQ+  compare (TimeDelta SignPos 1) (TimeDelta SignNeg 1) @?= GT+  compare (TimeDelta SignPos 1) (TimeDelta SignPos 2) @?= LT+  0 @?= TimeDelta SignPos 0+  mempty @TimeDelta @?= 0+  timeDeltaFromFracSecs @Rational 0 @?= 0+  timeDeltaFromFracSecs @Rational 1 @?= TimeDelta SignPos nsPerSec+  timeDeltaFromFracSecs @Rational (-1) @?= TimeDelta SignNeg nsPerSec+  negate @TimeDelta 0 @?= 0+  negate (TimeDelta SignPos 1) @?= TimeDelta SignNeg 1+  negate (TimeDelta SignNeg 1) @?= TimeDelta SignPos 1+  TimeDelta SignPos 1 + TimeDelta SignPos 2 @?= TimeDelta SignPos 3+  TimeDelta SignPos 1 - TimeDelta SignPos 2 @?= TimeDelta SignNeg 1+  TimeDelta SignPos 2 - TimeDelta SignPos 1 @?= TimeDelta SignPos 1+ main :: IO () main =   defaultMain $     testGroup       "Nanotime"-      [ testDummy+      [ testDelta       ]