on master help sign in
Merge pull request #270 from lpsmith/utcoffset-fixes

Utc offset fixes

commit02a104a6079468de067baffc4da53fb8aa5ab3ee authorBryan O'Sullivan <bos@serpentine.com> dateTue 21 Jul 2015, 00:00 (11 years ago) parentefb569d6b8acde
2 files changed+21−14
Data/Aeson/Parser/Time.hs+15−12 tests/UnitTests.hs+6−2
Data/Aeson/Parser/Time.hs+15−12
@@ -98,17 +98,22 @@ timeZone = do
9898 if ch == 'Z'
9999 then return Nothing
100100 else do
101− h0 <- twoDigits
102− m <- maybeSkip ':' *> twoDigits
103− let h | ch == '-' = negate h0
104− | otherwise = h0
105− case h * 60 + m of
106− off | off == 0 ->
101+ h <- twoDigits
102+ mc <- peekChar
103+ m <- case mc of
104+ Just c | c == ':' -> anyChar *> twoDigits
105+ | isDigit c -> twoDigits
106+ _ -> return 0
107+ case () of
108+ _ | h == 0 && m == 0 ->
107109 return Nothing
108− | off < -720 || off > 840 || m > 59 ->
110+ | h > 23 || m > 59 ->
109111 fail "invalid time zone offset"
110− | otherwise ->
111− let !tz = Local.minutesToTimeZone off
112+ | otherwise ->
113+ let !absset = h * 60 + m
114+ !offset | ch == '-' = negate absset
115+ | otherwise = absset
116+ !tz = Local.minutesToTimeZone offset
112117 in return (Just tz)
113118
114119-- | Parse a date and time, of the form @YYYY-MM-DD HH:MM:SS@.
@@ -135,9 +140,7 @@ utcTime = do
135140--
136141-- The first space may instead be a @T@, and the second space is
137142-- optional. The @Z@ represents UTC. The @Z@ may be replaced with a
138−-- time zone offset of the form @+0000@ or @-08:00@, where the first
139−-- two digits are hours, the @:@ is optional and the second two digits
140−-- are minutes.
143+-- time zone offset of the form @±HH@ or @±HH:MM@.
141144zonedTime :: Parser Local.ZonedTime
142145zonedTime = Local.ZonedTime <$> localTime <*> (fromMaybe utc <$> timeZone)
143146
tests/UnitTests.hs+6−2
@@ -102,20 +102,23 @@ utcTimeGood = do
102102 assertEqual "utctime" (parseWithRead "%FT%T%QZ" ts2) t2
103103 assertEqual "utctime" (parseWithRead "%F %T%QZ" ts3) t3
104104 assertEqual "utctime" (parseWithRead "%F %T%QZ" ts4) t4
105− -- timezones. Both +HHMM and +HH:MM are allowed for timezone offset
106− let ts5 = "2015-01-01T12:30:00.00+0000" :: LT.Text
105+ -- offsets. ±HH, ±HHMM, and ±HH:MM are allowed for utc offset
106+ let ts5 = "2015-01-01T12:30:00.00+00" :: LT.Text
107107 let ts6 = "2015-01-01T12:30:00.00+01:15" :: LT.Text
108108 let ts7 = "2015-01-01T12:30:00.00-0200" :: LT.Text
109109 let ts8 = "2015-01-01T22:00:00.00-03:00" :: LT.Text
110+ let ts9 = "2015-01-01T22:00:00.00-04:30" :: LT.Text
110111 let (Just (t5 :: UTCTime)) = parseWithAeson ts5
111112 let (Just (t6 :: UTCTime)) = parseWithAeson ts6
112113 let (Just (t7 :: UTCTime)) = parseWithAeson ts7
113114 let (Just (t8 :: UTCTime)) = parseWithAeson ts8
115+ let (Just (t9 :: UTCTime)) = parseWithAeson ts9
114116 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-01T12:30:00.00Z") t5
115117 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-01T11:15:00.00Z") t6
116118 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-01T14:30:00Z") t7
117119 -- ts8 wraps around to the next day in UTC
118120 assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-02T01:00:00Z") t8
121+ assertEqual "utctime" (parseWithRead "%FT%T%QZ" "2015-01-02T02:30:00Z") t9
119122 where
120123 parseWithRead :: String -> LT.Text -> UTCTime
121124 parseWithRead f s =
@@ -136,6 +139,7 @@ utcTimeBad = do
136139 verifyFailParse "2015-01-01T12:30:00.00+00Z" -- no Zulu if offset given
137140 verifyFailParse "2015-01-01T12:30:00.00+00:00Z" -- no Zulu if offset given
138141 verifyFailParse "2015-01-03 12:13:00.Z" -- decimal at the end but no digits
142+ verifyFailParse "2015-01-01 13:30:00-01:60" -- malformed offset
139143 where
140144 verifyFailParse (s :: LT.Text) =
141145 let (dec :: Maybe UTCTime) = decode . LT.encodeUtf8 $ (LT.concat ["\"", s, "\""]) in