bytestring-time (empty) → 0.1.0
raw patch · 8 files changed
+240/−0 lines, 8 filesdep +Cabaldep +QuickCheckdep +attoparsecsetup-changed
Dependencies added: Cabal, QuickCheck, attoparsec, base, bytestring, hspec, text, time
Files
- CHANGES.md +3/−0
- LICENSE +27/−0
- Setup.hs +2/−0
- bytestring-time.cabal +54/−0
- src/Data/ByteString/Time.hs +11/−0
- src/Data/ByteString/Time/Parsers.hs +107/−0
- test-src/Data/ByteString/TimeSpec.hs +29/−0
- test-src/Spec.hs +7/−0
+ CHANGES.md view
@@ -0,0 +1,3 @@+# Version 0.1 (2016-12-???)++ *
+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) 2014 Krzysztof Langner+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * 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.++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.+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ bytestring-time.cabal view
@@ -0,0 +1,54 @@+name: bytestring-time+version: 0.1.0+synopsis: Library for Time parsing from ByteString+License: BSD3+License-file: LICENSE+Extra-Source-Files:+ CHANGES.md+description:+ Parser for ISO date from ByteString+homepage: https://github.com/klangner/bytestring-time+author: Krzysztof Langner+maintainer: klangner@gmail.com+category: Data+build-type: Simple+cabal-version: >=1.10++source-repository head+ type: git+ location: https://github.com/klangner/bytestring-time++library+ hs-source-dirs: src+ default-language: Haskell2010+ ghc-options: -Wall+ build-depends:+ base >= 4.7 && < 5,+ attoparsec < 0.14,+ bytestring < 0.11,+ text < 2,+ time < 2+ exposed-modules:+ Data.ByteString.Time+ other-modules:+ Data.ByteString.Time.Parsers++test-suite unit-tests+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ default-language: Haskell2010+ build-depends:+ base >= 4 && <5,+ hspec >=2 && <3,+ QuickCheck >=2.6 && <3,+ Cabal >=1.16.0 && <2,+ attoparsec < 0.14,+ bytestring < 0.11,+ text < 2,+ time < 2+ other-modules:+ Data.ByteString.Time,+ Data.ByteString.TimeSpec+ hs-source-dirs:+ src,+ test-src
+ src/Data/ByteString/Time.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Parser for ISO date from ByteString+module Data.ByteString.Time+ ( module Data.ByteString.Time.Parsers+ ) where+++import Data.ByteString.Time.Parsers++
+ src/Data/ByteString/Time/Parsers.hs view
@@ -0,0 +1,107 @@++module Data.ByteString.Time.Parsers+ ( parseISODateTime+ , parseUTCTimeOrError+ ) where++-- import Prelude+import Control.Applicative+import Control.Monad (when)+import qualified Data.Attoparsec.Text as A+import Data.Bits ((.&.))+import Data.Char (isDigit, ord)+import qualified Data.Text as T+import Data.Time ( Day+ , TimeOfDay+ , UTCTime(..)+ , fromGregorian+ , fromGregorianValid+ , makeTimeOfDayValid+ , midnight+ , timeOfDayToTime+ )+++-- | Parse ISO date. If date can't be parsed then this function will return default value instead of error+-- Adapted from sqlite-simple package+parseISODateTime :: T.Text -> UTCTime+parseISODateTime str =+ case parseUTCTimeOrError str of+ Left _ -> defaultUTCTime+ Right dt -> dt+++-- | Default time if date can't be parsed+defaultUTCTime :: UTCTime+defaultUTCTime = UTCTime (fromGregorian 1 1 1) 0+++parseUTCTimeOrError :: T.Text -> Either String UTCTime+parseUTCTimeOrError = A.parseOnly getUTCTime++-- | Create UTCTime parser for ISO date time+-- This code is based on code from sqlite-simple package+--+getUTCTime :: A.Parser UTCTime+getUTCTime = do+ day <- getDay+ time <- ((A.char ' ' <|> A.char 'T') *> getTimeOfDay) <|> pure midnight+ let time' = timeOfDayToTime time+ return (UTCTime day time')+++-- | Date parser+getDay :: A.Parser Day+getDay = do+ yearStr <- A.takeWhile isDigit+ when (T.length yearStr < 4) (fail "year must consist of at least 4 digits")++ let year = toNum yearStr+ month <- (A.char '-' *> digits "month") <|> pure 1+ day <- (A.char '-' *> digits "day") <|> pure 1+ case fromGregorianValid year month day of+ Nothing -> fail "invalid date"+ Just x -> return $! x+++getTimeOfDay :: A.Parser TimeOfDay+getTimeOfDay = do+ hour <- digits "hours"+ _ <- A.char ':'+ minute <- digits "minutes"+ -- Allow omission of seconds. If seconds is omitted, don't try to+ -- parse the sub-second part.+ (sec,subsec)+ <- ((,) <$> (A.char ':' *> digits "seconds") <*> fract) <|> pure (0,0)++ let picos' = sec + subsec++ case makeTimeOfDayValid hour minute picos' of+ Nothing -> fail "invalid time of day"+ Just x -> return $! x++ where+ fract =+ (A.char '.' *> (decimal <$> A.takeWhile1 isDigit)) <|> pure 0+++toNum :: Num n => T.Text -> n+toNum = T.foldl' (\a c -> 10*a + digit c) 0+{-# INLINE toNum #-}++digit :: Num n => Char -> n+digit c = fromIntegral (ord c .&. 0x0f)+{-# INLINE digit #-}++digits :: Num n => String -> A.Parser n+digits msg = do+ x <- A.anyChar+ y <- A.anyChar+ if isDigit x && isDigit y+ then return $! (10 * digit x + digit y)+ else fail (msg ++ " is not 2 digits")+{-# INLINE digits #-}++decimal :: Fractional a => T.Text -> a+decimal str = toNum str / 10^(T.length str)+{-# INLINE decimal #-}
+ test-src/Data/ByteString/TimeSpec.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE OverloadedStrings #-}+module Data.ByteString.TimeSpec (spec) where++import Data.ByteString.Time+import Test.Hspec+import Data.Time+++spec :: Spec+spec = do++ describe "Parsers" $ do++ it "iso format: YYYY" $ do+ parseISODateTime "2014" `shouldBe` UTCTime (fromGregorian 2014 1 1) 0++ it "iso format: YYYY-MM" $ do+ parseISODateTime "2014-04" `shouldBe` UTCTime (fromGregorian 2014 4 1) 0++ it "iso format: YYYY-MM-DD" $ do+ parseISODateTime "2014-04-23" `shouldBe` UTCTime (fromGregorian 2014 4 23) 0++ it "iso format: YYYY-MM-DDTHH:MM:SS" $ do+ let t = timeOfDayToTime (TimeOfDay 12 34 56)+ parseISODateTime "2014-04-23T12:34:56" `shouldBe` UTCTime (fromGregorian 2014 4 23) t++ it "iso format: YYYY-MM-DD HH:MM:SS" $ do+ let t = timeOfDayToTime (TimeOfDay 12 34 56)+ parseISODateTime "2014-04-23 12:34:56" `shouldBe` UTCTime (fromGregorian 2014 4 23) t
+ test-src/Spec.hs view
@@ -0,0 +1,7 @@+import Test.Hspec+import qualified Data.ByteString.TimeSpec+++main :: IO ()+main = hspec $ do+ describe "Time" Data.ByteString.TimeSpec.spec