{-# LANGUAGE UnicodeSyntax, DeriveDataTypeable #-}
-- | 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"
--
--  * and so on.
--
module Data.Dates.Formats
  (FormatElement (..), Format,
   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   Int
  | MONTH  Int
  | DAY    Int
  | HOUR   Int
  | MINUTE Int
  | SECOND Int
  | Fixed String
  deriving (Eq, Show)

-- | Date\/time format
type Format = [FormatElement]

nchars  Char  Parsec String st Int
nchars c = do
  s  many1 $ char c
  return $ length s

-- | Parser for date\/time format.
pFormat  Parsec String st Format
pFormat = many1 $ choice $ map try [pYear, pMonth, pDay,
                                    pHour, pMinute, pSecond,
                                    pFixed]
  where
    pYear   = YEAR   <$> nchars 'Y'
    pMonth  = MONTH  <$> nchars 'M'
    pDay    = DAY    <$> nchars 'D'
    pHour   = HOUR   <$> nchars 'H'
    pMinute = MINUTE <$> nchars 'm'
    pSecond = SECOND <$> nchars 'S'
    pFixed  = Fixed <$> (many1 $ noneOf "YMDHmS")

pYear  Int  Parsec String st DateTime
pYear n = do
  y  number n 10000
  if y < 2000
    then return $ mempty {year = y+2000}
    else return $ mempty {year = y}

pMonth  Int  Parsec String st DateTime
pMonth n = do
  m  number n 12
  return $ mempty {month = m}

pDay  Int  Parsec String st DateTime
pDay n = do
  d  number n 31
  return $ mempty {day = d}

pHour  Int  Parsec String st DateTime
pHour n = do
  h  number n 23
  return $ mempty {hour = h}

pMinute  Int  Parsec String st DateTime
pMinute n = do
  m  number n 59
  return $ mempty {minute = m}

pSecond  Int  Parsec String st DateTime
pSecond n = do
  s  number n 59
  return $ mempty {second = s}

-- | Make Parser for specified date format.
formatParser  Format  Parsec String st DateTime
formatParser format = mconcat <$> mapM parser format
  where
    parser (YEAR   n) = pYear n
    parser (MONTH  n) = pMonth n
    parser (DAY    n) = pDay n
    parser (HOUR   n) = pHour n
    parser (MINUTE n) = pMinute n
    parser (SECOND n) = pSecond n
    parser (Fixed s) = 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 <- runParser pFormat () "(date format string)" formatStr
  runParser (formatParser format) () "(date)" str