1{-# OPTIONS_HADDOCK hide #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4{-# LANGUAGE ScopedTypeVariables #-}
9-- Module : Streamly.Internal.Data.Time.Units
10-- Copyright : (c) 2019 Harendra Kumar
13-- Maintainer : streamly@composewell.com
14-- Stability : experimental
17module Streamly.Internal.Data.Time.Units
19 -- * Time Unit Conversions
31 -- * Absolute times (using TimeSpec)
36 -- * Relative times (using TimeSpec)
43 -- * Relative times (using NanoSecond64)
54import Text.Printf (printf)
56-------------------------------------------------------------------------------
58-------------------------------------------------------------------------------
60{-# INLINE tenPower3 #-}
64{-# INLINE tenPower6 #-}
68{-# INLINE tenPower9 #-}
72-------------------------------------------------------------------------------
73-- Time Unit Representations
74-------------------------------------------------------------------------------
76-- XXX We should be able to use type families to use different represenations
86-- Double or Fixed would be a much better representation so that we do not lose
87-- information between conversions. However, for faster arithmetic operations
88-- we use an 'Int64' here. When we need convservation of values we can use a
89-- different system of units with a Fixed precision.
91-------------------------------------------------------------------------------
93-------------------------------------------------------------------------------
95-- | An 'Int64' time representation with a nanosecond resolution. It can
96-- represent time up to ~292 years.
97newtype NanoSecond64 = NanoSecond64 Int64
109-- | An 'Int64' time representation with a microsecond resolution.
110-- It can represent time up to ~292,000 years.
111newtype MicroSecond64 = MicroSecond64 Int64
123-- | An 'Int64' time representation with a millisecond resolution.
124-- It can represent time up to ~292 million years.
125newtype MilliSecond64 = MilliSecond64 Int64
137-------------------------------------------------------------------------------
139-------------------------------------------------------------------------------
141-------------------------------------------------------------------------------
142-- TimeSpec representation
143-------------------------------------------------------------------------------
145-- A structure storing seconds and nanoseconds as 'Int64' is the simplest and
146-- fastest way to store practically large quantities of time with efficient
147-- arithmetic operations. If we store nanoseconds using 'Integer' it can store
148-- practically unbounded quantities but it may not be as efficient to
149-- manipulate in performance critical applications. XXX need to measure the
152-- | Data type to represent practically large quantities of time efficiently.
153-- It can represent time up to ~292 billion years at nanosecond resolution.
154data TimeSpec = TimeSpec
155 { sec :: {-# UNPACK #-} !Int64 -- ^ seconds
156 , nsec :: {-# UNPACK #-} !Int64 -- ^ nanoseconds
157 } deriving (Eq, Read, Show)
159-- We assume that nsec is always less than 10^9. When TimeSpec is negative then
160-- both sec and nsec are negative.
161instance Ord TimeSpec where
162 compare (TimeSpec s1 ns1) (TimeSpec s2 ns2) =
167-- make sure nsec is less than 10^9
168{-# INLINE addWithOverflow #-}
169addWithOverflow :: TimeSpec -> TimeSpec -> TimeSpec
170addWithOverflow (TimeSpec s1 ns1) (TimeSpec s2 ns2) =
172 (s', ns) = if (nsum > tenPower9 || nsum < negate tenPower9)
173 then nsum `divMod` tenPower9
175 in TimeSpec (s1 + s2 + s') ns
177-- make sure both sec and nsec have the same sign
178{-# INLINE adjustSign #-}
179adjustSign :: TimeSpec -> TimeSpec
180adjustSign (t@(TimeSpec s ns)) =
182 then TimeSpec (s - 1) (ns + tenPower9)
183 else if (s < 0 && ns > 0)
184 then TimeSpec (s + 1) (ns - tenPower9)
187{-# INLINE timeSpecToInteger #-}
188timeSpecToInteger :: TimeSpec -> Integer
189timeSpecToInteger (TimeSpec s ns) = toInteger $ s * tenPower9 + ns
191instance Num TimeSpec where
193 t1 + t2 = adjustSign (addWithOverflow t1 t2)
195 -- XXX will this be more optimal if imlemented without "negate"?
197 t1 - t2 = t1 + (negate t2)
198 t1 * t2 = fromInteger $ timeSpecToInteger t1 * timeSpecToInteger t2
200 {-# INLINE negate #-}
201 negate (TimeSpec s ns) = TimeSpec (negate s) (negate ns)
203 abs (TimeSpec s ns) = TimeSpec (abs s) (abs ns)
204 {-# INLINE signum #-}
205 signum (TimeSpec s ns) | s == 0 = TimeSpec (signum ns) 0
206 | otherwise = TimeSpec (signum s) 0
207 -- This is fromNanoSecond64 Integer
208 {-# INLINE fromInteger #-}
209 fromInteger nanosec = TimeSpec (fromInteger s) (fromInteger ns)
210 where (s, ns) = nanosec `divMod` toInteger tenPower9
212-------------------------------------------------------------------------------
213-- Time unit conversions
214-------------------------------------------------------------------------------
216-- TODO: compare whether using TimeSpec instead of Integer provides significant
217-- performance boost. If not then we can just use Integer nanoseconds and get
218-- rid of TimeUnitWide.
220-- | A type class for converting between time units using 'Integer' as the
221-- intermediate and the widest representation with a nanosecond resolution.
222-- This system of units can represent arbitrarily large times but provides
223-- least efficient arithmetic operations due to 'Integer' arithmetic.
225-- NOTE: Converting to and from units may truncate the value depending on the
226-- original value and the size and resolution of the destination unit.
228class TimeUnitWide a where
229 toTimeInteger :: a -> Integer
230 fromTimeInteger :: Integer -> a
233-- | A type class for converting between units of time using 'TimeSpec' as the
234-- intermediate representation. This system of units can represent up to ~292
235-- billion years at nanosecond resolution with reasonably efficient arithmetic
238-- NOTE: Converting to and from units may truncate the value depending on the
239-- original value and the size and resolution of the destination unit.
240class TimeUnit a where
241 toTimeSpec :: a -> TimeSpec
242 fromTimeSpec :: TimeSpec -> a
244-- XXX we can use a fromNanoSecond64 for conversion with overflow check and
245-- fromNanoSecond64Unsafe for conversion without overflow check.
247-- | A type class for converting between units of time using 'Int64' as the
248-- intermediate representation with a nanosecond resolution. This system of
249-- units can represent up to ~292 years at nanosecond resolution with fast
250-- arithmetic operations.
252-- NOTE: Converting to and from units may truncate the value depending on the
253-- original value and the size and resolution of the destination unit.
254class TimeUnit64 a where
255 toNanoSecond64 :: a -> NanoSecond64
256 fromNanoSecond64 :: NanoSecond64 -> a
258-------------------------------------------------------------------------------
260-------------------------------------------------------------------------------
262instance TimeUnit TimeSpec where
266instance TimeUnit NanoSecond64 where
267 {-# INLINE toTimeSpec #-}
268 toTimeSpec (NanoSecond64 t) = TimeSpec s ns
269 where (s, ns) = t `divMod` tenPower9
271 {-# INLINE fromTimeSpec #-}
272 fromTimeSpec (TimeSpec s ns) =
273 NanoSecond64 $ s * tenPower9 + ns
275instance TimeUnit64 NanoSecond64 where
276 {-# INLINE toNanoSecond64 #-}
279 {-# INLINE fromNanoSecond64 #-}
280 fromNanoSecond64 = id
282instance TimeUnit MicroSecond64 where
283 {-# INLINE toTimeSpec #-}
284 toTimeSpec (MicroSecond64 t) = TimeSpec s us
285 where (s, us) = t `divMod` tenPower6
287 {-# INLINE fromTimeSpec #-}
288 fromTimeSpec (TimeSpec s us) =
289 MicroSecond64 $ s * tenPower6 + us
291instance TimeUnit64 MicroSecond64 where
292 {-# INLINE toNanoSecond64 #-}
293 toNanoSecond64 (MicroSecond64 us) = NanoSecond64 $ us * tenPower3
295 {-# INLINE fromNanoSecond64 #-}
296 fromNanoSecond64 (NanoSecond64 ns) = MicroSecond64 $ ns `div` tenPower3
298instance TimeUnit MilliSecond64 where
299 {-# INLINE toTimeSpec #-}
300 toTimeSpec (MilliSecond64 t) = TimeSpec s us
301 where (s, us) = t `divMod` tenPower3
303 {-# INLINE fromTimeSpec #-}
304 fromTimeSpec (TimeSpec s us) =
305 MilliSecond64 $ s * tenPower3 + us
307instance TimeUnit64 MilliSecond64 where
308 {-# INLINE toNanoSecond64 #-}
309 toNanoSecond64 (MilliSecond64 us) = NanoSecond64 $ us * tenPower6
311 {-# INLINE fromNanoSecond64 #-}
312 fromNanoSecond64 (NanoSecond64 ns) = MilliSecond64 $ ns `div` tenPower6
314-------------------------------------------------------------------------------
316-------------------------------------------------------------------------------
318-- | Absolute times are relative to a predefined epoch in time. 'AbsTime'
319-- represents times using 'TimeSpec' which can represent times up to ~292
320-- billion years at a nanosecond resolution.
321newtype AbsTime = AbsTime TimeSpec
322 deriving (Eq, Ord, Show)
324-- | Convert a 'TimeUnit' to an absolute time.
325{-# INLINE_NORMAL toAbsTime #-}
326toAbsTime :: TimeUnit a => a -> AbsTime
327toAbsTime = AbsTime . toTimeSpec
329-- | Convert absolute time to a 'TimeUnit'.
330{-# INLINE_NORMAL fromAbsTime #-}
331fromAbsTime :: TimeUnit a => AbsTime -> a
332fromAbsTime (AbsTime t) = fromTimeSpec t
334-- XXX We can also write rewrite rules to simplify divisions multiplications
335-- and additions when manipulating units. Though, that might get simplified at
336-- the assembly (llvm) level as well. Note to/from conversions may be lossy and
337-- therefore this equation may not hold, but that's ok.
338{-# RULES "fromAbsTime/toAbsTime" forall a. toAbsTime (fromAbsTime a) = a #-}
339{-# RULES "toAbsTime/fromAbsTime" forall a. fromAbsTime (toAbsTime a) = a #-}
341-------------------------------------------------------------------------------
342-- Relative time using NaonoSecond64 as the underlying representation
343-------------------------------------------------------------------------------
345-- We use a separate type to represent relative time for safety and speed.
346-- RelTime has a Num instance, absolute time doesn't. Relative times are
347-- usually shorter and for our purposes an Int64 nanoseconds can hold close to
348-- thousand year duration. It is also faster to manipulate. We do not check for
349-- overflows during manipulations so use it only when you know the time cannot
350-- be too big. If you need a bigger RelTime representation then use RelTimeBig.
352-- | Relative times are relative to some arbitrary point of time. Unlike
353-- 'AbsTime' they are not relative to a predefined epoch.
354newtype RelTime64 = RelTime64 NanoSecond64
366-- | Convert a 'TimeUnit' to a relative time.
367{-# INLINE_NORMAL toRelTime64 #-}
368toRelTime64 :: TimeUnit64 a => a -> RelTime64
369toRelTime64 = RelTime64 . toNanoSecond64
371-- | Convert relative time to a 'TimeUnit'.
372{-# INLINE_NORMAL fromRelTime64 #-}
373fromRelTime64 :: TimeUnit64 a => RelTime64 -> a
374fromRelTime64 (RelTime64 t) = fromNanoSecond64 t
376{-# RULES "fromRelTime64/toRelTime64" forall a .
377 toRelTime64 (fromRelTime64 a) = a #-}
379{-# RULES "toRelTime64/fromRelTime64" forall a .
380 fromRelTime64 (toRelTime64 a) = a #-}
382-- | Difference between two absolute points of time.
383{-# INLINE diffAbsTime64 #-}
384diffAbsTime64 :: AbsTime -> AbsTime -> RelTime64
385diffAbsTime64 (AbsTime (TimeSpec s1 ns1)) (AbsTime (TimeSpec s2 ns2)) =
386 RelTime64 $ NanoSecond64 $ ((s1 - s2) * tenPower9) + (ns1 - ns2)
388{-# INLINE addToAbsTime64 #-}
389addToAbsTime64 :: AbsTime -> RelTime64 -> AbsTime
390addToAbsTime64 (AbsTime (TimeSpec s1 ns1)) (RelTime64 (NanoSecond64 ns2)) =
391 AbsTime $ TimeSpec (s1 + s) ns
392 where (s, ns) = (ns1 + ns2) `divMod` tenPower9
394-------------------------------------------------------------------------------
395-- Relative time using TimeSpec as the underlying representation
396-------------------------------------------------------------------------------
398newtype RelTime = RelTime TimeSpec
410{-# INLINE_NORMAL toRelTime #-}
411toRelTime :: TimeUnit a => a -> RelTime
412toRelTime = RelTime . toTimeSpec
414{-# INLINE_NORMAL fromRelTime #-}
415fromRelTime :: TimeUnit a => RelTime -> a
416fromRelTime (RelTime t) = fromTimeSpec t
418{-# RULES "fromRelTime/toRelTime" forall a. toRelTime (fromRelTime a) = a #-}
419{-# RULES "toRelTime/fromRelTime" forall a. fromRelTime (toRelTime a) = a #-}
421-- XXX rename to diffAbsTimes?
422{-# INLINE diffAbsTime #-}
423diffAbsTime :: AbsTime -> AbsTime -> RelTime
424diffAbsTime (AbsTime t1) (AbsTime t2) = RelTime (t1 - t2)
426{-# INLINE addToAbsTime #-}
427addToAbsTime :: AbsTime -> RelTime -> AbsTime
428addToAbsTime (AbsTime t1) (RelTime t2) = AbsTime $ t1 + t2
430-------------------------------------------------------------------------------
431-- Formatting and printing
432-------------------------------------------------------------------------------
434-- | Convert nanoseconds to a string showing time in an appropriate unit.
435showNanoSecond64 :: NanoSecond64 -> String
436showNanoSecond64 time@(NanoSecond64 ns)
437 | time < 0 = '-' : showNanoSecond64 (-time)
438 | ns < 1000 = fromIntegral ns `with` "ns"
439#ifdef mingw32_HOST_OS
440 | ns < 1000000 = (fromIntegral ns / 1000) `with` "us"
442 | ns < 1000000 = (fromIntegral ns / 1000) `with` "μs"
444 | ns < 1000000000 = (fromIntegral ns / 1000000) `with` "ms"
445 | ns < (60 * 1000000000) = (fromIntegral ns / 1000000000) `with` "s"
446 | ns < (60 * 60 * 1000000000) =
447 (fromIntegral ns / (60 * 1000000000)) `with` "min"
448 | ns < (24 * 60 * 60 * 1000000000) =
449 (fromIntegral ns / (60 * 60 * 1000000000)) `with` "hr"
450 | ns < (365 * 24 * 60 * 60 * 1000000000) =
451 (fromIntegral ns / (24 * 60 * 60 * 1000000000)) `with` "days"
453 (fromIntegral ns / (365 * 24 * 60 * 60 * 1000000000)) `with` "years"
454 where with (t :: Double) (u :: String)
455 | t >= 1e9 = printf "%.4g %s" t u
456 | t >= 1e3 = printf "%.0f %s" t u
457 | t >= 1e2 = printf "%.1f %s" t u
458 | t >= 1e1 = printf "%.2f %s" t u
459 | otherwise = printf "%.3f %s" t u
461-- In general we should be able to show the time in a specified unit, if we
462-- omit the unit we can show it in an automatically chosen one.
471showRelTime64 :: RelTime64 -> String
472showRelTime64 = showNanoSecond64 . fromRelTime64
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list