{-# LANGUAGE UnicodeSyntax, DeriveDataTypeable, FlexibleContexts, FlexibleInstances #-} -- | This module allows to parse arbitrary date formats. -- Date formats are specified as strings: -- -- * "DD.MM.YYY" -- -- * "YYYY\/MM\/DD" -- -- * "DD\/MM\/YYYY, HH:mm:SS" -- -- * "YY.MM.DD[, HH:mm:SS]" -- -- * and so on. -- module Data.Dates.Formats (FormatElement (..), Format, FormatParser, parseFormat, pFormat, formatParser, parseDateFormat ) where import Control.Applicative ((<$>)) import Data.Monoid import Text.Parsec import Data.Dates.Types import Data.Dates.Internal (number) -- | Date\/time format element data FormatElement = YEAR Bool Int | MONTH Bool Int | DAY Bool Int | HOUR Bool Int | MINUTE Bool Int | SECOND Bool Int | Whitespace Bool | Fixed Bool String deriving (Eq, Show) type FormatParser a = Parsec String Bool a -- | Date\/time format type Format = [FormatElement] nchars ∷ Char → FormatParser Int nchars c = do s ← many1 $ char c return $ length s brackets :: FormatParser a -> FormatParser a brackets p = do char '[' setState False result <- p char ']' setState True return result pFormat :: FormatParser Format pFormat = do elems <- many1 $ try (brackets format) <|> format return $ concat elems where format :: FormatParser Format format = many1 $ choice $ map try [element YEAR 'Y', element MONTH 'M', element DAY 'D', element HOUR 'H', element MINUTE 'm', element SECOND 'S', whitespaces, fixed] element constr c = do mandatory <- getState constr mandatory <$> nchars c whitespaces = do many1 $ oneOf " \r\n\t" mandatory <- getState return $ Whitespace mandatory fixed = do mandatory <- getState Fixed mandatory <$> (many1 $ noneOf "YMDHmS[] \t\r\n") pYear ∷ Stream s m Char => Int → ParsecT s st m DateTime pYear n = do y ← number n 10000 if y < 2000 then return $ mempty {year = y+2000} else return $ mempty {year = y} pMonth ∷ Stream s m Char => Int → ParsecT s st m DateTime pMonth n = do m ← number n 12 return $ mempty {month = m} pDay ∷ Stream s m Char => Int → ParsecT s st m DateTime pDay n = do d ← number n 31 return $ mempty {day = d} pHour ∷ Stream s m Char => Int → ParsecT s st m DateTime pHour n = do h ← number n 23 return $ mempty {hour = h} pMinute ∷ Stream s m Char => Int → ParsecT s st m DateTime pMinute n = do m ← number n 59 return $ mempty {minute = m} pSecond ∷ Stream s m Char => Int → ParsecT s st m DateTime pSecond n = do s ← number n 59 return $ mempty {second = s} opt :: Stream s m Char => Monoid a => Bool -> ParsecT s st m a -> ParsecT s st m a opt True p = p opt False p = option mempty p parseFormat :: String -> Either ParseError Format parseFormat formatStr = runParser pFormat True "(date format string)" formatStr -- | Make Parser for specified date format. formatParser ∷ Stream s m Char => Format → ParsecT s st m DateTime formatParser format = mconcat <$> mapM parser format where parser (YEAR m n) = opt m $ pYear n parser (MONTH m n) = opt m $ pMonth n parser (DAY m n) = opt m $ pDay n parser (HOUR m n) = opt m $ pHour n parser (MINUTE m n) = opt m $ pMinute n parser (SECOND m n) = opt m $ pSecond n parser (Whitespace m) = opt m ((many1 $ oneOf " \t\r\n") >> return mempty) parser (Fixed m s) = opt m ( string s >> return mempty ) -- | Parse date\/time in specified format. parseDateFormat :: String -- ^ Format string, i.e. "DD.MM.YY" -> String -- ^ String to parse -> Either ParseError DateTime parseDateFormat formatStr str = do format <- parseFormat formatStr runParser (formatParser format) () "(date)" str