rrule-0.1.2: src/Data/Time/RRule/Parse.hs
module Data.Time.RRule.Parse
( parseRRule
)
where
import Prelude hiding (until)
import Control.Monad (msum)
import qualified Control.Monad.Combinators.NonEmpty as NE
import Data.List.NonEmpty (NonEmpty(..))
import Data.Maybe (fromMaybe, catMaybes, isJust)
import Data.Text (Text, intercalate, pack, unpack)
import Data.Time.Clock (UTCTime)
import Data.Time.Format (parseTimeM, defaultTimeLocale)
import Data.Time.RRule.Types (defaultRRule, RRule(..), Day(..), Frequency(..), ToRRule, TimeOrDate(..))
import Text.Megaparsec hiding (count)
import Text.Megaparsec.Char.Lexer
import qualified Data.Time.Calendar as Cal (Day, toGregorian)
type Parser = Parsec () Text
parseRRule :: Parser RRule
parseRRule = do
prefixText <- try . optional $ chunk "RRULE:"
rules <- parseVariable `sepBy` single ';'
let allRules = foldr combineRules defaultRRule rules
return allRules { prefix = isJust prefixText }
combineRules :: RRule -> RRule -> RRule
combineRules r s = RRule
{ prefix = prefix r || prefix s
, weekStart = combine weekStart
, frequency = combine frequency
, count = combine count
, until = combine until
, interval = combine interval
, bySecond = combine bySecond
, byMinute = combine byMinute
, byHour = combine byHour
, byDay = combine byDay
, byWeekNo = combine byWeekNo
, byMonth = combine byMonth
, byMonthDay = combine byMonthDay
, byYearDay = combine byYearDay
, bySetPos = combine bySetPos
}
where combine f = msum [f r, f s]
parseVariable :: Parser RRule
parseVariable = do
let prefix = False
weekStart <- parseVar "WKST" parseDay
frequency <- parseVar "FREQ" parseFrequency
count <- parseVar "COUNT" decimal
until <- parseVar "UNTIL" parseTimeOrDate
interval <- parseVar "INTERVAL" decimal
bySecond <- parseVar "BYSECOND" parseSomeInt
byMinute <- parseVar "BYMINUTE" parseSomeInt
byHour <- parseVar "BYHOUR" parseSomeInt
byDay <- parseVar "BYDAY" parseSomeDay
byWeekNo <- parseVar "BYWEEKNO" parseSomeInt
byMonthDay <- parseVar "BYMONTHDAY" parseSomeInt
byMonth <- parseVar "BYMONTH" parseSomeInt
byYearDay <- parseVar "BYYEARDAY" parseSomeInt
bySetPos <- parseVar "BYSETPOS" parseSomeInt
return RRule{..}
parseVar :: Text -> Parser a -> Parser (Maybe a)
parseVar label parse = try . optional $ chunk label >> single '=' >> parse
parseSomeInt :: Parser (NonEmpty Int)
parseSomeInt = parseInt `NE.sepBy1` single ','
parseSomeDay :: Parser (NonEmpty (Int, Day))
parseSomeDay = parseIntDay `NE.sepBy1` single ','
parseInt :: Parser Int
parseInt = do
sign <- try . optional $ single '-'
d <- decimal
return $ if isJust sign then (negate d) else d
parseIntDay :: Parser (Int, Day)
parseIntDay = do
n <- try . optional $ parseInt
d <- parseDay
return (fromMaybe 0 n, d)
parseTimeOrDate :: Parser TimeOrDate
parseTimeOrDate = fmap Time (try parseUtcTime) <|> fmap Date parseDate
parseDate :: Parser Cal.Day
parseDate = do
d <- takeP Nothing 8
parseTimeM False defaultTimeLocale "%Y%m%d" (unpack d)
parseUtcTime :: Parser UTCTime
parseUtcTime = do
d <- manyTill anySingle (single 'Z')
parseTimeM False defaultTimeLocale "%Y%m%dT%H%M%S" d
parseFrequency :: Parser Frequency
parseFrequency =
Secondly <$ chunk "SECONDLY" <|>
Minutely <$ chunk "MINUTELY" <|>
Hourly <$ chunk "HOURLY" <|>
Daily <$ chunk "DAILY" <|>
Weekly <$ chunk "WEEKLY" <|>
Monthly <$ chunk "MONTHLY" <|>
Yearly <$ chunk "YEARLY"
parseDay :: Parser Day
parseDay =
Sunday <$ chunk "SU" <|>
Monday <$ chunk "MO" <|>
Tuesday <$ chunk "TU" <|>
Wednesday <$ chunk "WE" <|>
Thursday <$ chunk "TH" <|>
Friday <$ chunk "FR" <|>
Saturday <$ chunk "SA"