chronologique (empty) → 0.2.1.0
raw patch · 8 files changed
+580/−0 lines, 8 filesdep +QuickCheckdep +basedep +chronologique
Dependencies added: QuickCheck, base, chronologique, hourglass, hspec, time
Files
- LICENCE +32/−0
- chronologique.cabal +88/−0
- lib/Chrono/Compat.hs +73/−0
- lib/Chrono/Formats.hs +116/−0
- lib/Chrono/TimeStamp.hs +131/−0
- tests/CheckTimeStamp.hs +114/−0
- tests/Experiment.hs +11/−0
- tests/TestSuite.hs +15/−0
+ LICENCE view
@@ -0,0 +1,32 @@+Time to manipulate time++Copyright © 2013-2017 Operational Dynamics Consulting, Pty Ltd and Others+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++ 1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ 2. Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.+ + 3. Neither the name of the project nor the names of its contributors+ may be used to endorse or promote products derived from this + software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ chronologique.cabal view
@@ -0,0 +1,88 @@+cabal-version: >= 1.10+name: chronologique+version: 0.2.1.0+synopsis: Time to manipulate time+description:+ A simple type useful for representing timestamps as generated by system+ events, along with conveniences for converting between time types from common+ Haskell time libraries.+ .+ Our original use was wanting to conveniently measure things happening on+ distributed computer systems. Since machine clock cycles are in units of+ nanoseconds, this has the nice property that, assuming the system clock is not+ corrupted, two subsequent events from the same source process are likely to+ have monotonically increasing timestamps. And even if the system clock has+ skew, they're still decently likely to be unique per device. These TimeStamps+ thus make good keys when building Maps.+ .+ The core type is in "Chrono.TimeStamp", see there for full documentation.++license: BSD3+license-file: LICENCE+author: Andrew Cowie <andrew@operationaldynamics.com>+maintainer: Andrew Cowie <andrew@operationaldynamics.com>+copyright: © 2016-2017 Operational Dynamics Consulting Pty Ltd, and Others+category: Text+tested-with: GHC == 8.0++build-type: Simple++library+ default-language: Haskell2010++ build-depends: base >= 4.9 && < 5,+ time,+ hourglass++ hs-source-dirs: lib++ exposed-modules: Chrono.TimeStamp+ Chrono.Compat+ other-modules: Chrono.Formats++ ghc-options: -Wall+ -Wwarn+ -fwarn-tabs++test-suite experiment+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ buildable: False++ build-depends: base,+ chronologique++ hs-source-dirs: tests++ main-is: Experiment.hs++ ghc-options: -threaded+ -O2+ -Wall+ -Wwarn+ -fwarn-tabs+++test-suite check+ type: exitcode-stdio-1.0+ default-language: Haskell2010++ build-depends: base,+ hspec,+ hourglass,+ QuickCheck,+ chronologique++ hs-source-dirs: tests++ main-is: TestSuite.hs++ other-modules: CheckTimeStamp++ ghc-options: -threaded+ -O2+ -Wall+ -Wwarn+ -fwarn-tabs++-- vim: set tabstop=22 expandtab:
+ lib/Chrono/Compat.hs view
@@ -0,0 +1,73 @@+--+-- Time to manipulate time+--+-- Copyright © 2013-2016 Operational Dynamics Consulting, Pty Ltd and Others+--+-- The code in this file, and the program it is a part of, is+-- made available to you by its authors as open source software:+-- you can redistribute it and/or modify it under the terms of+-- the 3-clause BSD licence.+--++--+-- | Compatibility with time types from other time handling libraries. Some of+-- these are just conveniences, but it's not always obvious how to convert+-- between time types even in the same package.+--+module Chrono.Compat+(+ -- * time package, from base+ convertToPosix+ , convertFromPosix+ , convertToUTC+ , convertFromUTC+ -- * hourglass package+ , convertToHourglass+ , convertFromHourglass+) where++import Data.Int (Int64)+import Data.Hourglass+import Data.Time.Clock+import Data.Time.Clock.POSIX++import Chrono.TimeStamp++--+-- | Utility function to convert nanoseconds since Unix epoch to a+-- 'NominalDiffTime', allowing you to then use the time manipulation+-- functions in "Data.Time.Clock" from __time__.+--+convertToPosix :: TimeStamp -> POSIXTime+convertToPosix = fromRational . (/ 1e9) . fromIntegral++--+--+-- | Utility function to convert nanoseconds since Unix epoch to a+-- 'ElapsedP', allowing you to then use the time manipulation+-- functions in the __hourglass__ package.+--+convertToHourglass :: TimeStamp -> ElapsedP+convertToHourglass = timeGetElapsedP++convertFromHourglass :: ElapsedP -> TimeStamp+convertFromHourglass = timeFromElapsedP+++--+-- | Annoyingly, the various types in __time__ don't interoperate. Quite frequently+-- you need to get to, or from, 'UTCTime'.+--+convertToUTC :: TimeStamp -> UTCTime+convertToUTC = posixSecondsToUTCTime . convertToPosix++convertFromUTC :: UTCTime -> TimeStamp+convertFromUTC = convertFromPosix . utcTimeToPOSIXSeconds++convertFromPosix :: POSIXTime -> TimeStamp+convertFromPosix =+ let+ nano :: POSIXTime -> Int64+ nano = floor . (* 1000000000) . toRational+ in+ TimeStamp . fromIntegral . nano
+ lib/Chrono/Formats.hs view
@@ -0,0 +1,116 @@+--+-- Time to manipulate time+--+-- Copyright © 2013-2016 Operational Dynamics Consulting, Pty Ltd and Others+--+-- The code in this file, and the program it is a part of, is+-- made available to you by its authors as open source software:+-- you can redistribute it and/or modify it under the terms of+-- the 3-clause BSD licence.+--++{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Chrono.Formats+(+ ISO8601_Precise(..)+ , ISO8601_Seconds(..)+ , ISO8601_Days(..)+ , Posix_Precise(..)+ , Posix_Micro(..)+ , Posix_Milli(..)+ , Posix_Seconds(..)+) where++import Data.Hourglass++--+-- | Format string describing full (nanosecond) precision ISO8601 time,+--+-- > 2014-07-31T23:09:35.274387019Z+--+data ISO8601_Precise = ISO8601_Precise++instance TimeFormat ISO8601_Precise where+ toFormat _ = TimeFormatString+ [ Format_Year+ , Format_Text '-'+ , Format_Month2+ , Format_Text '-'+ , Format_Day2+ , Format_Text 'T'+ , Format_Hour+ , Format_Text ':'+ , Format_Minute+ , Format_Text ':'+ , Format_Second+ , Format_Text '.'+ , Format_Precision 9+ , Format_Text 'Z'+ ]++data ISO8601_Seconds = ISO8601_Seconds++instance TimeFormat ISO8601_Seconds where+ toFormat _ = TimeFormatString+ [ Format_Year+ , Format_Text '-'+ , Format_Month2+ , Format_Text '-'+ , Format_Day2+ , Format_Text 'T'+ , Format_Hour+ , Format_Text ':'+ , Format_Minute+ , Format_Text ':'+ , Format_Second+ , Format_Text 'Z'+ ]++data ISO8601_Days = ISO8601_Days++instance TimeFormat ISO8601_Days where+ toFormat _ = TimeFormatString+ [ Format_Year+ , Format_Text '-'+ , Format_Month2+ , Format_Text '-'+ , Format_Day2+ ]++data Posix_Precise = Posix_Precise++instance TimeFormat Posix_Precise where+ toFormat _ = TimeFormatString+ [ Format_UnixSecond+ , Format_Text '.'+ , Format_MilliSecond+ , Format_MicroSecond+ , Format_NanoSecond+ ]++data Posix_Micro = Posix_Micro++instance TimeFormat Posix_Micro where+ toFormat _ = TimeFormatString+ [ Format_UnixSecond+ , Format_Text '.'+ , Format_MilliSecond+ , Format_MicroSecond+ ]++data Posix_Milli = Posix_Milli++instance TimeFormat Posix_Milli where+ toFormat _ = TimeFormatString+ [ Format_UnixSecond+ , Format_Text '.'+ , Format_MilliSecond+ ]++data Posix_Seconds = Posix_Seconds++instance TimeFormat Posix_Seconds where+ toFormat _ = TimeFormatString+ [ Format_UnixSecond+ ]
+ lib/Chrono/TimeStamp.hs view
@@ -0,0 +1,131 @@+--+-- Time to manipulate time+--+-- Copyright © 2013-2016 Operational Dynamics Consulting, Pty Ltd and Others+--+-- The code in this file, and the program it is a part of, is+-- made available to you by its authors as open source software:+-- you can redistribute it and/or modify it under the terms of+-- the 3-clause BSD licence.+--++{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE TupleSections #-}++module Chrono.TimeStamp+(+ TimeStamp(..)+ , getCurrentTimeNanoseconds++ , ISO8601_Precise(..)+) where++import Control.Applicative+import Data.Maybe+import Data.Int (Int64)+import Data.Hourglass+import Time.System++import Chrono.Formats++--+-- | Number of nanoseconds since the Unix epoch.+--+-- The Show instance displays the TimeStamp as seconds with the nanosecond+-- precision expressed as a decimal amount after the interger, ie:+--+-- >>> t <- getCurrentTimeNanoseconds+-- >>> show t+-- 2014-07-31T23:09:35.274387031Z+--+-- However this doesn't change the fact the underlying representation counts+-- nanoseconds since epoch:+--+-- >>> show $ unTimeStamp t+-- 1406848175274387031+--+-- There is a Read instance that is reasonably accommodating.+--+-- >>> read "2014-07-31T13:05:04.942089001Z" :: TimeStamp+-- 2014-07-31T13:05:04.942089001Z+--+-- >>> read "1406811904.942089001" :: TimeStamp+-- 2014-07-31T13:05:04.942089001Z+--+-- >>> read "1406811904" :: TimeStamp+-- 2014-07-31T13:05:04.000000000Z+--+-- In case you're wondering, the valid range of nanoseconds that fits into the+-- underlying Int64 is:+--+-- >>> show $ minBound :: TimeStamp+-- 1677-09-21T00:12:43.145224192Z+--+-- >>> show $ maxBound :: TimeStamp+-- 2262-04-11T23:47:16.854775807Z+--+-- so in a quarter millenium's time, yes, you'll have the Y2262 Problem.+-- Haskell code from today will, of course, still be running, so in the mid+-- Twenty-Third century you will need to replace this implementation with+-- something else.+--+newtype TimeStamp = TimeStamp {+ unTimeStamp :: Int64+} deriving (Eq, Ord, Enum, Num, Real, Integral, Bounded)++{-+ Hourglass works by sending types in and out of the Timeable and Time+ typeclasses. They're not particularly easy to work with, but they're a+ prerequisite for using timePrint+-}++instance Timeable TimeStamp where+ timeGetElapsedP :: TimeStamp -> ElapsedP+ timeGetElapsedP (TimeStamp ticks) =+ let+ (s,ns) = divMod ticks 1000000000+ in+ ElapsedP (Elapsed (Seconds (s))) (NanoSeconds (ns))++instance Time TimeStamp where+ timeFromElapsedP :: ElapsedP -> TimeStamp+ timeFromElapsedP (ElapsedP (Elapsed (Seconds seconds)) (NanoSeconds nanoseconds)) =+ let+ s = fromIntegral seconds :: Int64+ ns = fromIntegral nanoseconds+ in+ TimeStamp $! (s * 1000000000) + ns+++instance Show TimeStamp where+ show t =+ timePrint ISO8601_Precise t++instance Read TimeStamp where+ readsPrec _ s = maybeToList $ (,"") <$> reduceDateTime <$> parse s+ where+ parse :: String -> Maybe DateTime+ parse x = timeParse ISO8601_Precise x+ <|> timeParse ISO8601_Seconds x+ <|> timeParse ISO8601_DateAndTime x -- from hourglass+ <|> timeParse ISO8601_Date x -- from hourglass+ <|> timeParse Posix_Precise x+ <|> timeParse Posix_Micro x+ <|> timeParse Posix_Milli x+ <|> timeParse Posix_Seconds x++reduceDateTime :: DateTime -> TimeStamp+reduceDateTime = timeFromElapsedP . timeGetElapsedP++--+-- | Get the current system time, expressed as a 'TimeStamp' (which is to+-- say, number of nanoseconds since the Unix epoch).+--+getCurrentTimeNanoseconds :: IO TimeStamp+getCurrentTimeNanoseconds = do+ p <- timeCurrentP+ return $! convertToTimeStamp p++convertToTimeStamp :: ElapsedP -> TimeStamp+convertToTimeStamp = timeFromElapsedP
+ tests/CheckTimeStamp.hs view
@@ -0,0 +1,114 @@+--+-- Time to manipulate time+--+-- Copyright © 2013-2016 Operational Dynamics Consulting, Pty Ltd and Others+--+-- The code in this file, and the program it is a part of, is+-- made available to you by its authors as open source software:+-- you can redistribute it and/or modify it under the terms of+-- the 3-clause BSD licence.+--++-- | Test serialisation/deserialiastion for TimeStamp type++{-# LANGUAGE BinaryLiterals #-}+{-# LANGUAGE NegativeLiterals #-}+{-# OPTIONS -fno-warn-orphans #-}++module CheckTimeStamp where++import Test.Hspec+import Test.QuickCheck+import Data.Hourglass++import Chrono.TimeStamp+import Chrono.Compat++checkTimeStamp :: Spec+checkTimeStamp = do+ describe "Smallest valid TimeStamp" $+ let+ t = TimeStamp (-9223372036854775808)+ in do+ it "should be representable" $ do+ t `shouldBe` (minBound :: TimeStamp)++ it "should be match when Shown" $ do+ show t `shouldBe` show (minBound :: TimeStamp)++ it "should equal expected value" $ do+ show t `shouldBe` "1677-09-21T00:12:43.145224192Z"++ describe "Largest valid TimeStamp" $+ let+ t = TimeStamp 9223372036854775807+ in do+ it "should be representable" $ do+ t `shouldBe` (maxBound :: TimeStamp)++ it "should be match when Shown" $ do+ show t `shouldBe` show (maxBound :: TimeStamp)++ it "should equal expected value" $ do+ show t `shouldBe` "2262-04-11T23:47:16.854775807Z"++ describe "Printing and parsing with precise format" $ do+ it "formats a known date correctly" $ do+ timePrint ISO8601_Precise (TimeStamp 1406849015948797001) `shouldBe` "2014-07-31T23:23:35.948797001Z"++ it "uses timeParse effectively" $ do+ timeParse ISO8601_Precise "2014-07-31T23:42:35.948797001Z" `shouldBe`+ Just (DateTime (Date 2014 July 31) (TimeOfDay 23 42 35 948797001))++ describe "Round trip through Read and Show instances" $ do+ it "outputs a correctly formated ISO 8601 timestamp when Shown" $ do+ show (TimeStamp 1406849015948797001) `shouldBe` "2014-07-31T23:23:35.948797001Z"+ show (TimeStamp 1406849015948797001) `shouldBe` "2014-07-31T23:23:35.948797001Z"+ show (TimeStamp 0) `shouldBe` "1970-01-01T00:00:00.000000000Z"++ it "Reads ISO 8601 timestamps" $ do+ read "2014-07-31T23:23:35.948797001Z" `shouldBe` TimeStamp 1406849015948797001+ read "2014-07-31T23:23:35Z" `shouldBe` TimeStamp 1406849015000000000+ read "2014-07-31" `shouldBe` TimeStamp 1406764800000000000++ it "reads the Unix epoch date" $+ read "1970-01-01" `shouldBe` TimeStamp 0++ it "permissively reads various formats" $ do+ show (read "1970-01-01T00:00:00.000000000Z" :: TimeStamp) `shouldBe` "1970-01-01T00:00:00.000000000Z"+ show (read "1970-01-01" :: TimeStamp) `shouldBe` "1970-01-01T00:00:00.000000000Z"+ show (read "0" :: TimeStamp) `shouldBe` "1970-01-01T00:00:00.000000000Z"++ it "permissively reads Posix seconds since epoch" $ do+ show (read "1406849015.948797001" :: TimeStamp) `shouldBe` "2014-07-31T23:23:35.948797001Z"+ show (read "1406849015.948797" :: TimeStamp) `shouldBe` "2014-07-31T23:23:35.948797000Z"+ show (read "1406849015.948" :: TimeStamp) `shouldBe` "2014-07-31T23:23:35.948000000Z"+ show (read "1406849015" :: TimeStamp) `shouldBe` "2014-07-31T23:23:35.000000000Z"+{-+ This is a bit fragile, depending as it does on the serialization to String+ in the Show instance of UTCTime. Not that they're going to change it+ anytime soon.+-}++ describe "Round trip through base time types" $ do+ it "converts to POSIXTime and back again" $ do+ let t = TimeStamp 1406849015948797001+ convertFromPosix (convertToPosix t) `shouldBe` t+ show (convertToPosix t) `shouldBe` "1406849015.948797001s"++ it "converts to UTCTime and back again" $ do+ let t = TimeStamp 1406849015948797001+ convertFromUTC (convertToUTC t) `shouldBe` t+ show (convertToUTC t) `shouldBe` "2014-07-31 23:23:35.948797001 UTC"++ it "behaves when QuickChecked" $ do+ property prop_RoundTrip+++instance Arbitrary TimeStamp where+ arbitrary = do+ tick <- arbitrary+ return (TimeStamp tick)++prop_RoundTrip :: TimeStamp -> Bool+prop_RoundTrip t = (read . show) t == t
+ tests/Experiment.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS -fno-warn-unused-imports #-}++module Main where++import Chrono.TimeStamp++main :: IO ()+main = do+ putStrLn "Hello"+
+ tests/TestSuite.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Test.Hspec++import CheckTimeStamp++main :: IO ()+main = hspec suite++suite :: Spec+suite = do+ checkTimeStamp+