on master help sign in
Parse HH:MM time in zonedTime and utcTime

Changes: - Make parsing the seconds optional in `timeOfDay` - Adjust documentation indicating the allowed format - Add corresponding tests Motivation for change: - HH:MM is a valid time format in ISO 8601 - Format without seconds is given by some JSON APIs on the Internet.

commita21760b9aa005254244599ec478beb86621c589e authorTim Bodeit <tim@bodeit.com> dateTue 13 Oct 2015, 19:19 (11 years ago) parent4667ef1
2 files changed+22−7
Data/Aeson/Parser/Time.hs+9−7 tests/UnitTests.hs+13
Data/Aeson/Parser/Time.hs+9−7
@@ -62,12 +62,12 @@ twoDigits = do
6262 let c2d c = ord c .&. 15
6363 return $! c2d a * 10 + c2d b
6464
65−-- | Parse a time of the form @HH:MM:SS[.SSS]@.
65+-- | Parse a time of the form @HH:MM[:SS[.SSS]]@.
6666timeOfDay :: Parser Local.TimeOfDay
6767timeOfDay = do
68− h <- twoDigits <* char ':'
69− m <- twoDigits <* char ':'
70− s <- seconds
68+ h <- twoDigits
69+ m <- char ':' *> twoDigits
70+ s <- option 0 (char ':' *> seconds)
7171 if h < 24 && m < 60 && s < 61
7272 then return (Local.TimeOfDay h m s)
7373 else fail "invalid time"
@@ -120,9 +120,9 @@ timeZone = do
120120 let !tz = Local.minutesToTimeZone off
121121 in return (Just tz)
122122
123−-- | Parse a date and time, of the form @YYYY-MM-DD HH:MM:SS@.
124−-- The space may be replaced with a @T@. The number of seconds may be
125−-- followed by a fractional component.
123+-- | Parse a date and time, of the form @YYYY-MM-DD HH:MM[:SS[.SSS]]@.
124+-- The space may be replaced with a @T@. The number of seconds is optional
125+-- and may be followed by a fractional component.
126126localTime :: Parser Local.LocalTime
127127localTime = Local.LocalTime <$> day <* daySep <*> timeOfDay
128128 where daySep = satisfy (\c -> c == 'T' || c == ' ')
@@ -140,7 +140,9 @@ utcTime = do
140140
141141-- | Parse a date with time zone info. Acceptable formats:
142142--
143+-- @YYYY-MM-DD HH:MM Z@
143144-- @YYYY-MM-DD HH:MM:SS Z@
145+-- @YYYY-MM-DD HH:MM:SS.SSS Z@
144146--
145147-- The first space may instead be a @T@, and the second space is
146148-- optional. The @Z@ represents UTC. The @Z@ may be replaced with a
tests/UnitTests.hs+13
@@ -123,6 +123,18 @@ utcTimeGood = do
123123 -- ts8 wraps around to the next day in UTC
124124 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-02T01:00:00Z") t8
125125 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-02T02:30:00Z") t9
126+
127+ -- Seconds in Time can be omitted
128+ let ts10 = "2015-01-03T12:13Z" :: LT.Text
129+ let ts11 = "2015-01-03 12:13Z" :: LT.Text
130+ let ts12 = "2015-01-01T12:30-02" :: LT.Text
131+ let (Just (t10 :: UTCTime)) = parseWithAeson ts10
132+ let (Just (t11 :: UTCTime)) = parseWithAeson ts11
133+ let (Just (t12 :: UTCTime)) = parseWithAeson ts12
134+ assertEqual "utctime" (parseWithRead "%FT%H:%MZ" ts10) t10
135+ assertEqual "utctime" (parseWithRead "%F %H:%MZ" ts11) t11
136+ assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-01T14:30:00Z") t12
137+
126138 where
127139 parseWithRead :: String -> LT.Text -> UTCTime
128140 parseWithRead f s =
@@ -143,6 +155,7 @@ utcTimeBad = do
143155 verifyFailParse "2015-01-01T12:30:00.00+00Z" -- no Zulu if offset given
144156 verifyFailParse "2015-01-01T12:30:00.00+00:00Z" -- no Zulu if offset given
145157 verifyFailParse "2015-01-03 12:13:00.Z" -- decimal at the end but no digits
158+ verifyFailParse "2015-01-03 12:13.000Z" -- decimal at the end, but no seconds
146159 where
147160 verifyFailParse (s :: LT.Text) =
148161 let (dec :: Maybe UTCTime) = decode . LT.encodeUtf8 $ (LT.concat ["\"", s, "\""]) in