module Data.Time.Format.HTTP.Common
( shortWeekDayName
, shortWeekDayNameP
, longWeekDayName
, longWeekDayNameP
, shortMonthName
, shortMonthNameP
, longMonthName
, longMonthNameP
, show4
, show2
, show2'
, read4
, read2
, read2'
, show4digitsTZ
, read4digitsTZ
, assertWeekDayIsGood
, assertGregorianDateIsGood
, assertTimeOfDayIsGood
, optionMaybe
, finishOff
, parseAttempt
, parseAttempt'
)
where
import Blaze.ByteString.Builder.ByteString as B
import Blaze.Text.Int as BT
import Control.Applicative
import Control.Exception.Base
import Control.Monad
import Control.Monad.Unicode
import Data.Ascii (Ascii, AsciiBuilder)
import qualified Data.Ascii as A
import Data.Attempt
import Data.Attoparsec.Char8 as P
import Data.ByteString (ByteString)
import Data.Char
import Data.Monoid.Unicode
import Data.Fixed
import Data.Time
import Data.Time.Calendar.WeekDate
import Prelude.Unicode
shortWeekDayName ∷ Num n ⇒ n → AsciiBuilder
shortWeekDayName = A.toAsciiBuilder ∘ go
where
go 1 = "Mon"
go 2 = "Tue"
go 3 = "Wed"
go 4 = "Thu"
go 5 = "Fri"
go 6 = "Sat"
go 7 = "Sun"
go n = error ("shortWeekDayName: invalid week day: " ⧺ show n)
shortWeekDayNameP ∷ Num n ⇒ Parser n
shortWeekDayNameP
= choice [ string "Mon" *> return 1
, char 'T'
*> choice [ string "ue" *> return 2
, string "hu" *> return 4
]
, string "Wed" *> return 3
, string "Fri" *> return 5
, char 'S'
*> choice [ string "at" *> return 6
, string "un" *> return 7
]
]
longWeekDayName ∷ Num n ⇒ n → AsciiBuilder
longWeekDayName = A.toAsciiBuilder ∘ go
where
go 1 = "Monday"
go 2 = "Tuesday"
go 3 = "Wednesday"
go 4 = "Thursday"
go 5 = "Friday"
go 6 = "Saturday"
go 7 = "Sunday"
go n = error ("longWeekDayName: invalid week day: " ⧺ show n)
longWeekDayNameP ∷ Num n ⇒ Parser n
longWeekDayNameP
= choice [ string "Monday" *> return 1
, char 'T'
*> choice [ string "uesday" *> return 2
, string "hursday" *> return 4
]
, string "Wednesday" *> return 3
, string "Friday" *> return 5
, char 'S'
*> choice [ string "aturday" *> return 6
, string "unday" *> return 7
]
]
shortMonthName ∷ Num n ⇒ n → AsciiBuilder
shortMonthName = A.toAsciiBuilder ∘ go
where
go 1 = "Jan"
go 2 = "Feb"
go 3 = "Mar"
go 4 = "Apr"
go 5 = "May"
go 6 = "Jun"
go 7 = "Jul"
go 8 = "Aug"
go 9 = "Sep"
go 10 = "Oct"
go 11 = "Nov"
go 12 = "Dec"
go n = error ("shortMonthName: invalid month: " ⧺ show n)
shortMonthNameP ∷ Num n ⇒ Parser n
shortMonthNameP
= choice [ char 'J'
*> choice [ string "an" *> return 1
, char 'u'
*> choice [ char 'n' *> return 6
, char 'l' *> return 7
]
]
, string "Feb" *> return 2
, string "Ma"
*> choice [ char 'r' *> return 3
, char 'y' *> return 5
]
, char 'A'
*> choice [ string "pr" *> return 4
, string "ug" *> return 8
]
, string "Sep" *> return 9
, string "Oct" *> return 10
, string "Nov" *> return 11
, string "Dec" *> return 12
]
longMonthName ∷ Num n ⇒ n → AsciiBuilder
longMonthName = A.toAsciiBuilder ∘ go
where
go 1 = "January"
go 2 = "February"
go 3 = "March"
go 4 = "April"
go 5 = "May"
go 6 = "June"
go 7 = "July"
go 8 = "August"
go 9 = "September"
go 10 = "October"
go 11 = "November"
go 12 = "December"
go n = error ("longMonthName: invalid month: " ⧺ show n)
longMonthNameP ∷ Num n ⇒ Parser n
longMonthNameP
= choice [ char 'J'
*> choice [ string "anuary" *> return 1
, char 'u'
*> choice [ string "ne" *> return 6
, string "ly" *> return 7
]
]
, string "February" *> return 2
, string "Ma"
*> choice [ string "rch" *> return 3
, char 'y' *> return 5
]
, char 'A'
*> choice [ string "pril" *> return 4
, string "ugust" *> return 8
]
, string "September" *> return 9
, string "October" *> return 10
, string "November" *> return 11
, string "December" *> return 12
]
show4 ∷ Integral i ⇒ i → AsciiBuilder
show4 = A.unsafeFromBuilder ∘ go
where
go i | i ≥ 0 ∧ i < 10 = B.fromByteString "000" ⊕ BT.digit i
| i ≥ 0 ∧ i < 100 = B.fromByteString "00" ⊕ BT.integral i
| i ≥ 0 ∧ i < 1000 = B.fromByteString "0" ⊕ BT.integral i
| i ≥ 0 ∧ i < 10000 = BT.integral i
| otherwise = error ("show4: the integer i must satisfy 0 <= i < 10000: " ⧺ show i)
show2 ∷ Integral i ⇒ i → AsciiBuilder
show2 = A.unsafeFromBuilder ∘ go
where
go i | i ≥ 0 ∧ i < 10 = B.fromByteString "0" ⊕ BT.digit i
| i ≥ 0 ∧ i < 100 = BT.integral i
| otherwise = error ("show2: the integer i must satisfy 0 <= i < 100: " ⧺ show i)
show2' ∷ Integral i ⇒ i → AsciiBuilder
show2' = A.unsafeFromBuilder ∘ go
where
go i | i ≥ 0 ∧ i < 10 = B.fromByteString " " ⊕ BT.digit i
| i ≥ 0 ∧ i < 100 = BT.integral i
| otherwise = error ("show2': the integer i must satisfy 0 <= i < 100: " ⧺ show i)
read4 ∷ Num n ⇒ Parser n
read4 = do n1 ← digit'
n2 ← digit'
n3 ← digit'
n4 ← digit'
return (n1 * 1000 + n2 * 100 + n3 * 10 + n4)
read2 ∷ Num n ⇒ Parser n
read2 = do n1 ← digit'
n2 ← digit'
return (n1 * 10 + n2)
read2' ∷ Num n ⇒ Parser n
read2' = do n1 ← (char ' ' *> pure 0) <|> digit'
n2 ← digit'
return (n1 * 10 + n2)
digit' ∷ Num n ⇒ Parser n
digit' = fromIntegral <$> fromC <$> P.digit
where
fromC c = ord c ord '0'
show4digitsTZ ∷ TimeZone → AsciiBuilder
show4digitsTZ tz
= case timeZoneMinutes tz of
offset | offset < 0 → A.toAsciiBuilder "-" ⊕ showTZ' (negate offset)
| otherwise → A.toAsciiBuilder "+" ⊕ showTZ' offset
where
showTZ' offset
= let h = offset `div` 60
m = offset h * 60
in
show2 h ⊕ show2 m
read4digitsTZ ∷ Parser TimeZone
read4digitsTZ
= do sign ← (char '+' *> return 1)
<|>
(char '-' *> return (1))
hour ← read2
minute ← read2
let tz = TimeZone {
timeZoneMinutes = sign * (hour * 60 + minute)
, timeZoneSummerOnly = False
, timeZoneName = timeZoneOffsetString tz
}
return tz
assertWeekDayIsGood ∷ Monad m ⇒ Int → Day → m ()
assertWeekDayIsGood givenWD gregDay
= let (_, _, correctWD ) = toWeekDate gregDay
(year, month, day) = toGregorian gregDay
in
unless (givenWD ≡ correctWD)
∘ fail
$ concat [ "Gregorian day "
, show year
, "-"
, show month
, "-"
, show day
, " is "
, toStr $ longWeekDayName correctWD
, ", not "
, toStr $ longWeekDayName givenWD
]
where
toStr ∷ AsciiBuilder → String
toStr = A.toString ∘ A.fromAsciiBuilder
assertGregorianDateIsGood ∷ Monad m ⇒ Integer → Int → Int → m Day
assertGregorianDateIsGood year month day
= case fromGregorianValid year month day of
Nothing
→ fail $ concat [ "Invalid gregorian day: "
, show year
, "-"
, show month
, "-"
, show day
]
Just gregDay
→ return gregDay
assertTimeOfDayIsGood ∷ Monad m ⇒ Int → Int → Pico → m TimeOfDay
assertTimeOfDayIsGood hour minute second
= case makeTimeOfDayValid hour minute second of
Nothing
→ fail $ concat [ "Invalid time of day: "
, show hour
, ":"
, show minute
, ":"
, showFixed True second
]
Just tod
→ return tod
optionMaybe ∷ Alternative f ⇒ f a → f (Maybe a)
optionMaybe p
= option Nothing (Just <$> p)
finishOff ∷ Parser α → Parser α
finishOff = ((endOfInput *>) ∘ return =≪)
parseAttempt ∷ Exception e
⇒ (String → e)
→ Parser α
→ ByteString
→ Attempt α
parseAttempt f p bs
= case parseOnly (finishOff p) bs of
Right α → Success α
Left e → Failure $ f e
parseAttempt' ∷ Parser α → Ascii → Attempt α
parseAttempt' = (∘ A.toByteString) ∘ parseAttempt StringException