{-# LANGUAGE OverloadedStrings #-}

module Network.HTTP.Date.Parser (parseHTTPDate) where

import Control.Applicative
import Control.Monad
import Data.Attoparsec.ByteString
import Data.Attoparsec.ByteString.Char8
import Data.ByteString
import Data.Char
import Network.HTTP.Date.Types

----------------------------------------------------------------

-- |
-- Parsing HTTP Date. Currently only RFC1123 style is supported.
--
-- >>> parseHTTPDate "Tue, 15 Nov 1994 08:12:31 GMT"
-- Just (HTTPDate {hdYear = 1994, hdMonth = 11, hdDay = 15, hdHour = 8, hdMinute = 12, hdSecond = 31, hdWkday = 2})

parseHTTPDate :: ByteString -> Maybe HTTPDate
parseHTTPDate bs = case parseOnly rfc1123Date bs of
    Right ut -> Just ut
    _        -> Nothing

rfc1123Date :: Parser HTTPDate
rfc1123Date = do
    w <- wkday
    void $ string ", "
    (y,m,d) <- date1
    sp
    (h,n,s) <- time
    sp
    -- RFC 2616 defines GMT only but there are actually ill-formed ones such 
    -- as "+0000" and "UTC" in the wild.
    void $ string "GMT" <|> string "+0000" <|> string "UTC"
    return $ defaultHTTPDate {
        hdYear   = y
      , hdMonth  = m
      , hdDay    = d
      , hdHour   = h
      , hdMinute = n
      , hdSecond = s
      , hdWkday  = w
      }

wkday :: Parser Int
wkday = 1 <$ string "Mon"
    <|> 2 <$ string "Tue"
    <|> 3 <$ string "Wed"
    <|> 4 <$ string "Thu"
    <|> 5 <$ string "Fri"
    <|> 6 <$ string "Sat"
    <|> 7 <$ string "Sun"

date1 :: Parser (Int,Int,Int)
date1 = do
    d <- day
    sp
    m <- month
    sp
    y <- year
    return (y,m,d)
 where
   day = digit2
   year = digit4

sp :: Parser ()
sp = () <$ char ' '

time :: Parser (Int,Int,Int)
time = do
    h <- digit2
    void $ char ':'
    m <- digit2
    void $ char ':'
    s <- digit2
    return (h,m,s)

month :: Parser Int
month =  1 <$ string "Jan"
    <|>  2 <$ string "Feb"
    <|>  3 <$ string "Mar"
    <|>  4 <$ string "Apr"
    <|>  5 <$ string "May"
    <|>  6 <$ string "Jun"
    <|>  7 <$ string "Jul"
    <|>  8 <$ string "Aug"
    <|>  9 <$ string "Sep"
    <|> 10 <$ string "Oct"
    <|> 11 <$ string "Nov"
    <|> 12 <$ string "Dec"

digit2 :: Parser Int
digit2 = do
    x1 <- toInt <$> digit
    x2 <- toInt <$> digit
    return $ x1 * 10 + x2

digit4 :: Parser Int
digit4 = do
    x1 <- toInt <$> digit
    x2 <- toInt <$> digit
    x3 <- toInt <$> digit
    x4 <- toInt <$> digit
    return $ x1 * 1000 + x2 * 100 + x3 * 10 + x4

toInt :: Char -> Int
toInt c = ord c - ord '0'
