text-time 0.1.0 → 0.2.0
raw patch · 8 files changed
+148/−116 lines, 8 filesdep +formatting
Dependencies added: formatting
Files
- CHANGES.md +6/−2
- README.md +1/−1
- src/Data/Text/Time.hs +4/−2
- src/Data/Text/Time/Format.hs +15/−0
- src/Data/Text/Time/Parse.hs +106/−0
- src/Data/Text/Time/Parsers.hs +0/−107
- test-src/Data/Text/TimeSpec.hs +8/−1
- text-time.cabal +8/−3
CHANGES.md view
@@ -1,3 +1,7 @@-# Version 0.1 (2016-12-???)+# Version 0.2 (2016-12-14) - *+ * ISO formatter++# Version 0.1 (2016-12-13)++ * ISO parser
README.md view
@@ -3,7 +3,7 @@ # Welcome to Text Time library -This library contains fast parser for ISO date from Text into UTCTime data+This library contains fast parser and formatter for iso dates in Text.
src/Data/Text/Time.hs view
@@ -2,10 +2,12 @@ -- | Parser for ISO date from ByteString module Data.Text.Time- ( module Data.Text.Time.Parsers+ ( module Data.Text.Time.Parse+ , module Data.Text.Time.Format ) where -import Data.Text.Time.Parsers+import Data.Text.Time.Parse+import Data.Text.Time.Format
+ src/Data/Text/Time/Format.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE OverloadedStrings #-}++module Data.Text.Time.Format+ ( formatISODateTime+ ) where++import Data.Text.Lazy (Text)+import Formatting (format, (%))+import Formatting.Time (dateDash, hms)+import Data.Time ( UTCTime )+++-- | Format ISO date.+formatISODateTime :: UTCTime -> Text+formatISODateTime utc = format (dateDash % "T" % hms) utc utc
+ src/Data/Text/Time/Parse.hs view
@@ -0,0 +1,106 @@+-- | This code is adapted from sqlite-simple package++module Data.Text.Time.Parse+ ( parseISODateTime+ , parseUTCTimeOrError+ ) where++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+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 #-}
− src/Data/Text/Time/Parsers.hs
@@ -1,107 +0,0 @@--module Data.Text.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/Text/TimeSpec.hs view
@@ -9,7 +9,7 @@ spec :: Spec spec = do - describe "Parsers" $ do+ describe "Parse" $ do it "iso format: YYYY" $ do parseISODateTime "2014" `shouldBe` UTCTime (fromGregorian 2014 1 1) 0@@ -27,3 +27,10 @@ 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+++ describe "Format" $ do++ it "to iso" $ do+ formatISODateTime (UTCTime (fromGregorian 2014 1 1) 13513) `shouldBe` "2014-01-01T03:45:13"+
text-time.cabal view
@@ -1,5 +1,5 @@ name: text-time-version: 0.1.0+version: 0.2.0 synopsis: Library for Time parsing from Text into UTCTime License: BSD3 License-file: LICENSE@@ -9,7 +9,7 @@ LICENSE, benchmark/*.hs description:- Fast parser for ISO date from Text to UTCTime+ Fast parser and formatter for ISO date to and from Text homepage: https://github.com/klangner/text-time author: Krzysztof Langner maintainer: klangner@gmail.com@@ -28,12 +28,14 @@ build-depends: base >= 4.7 && < 5, attoparsec < 0.14,+ formatting < 7, text < 2, time < 2 exposed-modules: Data.Text.Time other-modules:- Data.Text.Time.Parsers+ Data.Text.Time.Parse+ Data.Text.Time.Format test-suite unit-tests type: exitcode-stdio-1.0@@ -45,9 +47,12 @@ QuickCheck >=2.6 && <3, Cabal >=1.16.0 && <2, attoparsec < 0.14,+ formatting < 7, text < 2, time < 2 other-modules:+ Data.Text.Time.Parse+ Data.Text.Time.Format Data.Text.Time, Data.Text.TimeSpec hs-source-dirs: