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) import Text.Megaparsec hiding (count) import Text.Megaparsec.Char.Lexer 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" parseUtcTime 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) 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"