1{-# LANGUAGE TypeInType #-}
2{-# LANGUAGE UnboxedTuples #-}
5-- Module : Streamly.Internal.Data.Time.Units
6-- Copyright : (c) 2019 Composewell Technologies
9-- Maintainer : streamly@composewell.com
10-- Stability : pre-release
13module Streamly.Internal.Data.Time.Units
15 -- * Time Unit Conversions
27 -- * Absolute times (using TimeSpec)
32 -- * Relative times (using TimeSpec)
39 -- * Relative times (using NanoSecond64)
51import Text.Printf (printf)
54import Foreign.Storable (Storable)
55import Streamly.Internal.Data.Unboxed (Unbox)
56import Streamly.Internal.Data.Time.TimeSpec
58-------------------------------------------------------------------------------
60-------------------------------------------------------------------------------
62{-# INLINE tenPower3 #-}
66{-# INLINE tenPower6 #-}
70{-# INLINE tenPower9 #-}
75-------------------------------------------------------------------------------
76-- Time Unit Representations
77-------------------------------------------------------------------------------
79-- XXX We should be able to use type families to use different represenations
89-- Double or Fixed would be a much better representation so that we do not lose
90-- information between conversions. However, for faster arithmetic operations
91-- we use an 'Int64' here. When we need convservation of values we can use a
92-- different system of units with a Fixed precision.
94-------------------------------------------------------------------------------
96-------------------------------------------------------------------------------
98-- | An 'Int64' time representation with a nanosecond resolution. It can
99-- represent time up to ~292 years.
100newtype NanoSecond64 = NanoSecond64 Int64
114-- | An 'Int64' time representation with a microsecond resolution.
115-- It can represent time up to ~292,000 years.
116newtype MicroSecond64 = MicroSecond64 Int64
130-- | An 'Int64' time representation with a millisecond resolution.
131-- It can represent time up to ~292 million years.
132newtype MilliSecond64 = MilliSecond64 Int64
146-------------------------------------------------------------------------------
148-------------------------------------------------------------------------------
150-------------------------------------------------------------------------------
151-- Time unit conversions
152-------------------------------------------------------------------------------
154-- TODO: compare whether using TimeSpec instead of Integer provides significant
155-- performance boost. If not then we can just use Integer nanoseconds and get
156-- rid of TimeUnitWide.
158-- | A type class for converting between time units using 'Integer' as the
159-- intermediate and the widest representation with a nanosecond resolution.
160-- This system of units can represent arbitrarily large times but provides
161-- least efficient arithmetic operations due to 'Integer' arithmetic.
163-- NOTE: Converting to and from units may truncate the value depending on the
164-- original value and the size and resolution of the destination unit.
166class TimeUnitWide a where
167 toTimeInteger :: a -> Integer
168 fromTimeInteger :: Integer -> a
171-- | A type class for converting between units of time using 'TimeSpec' as the
172-- intermediate representation. This system of units can represent up to ~292
173-- billion years at nanosecond resolution with reasonably efficient arithmetic
176-- NOTE: Converting to and from units may truncate the value depending on the
177-- original value and the size and resolution of the destination unit.
178class TimeUnit a where
179 toTimeSpec :: a -> TimeSpec
180 fromTimeSpec :: TimeSpec -> a
182-- XXX we can use a fromNanoSecond64 for conversion with overflow check and
183-- fromNanoSecond64Unsafe for conversion without overflow check.
185-- | A type class for converting between units of time using 'Int64' as the
186-- intermediate representation with a nanosecond resolution. This system of
187-- units can represent up to ~292 years at nanosecond resolution with fast
188-- arithmetic operations.
190-- NOTE: Converting to and from units may truncate the value depending on the
191-- original value and the size and resolution of the destination unit.
192class TimeUnit64 a where
193 toNanoSecond64 :: a -> NanoSecond64
194 fromNanoSecond64 :: NanoSecond64 -> a
196-------------------------------------------------------------------------------
198-------------------------------------------------------------------------------
200instance TimeUnit TimeSpec where
204instance TimeUnit NanoSecond64 where
205 {-# INLINE toTimeSpec #-}
206 toTimeSpec (NanoSecond64 t) = TimeSpec s ns
207 where (s, ns) = t `divMod` tenPower9
209 {-# INLINE fromTimeSpec #-}
210 fromTimeSpec (TimeSpec s ns) =
211 NanoSecond64 $ s * tenPower9 + ns
213instance TimeUnit64 NanoSecond64 where
214 {-# INLINE toNanoSecond64 #-}
217 {-# INLINE fromNanoSecond64 #-}
218 fromNanoSecond64 = id
220instance TimeUnit MicroSecond64 where
221 {-# INLINE toTimeSpec #-}
222 toTimeSpec (MicroSecond64 t) = TimeSpec s (us * tenPower3)
223 where (s, us) = t `divMod` tenPower6
225 {-# INLINE fromTimeSpec #-}
226 fromTimeSpec (TimeSpec s ns) =
227 -- XXX round ns to nearest microsecond?
228 MicroSecond64 $ s * tenPower6 + (ns `div` tenPower3)
230instance TimeUnit64 MicroSecond64 where
231 {-# INLINE toNanoSecond64 #-}
232 toNanoSecond64 (MicroSecond64 us) = NanoSecond64 $ us * tenPower3
234 {-# INLINE fromNanoSecond64 #-}
235 -- XXX round ns to nearest microsecond?
236 fromNanoSecond64 (NanoSecond64 ns) = MicroSecond64 $ ns `div` tenPower3
238instance TimeUnit MilliSecond64 where
239 {-# INLINE toTimeSpec #-}
240 toTimeSpec (MilliSecond64 t) = TimeSpec s (ms * tenPower6)
241 where (s, ms) = t `divMod` tenPower3
243 {-# INLINE fromTimeSpec #-}
244 fromTimeSpec (TimeSpec s ns) =
245 -- XXX round ns to nearest millisecond?
246 MilliSecond64 $ s * tenPower3 + (ns `div` tenPower6)
248instance TimeUnit64 MilliSecond64 where
249 {-# INLINE toNanoSecond64 #-}
250 toNanoSecond64 (MilliSecond64 ms) = NanoSecond64 $ ms * tenPower6
252 {-# INLINE fromNanoSecond64 #-}
253 -- XXX round ns to nearest millisecond?
254 fromNanoSecond64 (NanoSecond64 ns) = MilliSecond64 $ ns `div` tenPower6
256-------------------------------------------------------------------------------
258-------------------------------------------------------------------------------
260-- | Absolute times are relative to a predefined epoch in time. 'AbsTime'
261-- represents times using 'TimeSpec' which can represent times up to ~292
262-- billion years at a nanosecond resolution.
263newtype AbsTime = AbsTime TimeSpec
264 deriving (Eq, Ord, Show)
266-- | Convert a 'TimeUnit' to an absolute time.
267{-# INLINE_NORMAL toAbsTime #-}
268toAbsTime :: TimeUnit a => a -> AbsTime
269toAbsTime = AbsTime . toTimeSpec
271-- | Convert absolute time to a 'TimeUnit'.
272{-# INLINE_NORMAL fromAbsTime #-}
273fromAbsTime :: TimeUnit a => AbsTime -> a
274fromAbsTime (AbsTime t) = fromTimeSpec t
276-- XXX We can also write rewrite rules to simplify divisions multiplications
277-- and additions when manipulating units. Though, that might get simplified at
278-- the assembly (llvm) level as well. Note to/from conversions may be lossy and
279-- therefore this equation may not hold, but that's ok.
280{-# RULES "fromAbsTime/toAbsTime" forall a. toAbsTime (fromAbsTime a) = a #-}
281{-# RULES "toAbsTime/fromAbsTime" forall a. fromAbsTime (toAbsTime a) = a #-}
283-------------------------------------------------------------------------------
284-- Relative time using NaonoSecond64 as the underlying representation
285-------------------------------------------------------------------------------
287-- We use a separate type to represent relative time for safety and speed.
288-- RelTime has a Num instance, absolute time doesn't. Relative times are
289-- usually shorter and for our purposes an Int64 nanoseconds can hold close to
290-- thousand year duration. It is also faster to manipulate. We do not check for
291-- overflows during manipulations so use it only when you know the time cannot
292-- be too big. If you need a bigger RelTime representation then use RelTimeBig.
294-- | Relative times are relative to some arbitrary point of time. Unlike
295-- 'AbsTime' they are not relative to a predefined epoch.
296newtype RelTime64 = RelTime64 NanoSecond64
308-- | Convert a 'TimeUnit' to a relative time.
309{-# INLINE_NORMAL toRelTime64 #-}
310toRelTime64 :: TimeUnit64 a => a -> RelTime64
311toRelTime64 = RelTime64 . toNanoSecond64
313-- | Convert relative time to a 'TimeUnit'.
314{-# INLINE_NORMAL fromRelTime64 #-}
315fromRelTime64 :: TimeUnit64 a => RelTime64 -> a
316fromRelTime64 (RelTime64 t) = fromNanoSecond64 t
318{-# RULES "fromRelTime64/toRelTime64" forall a .
319 toRelTime64 (fromRelTime64 a) = a #-}
321{-# RULES "toRelTime64/fromRelTime64" forall a .
322 fromRelTime64 (toRelTime64 a) = a #-}
324-- | Difference between two absolute points of time.
325{-# INLINE diffAbsTime64 #-}
326diffAbsTime64 :: AbsTime -> AbsTime -> RelTime64
327diffAbsTime64 (AbsTime (TimeSpec s1 ns1)) (AbsTime (TimeSpec s2 ns2)) =
328 RelTime64 $ NanoSecond64 $ ((s1 - s2) * tenPower9) + (ns1 - ns2)
330{-# INLINE addToAbsTime64 #-}
331addToAbsTime64 :: AbsTime -> RelTime64 -> AbsTime
332addToAbsTime64 (AbsTime (TimeSpec s1 ns1)) (RelTime64 (NanoSecond64 ns2)) =
333 AbsTime $ TimeSpec (s1 + s) ns
334 where (s, ns) = (ns1 + ns2) `divMod` tenPower9
336-------------------------------------------------------------------------------
337-- Relative time using TimeSpec as the underlying representation
338-------------------------------------------------------------------------------
340newtype RelTime = RelTime TimeSpec
352{-# INLINE_NORMAL toRelTime #-}
353toRelTime :: TimeUnit a => a -> RelTime
354toRelTime = RelTime . toTimeSpec
356{-# INLINE_NORMAL fromRelTime #-}
357fromRelTime :: TimeUnit a => RelTime -> a
358fromRelTime (RelTime t) = fromTimeSpec t
360{-# RULES "fromRelTime/toRelTime" forall a. toRelTime (fromRelTime a) = a #-}
361{-# RULES "toRelTime/fromRelTime" forall a. fromRelTime (toRelTime a) = a #-}
363-- XXX rename to diffAbsTimes?
364{-# INLINE diffAbsTime #-}
365diffAbsTime :: AbsTime -> AbsTime -> RelTime
366diffAbsTime (AbsTime t1) (AbsTime t2) = RelTime (t1 - t2)
368{-# INLINE addToAbsTime #-}
369addToAbsTime :: AbsTime -> RelTime -> AbsTime
370addToAbsTime (AbsTime t1) (RelTime t2) = AbsTime $ t1 + t2
372-------------------------------------------------------------------------------
373-- Formatting and printing
374-------------------------------------------------------------------------------
376-- | Convert nanoseconds to a string showing time in an appropriate unit.
377showNanoSecond64 :: NanoSecond64 -> String
378showNanoSecond64 time@(NanoSecond64 ns)
379 | time < 0 = '-' : showNanoSecond64 (-time)
380 | ns < 1000 = fromIntegral ns `with` "ns"
381#ifdef mingw32_HOST_OS
382 | ns < 1000000 = (fromIntegral ns / 1000) `with` "us"
384 | ns < 1000000 = (fromIntegral ns / 1000) `with` "μs"
386 | ns < 1000000000 = (fromIntegral ns / 1000000) `with` "ms"
387 | ns < (60 * 1000000000) = (fromIntegral ns / 1000000000) `with` "s"
388 | ns < (60 * 60 * 1000000000) =
389 (fromIntegral ns / (60 * 1000000000)) `with` "min"
390 | ns < (24 * 60 * 60 * 1000000000) =
391 (fromIntegral ns / (60 * 60 * 1000000000)) `with` "hr"
392 | ns < (365 * 24 * 60 * 60 * 1000000000) =
393 (fromIntegral ns / (24 * 60 * 60 * 1000000000)) `with` "days"
395 (fromIntegral ns / (365 * 24 * 60 * 60 * 1000000000)) `with` "years"
396 where with (t :: Double) (u :: String)
397 | t >= 1e9 = printf "%.4g %s" t u
398 | t >= 1e3 = printf "%.0f %s" t u
399 | t >= 1e2 = printf "%.1f %s" t u
400 | t >= 1e1 = printf "%.2f %s" t u
401 | otherwise = printf "%.3f %s" t u
403-- In general we should be able to show the time in a specified unit, if we
404-- omit the unit we can show it in an automatically chosen one.
413showRelTime64 :: RelTime64 -> String
414showRelTime64 = 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