summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregor Kleen <aethoago@141.li>2017-02-15 19:04:57 +0100
committerGregor Kleen <aethoago@141.li>2017-02-15 19:04:57 +0100
commit127c1212d7704392363e3614f339627bf514cfcf (patch)
tree10e9a0a1f48ec8aef518c3c33a6e34a7043d9738
parent5b09b096e38ed231b62df57736e87c989b481b5d (diff)
downloadpostdelay-127c1212d7704392363e3614f339627bf514cfcf.tar
postdelay-127c1212d7704392363e3614f339627bf514cfcf.tar.gz
postdelay-127c1212d7704392363e3614f339627bf514cfcf.tar.bz2
postdelay-127c1212d7704392363e3614f339627bf514cfcf.tar.xz
postdelay-127c1212d7704392363e3614f339627bf514cfcf.zip
Timezones
-rw-r--r--lib/Postdelay/Scan.hs5
-rw-r--r--lib/Postdelay/TimeSpec.hs49
-rw-r--r--lib/Postdelay/TimeSpec/Units.hs4
-rw-r--r--lib/Postdelay/TimeSpec/Utils.hs3
4 files changed, 45 insertions, 16 deletions
diff --git a/lib/Postdelay/Scan.hs b/lib/Postdelay/Scan.hs
index 888a237..0a265b4 100644
--- a/lib/Postdelay/Scan.hs
+++ b/lib/Postdelay/Scan.hs
@@ -68,10 +68,9 @@ extractDelay (Message headers _) = do
68 zoneHeaders = runListT $ do 68 zoneHeaders = runListT $ do
69 (OptionalField field content) <- ListT $ return headers 69 (OptionalField field content) <- ListT $ return headers
70 guard $ CI.mk field == "X-Timezone" 70 guard $ CI.mk field == "X-Timezone"
71 Right r <- runParserT (spaceConsumer *> lexeme pTimeZone <* eof) field content 71 either throwM return =<< runParserT (spaceConsumer *> lexeme timeZone <* eof) field content
72 return r
73 parseDelay :: Field -> m (Endo LocalTime) 72 parseDelay :: Field -> m (Endo LocalTime)
74 parseDelay (OptionalField field content) = either throwM return =<< runParserT (spaceConsumer *> lexeme pTimeSpec <* eof) field content 73 parseDelay (OptionalField field content) = either throwM return =<< runParserT (spaceConsumer *> lexeme timeSpec <* eof) field content
75 74
76localT :: Either TimeZone TZ -> Iso' UTCTime LocalTime 75localT :: Either TimeZone TZ -> Iso' UTCTime LocalTime
77localT (Left tz) = iso (utcToLocalTime tz) (localTimeToUTC tz) 76localT (Left tz) = iso (utcToLocalTime tz) (localTimeToUTC tz)
diff --git a/lib/Postdelay/TimeSpec.hs b/lib/Postdelay/TimeSpec.hs
index 03baf85..fbbce6b 100644
--- a/lib/Postdelay/TimeSpec.hs
+++ b/lib/Postdelay/TimeSpec.hs
@@ -1,8 +1,8 @@
1{-# LANGUAGE FlexibleContexts, ScopedTypeVariables, ViewPatterns, ConstraintKinds, TupleSections, RecordWildCards, TypeFamilies #-} 1{-# LANGUAGE FlexibleContexts, ScopedTypeVariables, ViewPatterns, ConstraintKinds, TupleSections, RecordWildCards, TypeFamilies #-}
2 2
3module Postdelay.TimeSpec 3module Postdelay.TimeSpec
4 ( pTimeSpec 4 ( timeSpec
5 , pTimeZone 5 , timeZone
6 6
7 , spaceConsumer, lexeme 7 , spaceConsumer, lexeme
8 ) where 8 ) where
@@ -15,6 +15,8 @@ import Text.Megaparsec
15import Control.Monad.IO.Class 15import Control.Monad.IO.Class
16import Control.Applicative 16import Control.Applicative
17import Control.Lens hiding ((#)) 17import Control.Lens hiding ((#))
18import Control.Exception (IOException)
19import Control.Monad.Catch
18 20
19import Data.Bool 21import Data.Bool
20import Data.Semigroup hiding (option) 22import Data.Semigroup hiding (option)
@@ -30,22 +32,49 @@ import Data.Time.Zones
30seconds' :: Timeable t => Lens' t Time 32seconds' :: Timeable t => Lens' t Time
31seconds' = seconds . iso (% Second) (# Second) 33seconds' = seconds . iso (% Second) (# Second)
32 34
35utcOffset :: Iso' TimeZone Time
36utcOffset = iso ((% Minute) . fromIntegral . timeZoneMinutes) (minutesToTimeZone . round . (# Minute))
33 37
34pTimeSpec :: StringParser s m => m (Endo LocalTime) 38
35pTimeSpec = label "Relative time specification" $ 39timeSpec :: StringParser s m => m (Endo LocalTime)
36 choice [ pOffsets False 40timeSpec = label "Relative time specification" $
41 choice [ offsets False
37 ] 42 ]
38 43
39pOffsets :: forall s m. StringParser s m 44offsets :: forall s m. StringParser s m
40 => Bool -- ^ Require sign on first offset? 45 => Bool -- ^ Require sign on first offset?
41 -> m (Endo LocalTime) 46 -> m (Endo LocalTime)
42pOffsets (bool optSigned signed -> reqSgn) = fmap fold $ (:) <$> offset reqSgn <*> many (offset optSigned) 47offsets (bool optSigned signed -> reqSgn) = fmap fold $ (:) <$> offset reqSgn <*> many (offset optSigned)
43 where 48 where
44 asOffset :: Time -> Endo LocalTime 49 asOffset :: Time -> Endo LocalTime
45 asOffset by = Endo $ flexDT.seconds' %~ (^+^ by) 50 asOffset by = Endo $ flexDT.seconds' %~ (^+^ by)
46 offset :: (m Time -> m Time) -> m (Endo LocalTime) 51 offset :: (m Time -> m Time) -> m (Endo LocalTime)
47 offset sgn = asOffset <$> lexeme (sgn timeLength) <?> "Time offset" 52 offset sgn = asOffset <$> lexeme (sgn timeLength) <?> "Time offset"
48 53
49pTimeZone :: (StringParser s m, MonadIO m) => m (Either TimeZone TZ) 54timeZone :: (StringParser s m, MonadIO m) => m (Either TimeZone TZ)
50pTimeZone = label "Timezone" $ 55timeZone = label "Timezone" $ (Left <$> numericTimezone) <|> (Right <$> namedTimezone)
51 empty 56
57numericTimezone :: StringParser s m => m TimeZone
58numericTimezone = review utcOffset <$> (sign <*> ((^+^) <$> hour <*> minute)) <?> "Numeric timezone"
59 where
60 hour = (% Hour) <$> twoDigit
61 minute = (% Minute) <$> twoDigit
62 twoDigit = (\n1 n2 -> fromDigit n1 * 10 + fromDigit n2) <$> digitChar <*> digitChar
63
64namedTimezone :: (StringParser s m, MonadIO m) => m TZ
65namedTimezone = do
66 n <- ident
67 tz <- liftIO $ do
68 let
69 fbHandler :: IO a -> (IOException -> IO a)
70 fbHandler fb _ = fb
71 foldl (\fb a -> a `catch` fbHandler fb) (return Nothing)
72 [ Just <$> loadSystemTZ n
73 , Just <$> loadTZFromDB n
74 ]
75 case tz of
76 Nothing -> fail $ "Could not resolve timezone: " ++ show n
77 (Just tz) -> return tz
78 where
79 asciiAlphaNum = oneOf $ ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z']
80 ident = (++) <$> some asciiAlphaNum <*> option [] ((:) <$> oneOf "_-/.+" <*> ident) <?> "Named timezone identifier"
diff --git a/lib/Postdelay/TimeSpec/Units.hs b/lib/Postdelay/TimeSpec/Units.hs
index 0062460..a094ea3 100644
--- a/lib/Postdelay/TimeSpec/Units.hs
+++ b/lib/Postdelay/TimeSpec/Units.hs
@@ -62,10 +62,8 @@ rational :: forall s m n. (StringParser s m, Fractional n, Num n) => m n
62rational = combine <$> some digitChar <*> option [] (char '.' *> many digitChar) <?> "Decimal number" 62rational = combine <$> some digitChar <*> option [] (char '.' *> many digitChar) <?> "Decimal number"
63 where 63 where
64 combine :: [Char] -> [Char] -> n 64 combine :: [Char] -> [Char] -> n
65 combine (map asN -> whole) (map asN -> fractional) 65 combine (map fromDigit -> whole) (map fromDigit -> fractional)
66 = foldl' (\i l -> i * 10 + l) 0 whole + (foldr' (\h t -> h + t / 10) 0 fractional) / 10 66 = foldl' (\i l -> i * 10 + l) 0 whole + (foldr' (\h t -> h + t / 10) 0 fractional) / 10
67 asN :: Char -> n
68 asN c = fromIntegral $ fromEnum c - fromEnum '0'
69 67
70timeUnit :: StringParser s m => m Time 68timeUnit :: StringParser s m => m Time
71timeUnit = label "Unit of time" . choice $ 69timeUnit = label "Unit of time" . choice $
diff --git a/lib/Postdelay/TimeSpec/Utils.hs b/lib/Postdelay/TimeSpec/Utils.hs
index 85ac299..83a271d 100644
--- a/lib/Postdelay/TimeSpec/Utils.hs
+++ b/lib/Postdelay/TimeSpec/Utils.hs
@@ -35,3 +35,6 @@ sign :: (StringParser s m, AdditiveGroup n) => m (n -> n)
35sign = label "sign" $ choice [ char '+' $> id 35sign = label "sign" $ choice [ char '+' $> id
36 , char '-' $> negateV 36 , char '-' $> negateV
37 ] 37 ]
38
39fromDigit :: Num n => Char -> n
40fromDigit c = fromIntegral $ fromEnum c - fromEnum '0'