@@ -1,20 +1,32 @@
1+{-# LANGUAGE CPP #-}
2+
13module Data.Aeson.Parser.Time
24 (
35 day
46 , timeOfDay
57 , timeZone
68 , utcTime
9+ , zonedTime
710 ) where
811
9−import Control.Applicative ((<$>))
12+import Control.Applicative ((<$>), (<|>), empty)
1013import Control.Monad (when, void)
14+import qualified Data.Aeson.Types.Internal as Aeson
1115import Data.Attoparsec.Text as A
16+import Data.Text (Text)
1217import Data.Time.Calendar (Day, fromGregorianValid)
1318import Data.Time.Clock (UTCTime)
19+import Data.Time.Format (parseTime)
1420import qualified Data.Time.LocalTime as Local
1521import qualified Data.Text as T
1622import qualified Data.Text.Read as T
1723
24+#if MIN_VERSION_time(1,5,0)
25+import Data.Time.Format (defaultTimeLocale, dateTimeFmt)
26+#else
27+import System.Locale (defaultTimeLocale, dateTimeFmt)
28+#endif
29+
1830day :: A.Parser Day
1931day = do
2032 y <- decimal <* char '-'
@@ -54,3 +66,34 @@ utcTime = do
5466 lt <- Local.LocalTime <$> day <* daySep <*> timeOfDay
5567 tz <- timeZone
5668 return (Local.localTimeToUTC tz lt)
69+
70+zonedTime :: Text -> Aeson.Parser Local.ZonedTime
71+zonedTime t =
72+ tryFormats alternateFormats
73+ <|> fail "could not parse ECMA-262 ISO-8601 date"
74+ where
75+ tryFormat f =
76+ case parseTime defaultTimeLocale f (T.unpack t) of
77+ Just d -> pure d
78+ Nothing -> empty
79+ tryFormats = foldr1 (<|>) . map tryFormat
80+ alternateFormats =
81+ "%FT%T%QZ" : -- (javascript new Date().toISOString())
82+ "%F %T%Q%z" : -- (postgres)
83+ "%F %T%Q %Z" : -- (time's Show format)
84+ "%FT%T%Q%z" :
85+ "%Y-%mT%T%Q" :
86+ "%Y-%mT%R" :
87+ "%Y-%mT%T" :
88+ "%Y-%mT%T%QZ" :
89+ "%Y-%mT%T%Q%z" :
90+ "%YT%T%Q" :
91+ "%YT%R" :
92+ "%YT%T" :
93+ "%YT%T%QZ" :
94+ "%YT%T%Q%z" :
95+ "%FT%T%Q" :
96+ "%FT%R" :
97+ "%FT%T" :
98+ dateTimeFmt defaultTimeLocale :
99+ []