date = expect [] . splitWhen (== ',') where
    expect parsers [] = return []
    expect parsers (pd:pds) = do
        parsed <- msum . sequence ([parseYMD, parseYM, parseY] ++ parsers) . splitWhen (== '-') $ pd
        liftM (parsed:) . ($ pds) $ case parsed of
            Year  {}                        -> expect []
            Month { year = y }              -> expect [parseMD y, parseM y]
            Day   { year = y, month = m }   -> expect [parseMD y, parseD y m]

    ensure p x = guard (p x) >> return x
    hasLength n xs = n >= 0 && hasLength' n xs where
        hasLength' n []     = n == 0
        hasLength' 0 (x:xs) = False
        hasLength' n (x:xs) = hasLength' (n-1) xs
    ensureLength = ensure . hasLength

    parseYMD    ss = ensureLength 3 ss >>= \[y, m, d] -> liftM3 Day   (checkY y) (checkMD m) (checkMD d)
    parseYM     ss = ensureLength 2 ss >>= \[y, m   ] -> liftM2 Month (checkY y) (checkMD m)
    parseY      ss = ensureLength 1 ss >>= \[y      ] -> liftM  Year  (checkY y)
    parseMD y   ss = ensureLength 2 ss >>= \[   m, d] -> liftM2 (Day y)          (checkMD m) (checkMD d)
    parseM  y   ss = ensureLength 1 ss >>= \[   m   ] -> liftM  (Month y)        (checkMD m)
    parseD  y m ss = ensureLength 1 ss >>= \[      d] -> liftM  (Day y m)                    (checkMD d)

    checkY  y  = ensureLength 4 y  >>= readM
    checkMD md = ensureLength 2 md >>= readM
    readM      = listToMaybe . map fst . filter (null . snd) . reads

-- |
-- Clip to a valid, representable date.  Years are clipped to the 0000-9999
-- range; months are clipped to the 1-12 range, and days are clipped to the
-- 1-\<number of days in the given month\> range (accounting for leap years in
-- the case of February).
--
-- If a parsed date is changed by this function, a warning is emitted.
date = expect [] . splitWhen (== ',') where
    expect parsers [] = return []
    expect parsers (pd:pds) = do
        parsed <- msum . sequence ([parseYMD, parseYM, parseY] ++ parsers) . splitWhen (== '-') $ pd
        liftM (parsed:) . ($ pds) $ case parsed of
            Year  {}                        -> expect []
            Month { year = y }              -> expect [parseMD y, parseM y]
            Day   { year = y, month = m }   -> expect [parseMD y, parseD y m]

    ensure p x = guard (p x) >> return x
    hasLength n xs = n >= 0 && hasLength' n xs where
        hasLength' n []     = n == 0
        hasLength' 0 (x:xs) = False
        hasLength' n (x:xs) = hasLength' (n-1) xs
    ensureLength = ensure . hasLength

    parseYMD    ss = ensureLength 3 ss >>= \[y, m, d] -> liftM3 Day   (checkY y) (checkMD m) (checkMD d)
    parseYM     ss = ensureLength 2 ss >>= \[y, m   ] -> liftM2 Month (checkY y) (checkMD m)
    parseY      ss = ensureLength 1 ss >>= \[y      ] -> liftM  Year  (checkY y)
    parseMD y   ss = ensureLength 2 ss >>= \[   m, d] -> liftM2 (Day y)          (checkMD m) (checkMD d)
    parseM  y   ss = ensureLength 1 ss >>= \[   m   ] -> liftM  (Month y)        (checkMD m)
    parseD  y m ss = ensureLength 1 ss >>= \[      d] -> liftM  (Day y m)                    (checkMD d)

    checkY  y  = ensureLength 4 y  >>= readM
    checkMD md = ensureLength 2 md >>= readM
    readM      = listToMaybe . map fst . filter (null . snd) . reads

-- |
-- Clip to a valid, representable date.  Years are clipped to the 0000-9999
-- range; months are clipped to the 1-12 range, and days are clipped to the
-- 1-\<number of days in the given month\> range (accounting for leap years in
-- the case of February).
--
-- If a parsed date is changed by this function, a warning is emitted.
date = expect [] . splitWhen (== ',') where
    expect parsers [] = return []
    expect parsers (pd:pds) = do
        parsed <- msum . sequence ([parseYMD, parseYM, parseY] ++ parsers) . splitWhen (== '-') $ pd
        liftM (parsed:) . ($ pds) $ case parsed of
            Year  {}                        -> expect []
            Month { year = y }              -> expect [parseMD y, parseM y]
            Day   { year = y, month = m }   -> expect [parseMD y, parseD y m]

    ensure p x = guard (p x) >> return x
    hasLength n xs = n >= 0 && hasLength' n xs where
        hasLength' n []     = n == 0
        hasLength' 0 (x:xs) = False
        hasLength' n (x:xs) = hasLength' (n-1) xs
    ensureLength = ensure . hasLength

    parseYMD    ss = ensureLength 3 ss >>= \[y, m, d] -> liftM3 Day   (checkY y) (checkMD m) (checkMD d)
    parseYM     ss = ensureLength 2 ss >>= \[y, m   ] -> liftM2 Month (checkY y) (checkMD m)
    parseY      ss = ensureLength 1 ss >>= \[y      ] -> liftM  Year  (checkY y)
    parseMD y   ss = ensureLength 2 ss >>= \[   m, d] -> liftM2 (Day y)          (checkMD m) (checkMD d)
    parseM  y   ss = ensureLength 1 ss >>= \[   m   ] -> liftM  (Month y)        (checkMD m)
    parseD  y m ss = ensureLength 1 ss >>= \[      d] -> liftM  (Day y m)                    (checkMD d)

    checkY  y  = ensureLength 4 y  >>= readM
    checkMD md = ensureLength 2 md >>= readM
    readM      = listToMaybe . map fst . filter (null . snd) . reads

-- |
-- Clip to a valid, representable date.  Years are clipped to the 0000-9999
-- range; months are clipped to the 1-12 range, and days are clipped to the
-- 1-\<number of days in the given month\> range (accounting for leap years in
-- the case of February).
--
-- If a parsed date is changed by this function, a warning is emitted.