{-# LANGUAGE Safe #-}

module Data.Time.Format.Parse.Class
    (
        -- * Parsing
      ParseNumericPadding(..)
    , ParseTime(..)
    , parseSpecifiers
    , timeSubstituteTimeSpecifier
    , timeParseTimeSpecifier
    , durationParseTimeSpecifier
    ) where

import Data.Char
import Data.Maybe
import Data.Proxy
import Data.Time.Format.Locale
import Text.ParserCombinators.ReadP

data ParseNumericPadding
    = NoPadding
    | SpacePadding
    | ZeroPadding

-- | The class of types which can be parsed given a UNIX-style time format
-- string.
class ParseTime t where
    -- | @since 1.9.1
    substituteTimeSpecifier :: Proxy t -> TimeLocale -> Char -> Maybe String
    substituteTimeSpecifier Proxy t
_ TimeLocale
_ Char
_ = forall a. Maybe a
Nothing
    -- | Get the string corresponding to the given format specifier.
    --
    -- @since 1.9.1
    parseTimeSpecifier :: Proxy t -> TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
    -- | Builds a time value from a parsed input string.
    -- If the input does not include all the information needed to
    -- construct a complete value, any missing parts should be taken
    -- from 1970-01-01 00:00:00 +0000 (which was a Thursday).
    -- In the absence of @%C@ or @%Y@, century is 1969 - 2068.
    --
    -- @since 1.9.1
    buildTime ::
           TimeLocale -- ^ The time locale.
        -> [(Char, String)] -- ^ Pairs of format characters and the
                                 -- corresponding part of the input.
        -> Maybe t

-- | Case-insensitive version of 'Text.ParserCombinators.ReadP.char'.
charCI :: Char -> ReadP Char
charCI :: Char -> ReadP Char
charCI Char
c = (Char -> Bool) -> ReadP Char
satisfy (\Char
x -> Char -> Char
toUpper Char
c forall a. Eq a => a -> a -> Bool
== Char -> Char
toUpper Char
x)

-- | Case-insensitive version of 'Text.ParserCombinators.ReadP.string'.
stringCI :: String -> ReadP String
stringCI :: String -> ReadP String
stringCI String
this = do
    let
        scan :: String -> String -> ReadP String
scan [] String
_ = forall (m :: * -> *) a. Monad m => a -> m a
return String
this
        scan (Char
x:String
xs) (Char
y:String
ys)
            | Char -> Char
toUpper Char
x forall a. Eq a => a -> a -> Bool
== Char -> Char
toUpper Char
y = do
                Char
_ <- ReadP Char
get
                String -> String -> ReadP String
scan String
xs String
ys
        scan String
_ String
_ = forall a. ReadP a
pfail
    String
s <- ReadP String
look
    String -> String -> ReadP String
scan String
this String
s

parseSpecifiers :: ParseTime t => Proxy t -> TimeLocale -> String -> ReadP [(Char, String)]
parseSpecifiers :: forall t.
ParseTime t =>
Proxy t -> TimeLocale -> String -> ReadP [(Char, String)]
parseSpecifiers Proxy t
pt TimeLocale
locale = let
    parse :: String -> ReadP [(Char, String)]
    parse :: String -> ReadP [(Char, String)]
parse [] = forall (m :: * -> *) a. Monad m => a -> m a
return []
    parse (Char
'%':String
cs) = String -> ReadP [(Char, String)]
parse1 String
cs
    parse (Char
c:String
cs)
        | Char -> Bool
isSpace Char
c = do
            Char
_ <- (Char -> Bool) -> ReadP Char
satisfy Char -> Bool
isSpace
            case String
cs of
                (Char
c':String
_)
                    | Char -> Bool
isSpace Char
c' -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
                String
_ -> ReadP ()
skipSpaces
            String -> ReadP [(Char, String)]
parse String
cs
    parse (Char
c:String
cs) = do
        Char
_ <- Char -> ReadP Char
charCI Char
c
        String -> ReadP [(Char, String)]
parse String
cs
    parse1 :: String -> ReadP [(Char, String)]
    parse1 :: String -> ReadP [(Char, String)]
parse1 (Char
'-':String
cs) = Maybe ParseNumericPadding -> String -> ReadP [(Char, String)]
parse2 (forall a. a -> Maybe a
Just ParseNumericPadding
NoPadding) String
cs
    parse1 (Char
'_':String
cs) = Maybe ParseNumericPadding -> String -> ReadP [(Char, String)]
parse2 (forall a. a -> Maybe a
Just ParseNumericPadding
SpacePadding) String
cs
    parse1 (Char
'0':String
cs) = Maybe ParseNumericPadding -> String -> ReadP [(Char, String)]
parse2 (forall a. a -> Maybe a
Just ParseNumericPadding
ZeroPadding) String
cs
    parse1 String
cs = Maybe ParseNumericPadding -> String -> ReadP [(Char, String)]
parse2 forall a. Maybe a
Nothing String
cs
    parse2 :: Maybe ParseNumericPadding -> String -> ReadP [(Char, String)]
    parse2 :: Maybe ParseNumericPadding -> String -> ReadP [(Char, String)]
parse2 Maybe ParseNumericPadding
mpad (Char
'E':String
cs) = Maybe ParseNumericPadding
-> Bool -> String -> ReadP [(Char, String)]
parse3 Maybe ParseNumericPadding
mpad Bool
True String
cs
    parse2 Maybe ParseNumericPadding
mpad String
cs = Maybe ParseNumericPadding
-> Bool -> String -> ReadP [(Char, String)]
parse3 Maybe ParseNumericPadding
mpad Bool
False String
cs
    parse3 :: Maybe ParseNumericPadding -> Bool -> String -> ReadP [(Char, String)]
    parse3 :: Maybe ParseNumericPadding
-> Bool -> String -> ReadP [(Char, String)]
parse3 Maybe ParseNumericPadding
_ Bool
_ (Char
'%':String
cs) = do
        Char
_ <- Char -> ReadP Char
char Char
'%'
        String -> ReadP [(Char, String)]
parse String
cs
    parse3 Maybe ParseNumericPadding
_ Bool
_ (Char
c:String
cs)
        | Just String
s <- forall t.
ParseTime t =>
Proxy t -> TimeLocale -> Char -> Maybe String
substituteTimeSpecifier Proxy t
pt TimeLocale
locale Char
c = String -> ReadP [(Char, String)]
parse forall a b. (a -> b) -> a -> b
$ String
s forall a. [a] -> [a] -> [a]
++ String
cs
    parse3 Maybe ParseNumericPadding
mpad Bool
_alt (Char
c:String
cs) = do
        String
str <- forall t.
ParseTime t =>
Proxy t
-> TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
parseTimeSpecifier Proxy t
pt TimeLocale
locale Maybe ParseNumericPadding
mpad Char
c
        [(Char, String)]
specs <- String -> ReadP [(Char, String)]
parse String
cs
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ (Char
c, String
str) forall a. a -> [a] -> [a]
: [(Char, String)]
specs
    parse3 Maybe ParseNumericPadding
_ Bool
_ [] = forall (m :: * -> *) a. Monad m => a -> m a
return []
    in String -> ReadP [(Char, String)]
parse

data PaddingSide
    = PrePadding
    | PostPadding

allowEmptyParser :: Bool -> ReadP String
allowEmptyParser :: Bool -> ReadP String
allowEmptyParser Bool
False = forall a. ReadP a -> ReadP [a]
many1 ((Char -> Bool) -> ReadP Char
satisfy Char -> Bool
isDigit)
allowEmptyParser Bool
True = forall a. ReadP a -> ReadP [a]
many ((Char -> Bool) -> ReadP Char
satisfy Char -> Bool
isDigit)

parsePaddedDigits :: PaddingSide -> ParseNumericPadding -> Bool -> Int -> ReadP String
parsePaddedDigits :: PaddingSide -> ParseNumericPadding -> Bool -> Int -> ReadP String
parsePaddedDigits PaddingSide
_ ParseNumericPadding
ZeroPadding Bool
_ Int
n = forall a. Int -> ReadP a -> ReadP [a]
count Int
n ((Char -> Bool) -> ReadP Char
satisfy Char -> Bool
isDigit)
parsePaddedDigits PaddingSide
PrePadding ParseNumericPadding
SpacePadding Bool
allowEmpty Int
_n = ReadP ()
skipSpaces forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Bool -> ReadP String
allowEmptyParser Bool
allowEmpty
parsePaddedDigits PaddingSide
PostPadding ParseNumericPadding
SpacePadding Bool
allowEmpty Int
_n = do
    String
r <- Bool -> ReadP String
allowEmptyParser Bool
allowEmpty
    ReadP ()
skipSpaces
    forall (m :: * -> *) a. Monad m => a -> m a
return String
r
parsePaddedDigits PaddingSide
_ ParseNumericPadding
NoPadding Bool
False Int
_n = forall a. ReadP a -> ReadP [a]
many1 ((Char -> Bool) -> ReadP Char
satisfy Char -> Bool
isDigit)
parsePaddedDigits PaddingSide
_ ParseNumericPadding
NoPadding Bool
True Int
_n = forall a. ReadP a -> ReadP [a]
many ((Char -> Bool) -> ReadP Char
satisfy Char -> Bool
isDigit)

parsePaddedSignedDigits :: ParseNumericPadding -> Int -> ReadP String
parsePaddedSignedDigits :: ParseNumericPadding -> Int -> ReadP String
parsePaddedSignedDigits ParseNumericPadding
pad Int
n = do
    String
sign <- forall a. a -> ReadP a -> ReadP a
option String
"" forall a b. (a -> b) -> a -> b
$ Char -> ReadP Char
char Char
'-' forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return String
"-"
    String
digits <- PaddingSide -> ParseNumericPadding -> Bool -> Int -> ReadP String
parsePaddedDigits PaddingSide
PrePadding ParseNumericPadding
pad Bool
False Int
n
    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String
sign forall a. [a] -> [a] -> [a]
++ String
digits

parseSignedDecimal :: ReadP String
parseSignedDecimal :: ReadP String
parseSignedDecimal = do
    String
sign <- forall a. a -> ReadP a -> ReadP a
option String
"" forall a b. (a -> b) -> a -> b
$ Char -> ReadP Char
char Char
'-' forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return String
"-"
    ReadP ()
skipSpaces
    String
digits <- forall a. ReadP a -> ReadP [a]
many1 forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> ReadP Char
satisfy Char -> Bool
isDigit
    String
decimaldigits <-
        forall a. a -> ReadP a -> ReadP a
option String
"" forall a b. (a -> b) -> a -> b
$ do
            Char
_ <- Char -> ReadP Char
char Char
'.'
            String
dd <- forall a. ReadP a -> ReadP [a]
many forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> ReadP Char
satisfy Char -> Bool
isDigit
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Char
'.' forall a. a -> [a] -> [a]
: String
dd
    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String
sign forall a. [a] -> [a] -> [a]
++ String
digits forall a. [a] -> [a] -> [a]
++ String
decimaldigits

timeParseTimeSpecifier :: TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
timeParseTimeSpecifier :: TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
timeParseTimeSpecifier TimeLocale
l Maybe ParseNumericPadding
mpad Char
c = let
    digits' :: PaddingSide -> ParseNumericPadding -> Bool -> Int -> ReadP String
digits' PaddingSide
ps ParseNumericPadding
pad = PaddingSide -> ParseNumericPadding -> Bool -> Int -> ReadP String
parsePaddedDigits PaddingSide
ps (forall a. a -> Maybe a -> a
fromMaybe ParseNumericPadding
pad Maybe ParseNumericPadding
mpad)
    digits :: ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
pad = PaddingSide -> ParseNumericPadding -> Bool -> Int -> ReadP String
digits' PaddingSide
PrePadding ParseNumericPadding
pad Bool
False
    oneOf :: [String] -> ReadP String
oneOf = forall a. [ReadP a] -> ReadP a
choice forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map String -> ReadP String
stringCI
    numericTZ :: ReadP String
numericTZ = do
        Char
s <- forall a. [ReadP a] -> ReadP a
choice [Char -> ReadP Char
char Char
'+', Char -> ReadP Char
char Char
'-']
        String
h <- PaddingSide -> ParseNumericPadding -> Bool -> Int -> ReadP String
parsePaddedDigits PaddingSide
PrePadding ParseNumericPadding
ZeroPadding Bool
False Int
2
        forall a. ReadP a -> ReadP ()
optional (Char -> ReadP Char
char Char
':')
        String
m <- PaddingSide -> ParseNumericPadding -> Bool -> Int -> ReadP String
parsePaddedDigits PaddingSide
PrePadding ParseNumericPadding
ZeroPadding Bool
False Int
2
        forall (m :: * -> *) a. Monad m => a -> m a
return (Char
s forall a. a -> [a] -> [a]
: String
h forall a. [a] -> [a] -> [a]
++ String
m)
    in case Char
c of
        -- century
           Char
'C' -> (Char -> ReadP Char
char Char
'-' forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Char
'-' forall a. a -> [a] -> [a]
:) (ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
SpacePadding Int
2)) forall a. ReadP a -> ReadP a -> ReadP a
<++ ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
SpacePadding Int
2
           Char
'f' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
SpacePadding Int
2
        -- year
           Char
'Y' -> (Char -> ReadP Char
char Char
'-' forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Char
'-' forall a. a -> [a] -> [a]
:) (ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
SpacePadding Int
4)) forall a. ReadP a -> ReadP a -> ReadP a
<++ ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
SpacePadding Int
4
           Char
'G' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
SpacePadding Int
4
        -- year of century
           Char
'y' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
2
           Char
'g' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
2
        -- month of year
           Char
'B' -> [String] -> ReadP String
oneOf (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> a
fst (TimeLocale -> [(String, String)]
months TimeLocale
l))
           Char
'b' -> [String] -> ReadP String
oneOf (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd (TimeLocale -> [(String, String)]
months TimeLocale
l))
           Char
'm' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
2
        -- day of month
           Char
'd' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
2
           Char
'e' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
SpacePadding Int
2
        -- week of year
           Char
'V' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
2
           Char
'U' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
2
           Char
'W' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
2
        -- day of week
           Char
'u' -> [String] -> ReadP String
oneOf forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (forall a. a -> [a] -> [a]
: []) [Char
'1' .. Char
'7']
           Char
'a' -> [String] -> ReadP String
oneOf (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd (TimeLocale -> [(String, String)]
wDays TimeLocale
l))
           Char
'A' -> [String] -> ReadP String
oneOf (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> a
fst (TimeLocale -> [(String, String)]
wDays TimeLocale
l))
           Char
'w' -> [String] -> ReadP String
oneOf forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (forall a. a -> [a] -> [a]
: []) [Char
'0' .. Char
'6']
        -- day of year
           Char
'j' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
3
        -- dayhalf of day (i.e. AM or PM)
           Char
'P' ->
               [String] -> ReadP String
oneOf
                   (let
                        (String
am, String
pm) = TimeLocale -> (String, String)
amPm TimeLocale
l
                        in [String
am, String
pm])
           Char
'p' ->
               [String] -> ReadP String
oneOf
                   (let
                        (String
am, String
pm) = TimeLocale -> (String, String)
amPm TimeLocale
l
                        in [String
am, String
pm])
        -- hour of day (i.e. 24h)
           Char
'H' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
2
           Char
'k' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
SpacePadding Int
2
        -- hour of dayhalf (i.e. 12h)
           Char
'I' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
2
           Char
'l' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
SpacePadding Int
2
        -- minute of hour
           Char
'M' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
2
        -- second of minute
           Char
'S' -> ParseNumericPadding -> Int -> ReadP String
digits ParseNumericPadding
ZeroPadding Int
2
        -- picosecond of second
           Char
'q' -> PaddingSide -> ParseNumericPadding -> Bool -> Int -> ReadP String
digits' PaddingSide
PostPadding ParseNumericPadding
ZeroPadding Bool
True Int
12
           Char
'Q' -> (Char -> ReadP Char
char Char
'.' forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> PaddingSide -> ParseNumericPadding -> Bool -> Int -> ReadP String
digits' PaddingSide
PostPadding ParseNumericPadding
NoPadding Bool
True Int
12) forall a. ReadP a -> ReadP a -> ReadP a
<++ forall (m :: * -> *) a. Monad m => a -> m a
return String
""
        -- time zone
           Char
'z' -> ReadP String
numericTZ
           Char
'Z' -> (Char -> Bool) -> ReadP String
munch1 Char -> Bool
isAlpha forall a. ReadP a -> ReadP a -> ReadP a
<++ ReadP String
numericTZ
        -- seconds since epoch
           Char
's' -> (Char -> ReadP Char
char Char
'-' forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Char
'-' forall a. a -> [a] -> [a]
:) ((Char -> Bool) -> ReadP String
munch1 Char -> Bool
isDigit)) forall a. ReadP a -> ReadP a -> ReadP a
<++ (Char -> Bool) -> ReadP String
munch1 Char -> Bool
isDigit
           Char
_ -> forall (m :: * -> *) a. MonadFail m => String -> m a
fail forall a b. (a -> b) -> a -> b
$ String
"Unknown format character: " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Char
c

timeSubstituteTimeSpecifier :: TimeLocale -> Char -> Maybe String
timeSubstituteTimeSpecifier :: TimeLocale -> Char -> Maybe String
timeSubstituteTimeSpecifier TimeLocale
l Char
'c' = forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ TimeLocale -> String
dateTimeFmt TimeLocale
l
timeSubstituteTimeSpecifier TimeLocale
_ Char
'R' = forall a. a -> Maybe a
Just String
"%H:%M"
timeSubstituteTimeSpecifier TimeLocale
_ Char
'T' = forall a. a -> Maybe a
Just String
"%H:%M:%S"
timeSubstituteTimeSpecifier TimeLocale
l Char
'X' = forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ TimeLocale -> String
timeFmt TimeLocale
l
timeSubstituteTimeSpecifier TimeLocale
l Char
'r' = forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ TimeLocale -> String
time12Fmt TimeLocale
l
timeSubstituteTimeSpecifier TimeLocale
_ Char
'D' = forall a. a -> Maybe a
Just String
"%m/%d/%y"
timeSubstituteTimeSpecifier TimeLocale
_ Char
'F' = forall a. a -> Maybe a
Just String
"%Y-%m-%d"
timeSubstituteTimeSpecifier TimeLocale
l Char
'x' = forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ TimeLocale -> String
dateFmt TimeLocale
l
timeSubstituteTimeSpecifier TimeLocale
_ Char
'h' = forall a. a -> Maybe a
Just String
"%b"
timeSubstituteTimeSpecifier TimeLocale
_ Char
_ = forall a. Maybe a
Nothing

durationParseTimeSpecifier :: TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
durationParseTimeSpecifier :: TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
durationParseTimeSpecifier TimeLocale
_ Maybe ParseNumericPadding
mpad Char
c = let
    padopt :: Int -> ReadP String
padopt = ParseNumericPadding -> Int -> ReadP String
parsePaddedSignedDigits forall a b. (a -> b) -> a -> b
$ forall a. a -> Maybe a -> a
fromMaybe ParseNumericPadding
NoPadding Maybe ParseNumericPadding
mpad
    in case Char
c of
           Char
'y' -> Int -> ReadP String
padopt Int
1
           Char
'b' -> Int -> ReadP String
padopt Int
1
           Char
'B' -> Int -> ReadP String
padopt Int
2
           Char
'w' -> Int -> ReadP String
padopt Int
1
           Char
'd' -> Int -> ReadP String
padopt Int
1
           Char
'D' -> Int -> ReadP String
padopt Int
1
           Char
'h' -> Int -> ReadP String
padopt Int
1
           Char
'H' -> Int -> ReadP String
padopt Int
2
           Char
'm' -> Int -> ReadP String
padopt Int
1
           Char
'M' -> Int -> ReadP String
padopt Int
2
           Char
's' -> ReadP String
parseSignedDecimal
           Char
'S' -> ReadP String
parseSignedDecimal
           Char
_ -> forall (m :: * -> *) a. MonadFail m => String -> m a
fail forall a b. (a -> b) -> a -> b
$ String
"Unknown format character: " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Char
c