Parse a date in various formats I have a problem similar to the question, namely reading in text which describes a date - in various formats. I want to use the parseTimeM function from Date.Time module in time package. 

My current solution can probably be improved style wise but it should remain easy to read and easy to extend. Suggestion?

readDate3 :: Text -&gt;   UTCTime
readDate3 datestring  =

    case shortMonth of
        Just t -&gt; t
        Nothing -&gt; case longMonth of
            Just t2 -&gt; t2
            Nothing -&gt; case monthPoint of
                Just t3 -&gt; t3
                Nothing -&gt; case germanNumeralShort of
                  Just t3 -&gt; t3
                  Nothing -&gt; case germanNumeral of
                    Just t3 -&gt; t3
                    Nothing -&gt; case isoformat of
                      Just t4 -&gt; t4
                      Nothing -&gt; errorT   ["readDate3", datestring, "is not parsed"]

    where
        shortMonth = parseTimeM True defaultTimeLocale
            "%b %-d, %Y" (t2s datestring) :: Maybe UTCTime
        longMonth = parseTimeM True defaultTimeLocale
            "%B %-d, %Y" (t2s datestring) :: Maybe UTCTime
        monthPoint = parseTimeM True defaultTimeLocale
            "%b. %-d, %Y" (t2s datestring) :: Maybe UTCTime
        germanNumeral = parseTimeM True defaultTimeLocale
            "%-d.%-m.%Y" (t2s datestring) :: Maybe UTCTime
        germanNumeralShort = parseTimeM True defaultTimeLocale
            "%-d.%-m.%y" (t2s datestring) :: Maybe UTCTime
        isoformat = parseTimeM True defaultTimeLocale
            "%Y-%m-%d" (t2s datestring) :: Maybe UTCTime
Don't repeat yourself

Your code violates the DRY principle. If we replace

germanNumeralShort = parseTimeM True defaultTimeLocale
    "%-d.%-m.%y" (t2s datestring) :: Maybe UTCTime
isoformat = parseTimeM True defaultTimeLocale
    "%Y-%m-%d" (t2s datestring) :: Maybe UTCTime
...


with

parse format = parseTimeM True defaultTimeLocale format (t2s datestring)

germanNumeralShort = parse "%-d.%-m.%y"
isoformat          = parse "%Y-%m-%d"
...


then we immediately notice that we use parse on all formats after another till we find a suitable one.

This can be modelled with map parse, e.g.

map parse
    [ "%b %-d, %Y"
    , "%B %-d, %Y"
    , "%b. %-d, %Y"
    , "%-d.%-m.%Y"
    , "%-d.%-m.%y"
    , "%Y-%m-%d"
    ]


We could use &lt;|&gt; alternatively, e.g.

parse "%b %-d, %Y" &lt;|&gt; parse "%B %-d, %Y" &lt;|&gt; ...


but that's less flexible than the list approach.

Use asum to get a single Maybe from a list of Maybes

To get a single Maybe a from [Maybe a], we can use asum. To get the errorT, we just need to pattern match on a single result and end up with

readDate3 :: Text -&gt;   UTCTime
readDate3 datestring = case result of
   Nohing -&gt; errorT ["readDate3", datestring, "is not parsed"]
   Just t -&gt; t
 where 
   parse format = parseTimeM True defaultTimeLocale format (t2s datestring) 
   result = asum . map parse $
        [ "%b %-d, %Y"
        , "%B %-d, %Y"
        , "%b. %-d, %Y"
        , "%-d.%-m.%Y"
        , "%-d.%-m.%y"
        , "%Y-%m-%d"
        ]


As the current strings are missing some documentation, we could introduce additional types to remedy that:

data DateFormat = ShortMonth
                | LongMonth
                | MonthPoint
                | GermanNumeral
                | GermanNumeralShort
                | ISOFormat

toFormatString :: DateFormat -&gt; String
toFormatString f = case f of
    ShortMonth -&gt; "%b %-d, %Y"
    LongMonth  -&gt; "%B %-d, %Y"
    MonthPoint -&gt; "%b. %-d, %Y"
    -- other left as an exercise


We can also use fromMaybe to get rid of the last pattern match and end up with

import Data.Foldable (asum)
import Data.Maybe (fromMaybe)

readDate :: Text -&gt;   UTCTime
readDate datestring = 
    fromMaybe (errorT ["readDate", datestring, "is not parsed"]) $
      asum . map parse $
        [ ShortMonth
        , LongMonth
        , MonthPoint
        , GermanNumeral
        , GermanNumeralShort
        , ISOFormat
        ]
 where 
   parse format = parseTimeM True defaultTimeLocale (toFormatString format) (t2s datestring)