packages feed

time-1.16: lib/Data/Time/Format/Parse/Instances.hs

{-# LANGUAGE Safe #-}

{-# OPTIONS -fno-warn-orphans #-}

module Data.Time.Format.Parse.Instances (

) where

import Control.Applicative ((<|>))
import Data.Char
import Data.Fixed
import Data.List (elemIndex, find)
import Data.Maybe
import Data.Ratio
import Data.Time.Calendar.CalendarDiffDays
import Data.Time.Calendar.Days
import Data.Time.Calendar.Gregorian
import Data.Time.Calendar.Month
import Data.Time.Calendar.OrdinalDate
import Data.Time.Calendar.Private (clipValid)
import Data.Time.Calendar.Quarter
import Data.Time.Calendar.Types
import Data.Time.Calendar.WeekDate
import Data.Time.Clock.Internal.DiffTime
import Data.Time.Clock.Internal.NominalDiffTime
import Data.Time.Clock.Internal.UTCTime
import Data.Time.Clock.Internal.UniversalTime
import Data.Time.Clock.POSIX
import Data.Time.Format.Locale
import Data.Time.Format.Parse.Class
import Data.Time.LocalTime.Internal.CalendarDiffTime
import Data.Time.LocalTime.Internal.LocalTime
import Data.Time.LocalTime.Internal.TimeOfDay
import Data.Time.LocalTime.Internal.TimeZone
import Data.Time.LocalTime.Internal.ZonedTime
import Data.Traversable
import Text.Read (readMaybe)

data WeekType
    = ISOWeek
    | SundayWeek
    | MondayWeek
    deriving Eq

mkDayFromWeekType :: WeekType -> Year -> WeekOfYear -> DayOfWeek -> Maybe Day
mkDayFromWeekType wt y woy dow =
    case wt of
        ISOWeek -> fromWeekDateValid y woy $ fromEnum dow
        SundayWeek -> fromSundayStartWeekValid y woy $ mod (fromEnum dow) 7
        MondayWeek -> fromMondayStartWeekValid y woy $ fromEnum dow

data DayFact
    = CenturyDayFact Integer -- century of all years
    | YearOfCenturyDayFact Integer -- 0-99, last two digits of both real years and week years
    | QuarterOfYearDayFact QuarterOfYear
    | MonthOfYearDayFact MonthOfYear -- 1-12
    | DayOfMonthDayFact DayOfMonth -- 1-31
    | DayOfYearDayFact DayOfYear -- 1-366
    | DayOfWeekDayFact DayOfWeek
    | WeekOfYearDayFact
        WeekType
        WeekOfYear -- 1-53 or 0-53
    | UTCTimeDayFact UTCTime
    | TimeZoneDayFact TimeZone

lastMatch :: (a -> Maybe b) -> [a] -> Maybe b
lastMatch f aa = listToMaybe $ reverse $ catMaybes $ fmap f aa

dayFactGetCentury :: [DayFact] -> Maybe Integer
dayFactGetCentury = lastMatch $ \case
    CenturyDayFact x -> Just x
    _ -> Nothing

dayFactGetYearOfCentury :: [DayFact] -> Maybe Integer
dayFactGetYearOfCentury = lastMatch $ \case
    YearOfCenturyDayFact x -> Just x
    _ -> Nothing

dayFactGetQuarterOfYear :: [DayFact] -> Maybe QuarterOfYear
dayFactGetQuarterOfYear = lastMatch $ \case
    QuarterOfYearDayFact x -> Just x
    _ -> Nothing

dayFactGetMonthOfYear :: [DayFact] -> Maybe MonthOfYear
dayFactGetMonthOfYear = lastMatch $ \case
    MonthOfYearDayFact x -> Just x
    _ -> Nothing

dayFactGetDayOfMonth :: [DayFact] -> Maybe DayOfMonth
dayFactGetDayOfMonth = lastMatch $ \case
    DayOfMonthDayFact x -> Just x
    _ -> Nothing

dayFactGetDayOfYear :: [DayFact] -> Maybe DayOfYear
dayFactGetDayOfYear = lastMatch $ \case
    DayOfYearDayFact x -> Just x
    _ -> Nothing

dayFactGetDayOfWeek :: [DayFact] -> Maybe DayOfWeek
dayFactGetDayOfWeek = lastMatch $ \case
    DayOfWeekDayFact x -> Just x
    _ -> Nothing

dayFactGetWeekOfYear :: [DayFact] -> Maybe (WeekType, WeekOfYear)
dayFactGetWeekOfYear = lastMatch $ \case
    WeekOfYearDayFact wt x -> Just (wt, x)
    _ -> Nothing

dayFactGetUTCTime :: [DayFact] -> Maybe UTCTime
dayFactGetUTCTime = lastMatch $ \case
    UTCTimeDayFact x -> Just x
    _ -> Nothing

dayFactGetTimeZone :: [DayFact] -> Maybe TimeZone
dayFactGetTimeZone = lastMatch $ \case
    TimeZoneDayFact x -> Just x
    _ -> Nothing

readSpec_z :: String -> Maybe Int
readSpec_z = readTzOffset

readSpec_Z :: TimeLocale -> String -> Maybe TimeZone
readSpec_Z _ str | Just offset <- readTzOffset str = Just $ TimeZone offset False ""
readSpec_Z l str | Just zone <- getKnownTimeZone l str = Just zone
readSpec_Z _ "UTC" = Just utc
readSpec_Z _ [c] | Just zone <- getMilZone c = Just zone
readSpec_Z _ _ = Nothing

makeDayFact :: TimeLocale -> Char -> String -> Maybe [DayFact]
makeDayFact l c x =
    let
        ra :: Read a => Maybe a
        ra = readMaybe x
        zeroBasedListIndex :: [String] -> Maybe Int
        zeroBasedListIndex ss = elemIndex (map toUpper x) $ fmap (map toUpper) ss
        oneBasedListIndex :: [String] -> Maybe Int
        oneBasedListIndex ss = do
            index <- zeroBasedListIndex ss
            return $ 1 + index
    in
        case c of
            -- %C: century (all but the last two digits of the year), 00 - 99
            'C' -> do
                a <- ra
                return [CenturyDayFact a]
            -- %f century (all but the last two digits of the year), 00 - 99
            'f' -> do
                a <- ra
                return [CenturyDayFact a]
            -- %Y: year
            'Y' -> do
                a <- ra
                return [CenturyDayFact (a `div` 100), YearOfCenturyDayFact (a `mod` 100)]
            -- %G: year for Week Date format
            'G' -> do
                a <- ra
                return [CenturyDayFact (a `div` 100), YearOfCenturyDayFact (a `mod` 100)]
            -- %y: last two digits of year, 00 - 99
            'y' -> do
                a <- ra
                return [YearOfCenturyDayFact a]
            -- %g: last two digits of year for Week Date format, 00 - 99
            'g' -> do
                a <- ra
                return [YearOfCenturyDayFact a]
            -- %v: quarter of year, 1 - 4
            'v' -> do
                raw <- ra
                a <- clipValid 1 4 raw
                return [QuarterOfYearDayFact $ toEnum a]
            -- %B: month name, long form (fst from months locale), January - December
            'B' -> do
                a <- oneBasedListIndex $ fmap fst $ months l
                return [MonthOfYearDayFact a]
            -- %b: month name, short form (snd from months locale), Jan - Dec
            'b' -> do
                a <- oneBasedListIndex $ fmap snd $ months l
                return [MonthOfYearDayFact a]
            -- %m: month of year, leading 0 as needed, 01 - 12
            'm' -> do
                raw <- ra
                a <- clipValid 1 12 raw
                return [MonthOfYearDayFact a]
            -- %d: day of month, leading 0 as needed, 01 - 31
            'd' -> do
                raw <- ra
                a <- clipValid 1 31 raw
                return [DayOfMonthDayFact a]
            -- %e: day of month, leading space as needed, 1 - 31
            'e' -> do
                raw <- ra
                a <- clipValid 1 31 raw
                return [DayOfMonthDayFact a]
            -- %V: week for Week Date format, 01 - 53
            'V' -> do
                raw <- ra
                a <- clipValid 1 53 raw
                return [WeekOfYearDayFact ISOWeek a]
            -- %U: week number of year, where weeks start on Sunday (as sundayStartWeek), 00 - 53
            'U' -> do
                raw <- ra
                a <- clipValid 0 53 raw
                return [WeekOfYearDayFact SundayWeek a]
            -- %W: week number of year, where weeks start on Monday (as mondayStartWeek), 00 - 53
            'W' -> do
                raw <- ra
                a <- clipValid 0 53 raw
                return [WeekOfYearDayFact MondayWeek a]
            -- %u: day for Week Date format, 1 - 7
            'u' -> do
                raw <- ra
                a <- clipValid 1 7 raw
                return [DayOfWeekDayFact $ toEnum a]
            -- %a: day of week, short form (snd from wDays locale), Sun - Sat
            'a' -> do
                a <- zeroBasedListIndex $ fmap snd $ wDays l
                return [DayOfWeekDayFact $ toEnum a]
            -- %A: day of week, long form (fst from wDays locale), Sunday - Saturday
            'A' -> do
                a <- zeroBasedListIndex $ fmap fst $ wDays l
                return [DayOfWeekDayFact $ toEnum a]
            -- %w: day of week number, 0 (= Sunday) - 6 (= Saturday)
            'w' -> do
                raw <- ra
                a <- clipValid 0 6 raw
                return [DayOfWeekDayFact $ toEnum a]
            -- %j: day of year for Ordinal Date format, 001 - 366
            'j' -> do
                raw <- ra
                a <- clipValid 1 366 raw
                return [DayOfYearDayFact a]
            -- %s: number of whole seconds since the Unix epoch.
            's' -> do
                raw <- ra
                return [UTCTimeDayFact $ posixSecondsToUTCTime $ fromInteger raw]
            'z' -> do
                a <- readSpec_z x
                return [TimeZoneDayFact $ TimeZone a False ""]
            'Z' -> do
                a <- readSpec_Z l x
                return [TimeZoneDayFact a]
            -- unrecognised, pass on to other parsers
            _ -> return []

makeDayFacts :: TimeLocale -> [(Char, String)] -> Maybe [DayFact]
makeDayFacts l pairs = do
    factss <- for pairs $ \(c, x) -> makeDayFact l c x
    return $ mconcat factss

dayFactYear :: [DayFact] -> Integer
dayFactYear facts =
    let
        d = fromMaybe 70 $ dayFactGetYearOfCentury facts
        c =
            fromMaybe
                ( if d >= 69
                    then 19
                    else 20
                )
                $ dayFactGetCentury facts
    in
        100 * c + d

dayFactDay :: [DayFact] -> Maybe Day
dayFactDay facts =
    case dayFactYear facts of
        y | Just doy <- dayFactGetDayOfYear facts -> fromOrdinalDateValid y doy
        y
            | Just moy <- dayFactGetMonthOfYear facts ->
                let
                    dom = fromMaybe 1 $ dayFactGetDayOfMonth facts
                in
                    fromGregorianValid y moy dom
        y
            | Just (wt, woy) <- dayFactGetWeekOfYear facts ->
                let
                    dow = fromMaybe Thursday $ dayFactGetDayOfWeek facts
                in
                    mkDayFromWeekType wt y woy dow
        y
            | Just qoy <- dayFactGetQuarterOfYear facts ->
                let
                    moy = case qoy of
                        Q1 -> 1
                        Q2 -> 4
                        Q3 -> 7
                        Q4 -> 10
                    dom = fromMaybe 1 $ dayFactGetDayOfMonth facts
                in
                    fromGregorianValid y moy dom
        _
            | Just ut <- dayFactGetUTCTime facts ->
                let
                    tz = fromMaybe utc $ dayFactGetTimeZone facts
                in
                    Just $ localDay $ utcToLocalTime tz ut
        y | Just dom <- dayFactGetDayOfMonth facts -> fromGregorianValid y 1 dom
        y | Just dow <- dayFactGetDayOfWeek facts -> fromWeekDateValid y 1 $ fromEnum dow
        y -> fromOrdinalDateValid y 1

instance ParseTime Day where
    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
    parseTimeSpecifier _ = timeParseTimeSpecifier
    buildTime l pairs = do
        facts <- makeDayFacts l pairs
        dayFactDay facts

instance ParseTime DayOfWeek where
    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
    parseTimeSpecifier _ = timeParseTimeSpecifier
    buildTime l pairs = do
        facts <- makeDayFacts l pairs
        dayFactGetDayOfWeek facts
            <|> (fmap dayOfWeek $ dayFactDay facts)

instance ParseTime Month where
    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
    parseTimeSpecifier _ = timeParseTimeSpecifier
    buildTime l pairs = do
        facts <- makeDayFacts l pairs
        case dayFactGetMonthOfYear facts of
            Just moy ->
                let
                    y = dayFactYear facts
                in
                    Just $ YearMonth y moy
            Nothing -> fmap dayPeriod $ dayFactDay facts

instance ParseTime QuarterOfYear where
    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
    parseTimeSpecifier _ = timeParseTimeSpecifier
    buildTime l pairs = do
        facts <- makeDayFacts l pairs
        case dayFactGetQuarterOfYear facts of
            Just qoy -> Just qoy
            Nothing -> do
                QuarterDay (YearQuarter _ qoy) _ <- dayFactDay facts
                return qoy

instance ParseTime Quarter where
    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
    parseTimeSpecifier _ = timeParseTimeSpecifier
    buildTime l pairs = do
        facts <- makeDayFacts l pairs
        case dayFactGetQuarterOfYear facts of
            Just qoy ->
                let
                    y = dayFactYear facts
                in
                    Just $ YearQuarter y qoy
            Nothing -> fmap dayPeriod $ dayFactDay facts

mfoldl :: Monad m => (a -> b -> m a) -> m a -> [b] -> m a
mfoldl f =
    let
        mf ma b = do
            a <- ma
            f a b
    in
        foldl mf

data AMPM = AM | PM

data TimeFact
    = AMAPMTimeFact AMPM
    | HourTimeFact Int
    | MinuteTimeFact Int
    | WholeSecondTimeFact Int
    | FractSecondTimeFact Pico
    | UTCTimeFact UTCTime
    | ZoneTimeFact TimeZone

timeFactGetAMPM :: [TimeFact] -> Maybe AMPM
timeFactGetAMPM = lastMatch $ \case
    AMAPMTimeFact x -> Just x
    _ -> Nothing

timeFactGetHour :: [TimeFact] -> Maybe Int
timeFactGetHour = lastMatch $ \case
    HourTimeFact x -> Just x
    _ -> Nothing

timeFactGetMinute :: [TimeFact] -> Maybe Int
timeFactGetMinute = lastMatch $ \case
    MinuteTimeFact x -> Just x
    _ -> Nothing

timeFactGetWholeSecond :: [TimeFact] -> Maybe Int
timeFactGetWholeSecond = lastMatch $ \case
    WholeSecondTimeFact x -> Just x
    _ -> Nothing

timeFactGetFractSecond :: [TimeFact] -> Maybe Pico
timeFactGetFractSecond = lastMatch $ \case
    FractSecondTimeFact x -> Just x
    _ -> Nothing

timeFactGetUTC :: [TimeFact] -> Maybe UTCTime
timeFactGetUTC = lastMatch $ \case
    UTCTimeFact x -> Just x
    _ -> Nothing

timeFactGetZone :: [TimeFact] -> Maybe TimeZone
timeFactGetZone = lastMatch $ \case
    ZoneTimeFact x -> Just x
    _ -> Nothing

makeTimeFact :: TimeLocale -> Char -> String -> Maybe [TimeFact]
makeTimeFact l c x =
    let
        ra :: Read a => Maybe a
        ra = readMaybe x
        getAmPm =
            let
                upx = map toUpper x
                (amStr, pmStr) = amPm l
            in
                if upx == amStr
                    then Just [AMAPMTimeFact AM]
                    else
                        if upx == pmStr
                            then Just [AMAPMTimeFact PM]
                            else Nothing
    in
        case c of
            'P' -> getAmPm
            'p' -> getAmPm
            'H' -> do
                raw <- ra
                a <- clipValid 0 23 raw
                return [HourTimeFact a]
            'I' -> do
                raw <- ra
                a <- clipValid 1 12 raw
                return [HourTimeFact a]
            'k' -> do
                raw <- ra
                a <- clipValid 0 23 raw
                return [HourTimeFact a]
            'l' -> do
                raw <- ra
                a <- clipValid 1 12 raw
                return [HourTimeFact a]
            'M' -> do
                raw <- ra
                a <- clipValid 0 59 raw
                return [MinuteTimeFact a]
            'S' -> do
                raw <- ra
                a <- clipValid 0 60 raw
                return [WholeSecondTimeFact $ fromInteger a]
            'q' -> do
                ps <- (readMaybe $ take 12 $ rpad 12 '0' x) <|> return 0
                return [FractSecondTimeFact $ mkPico 0 ps]
            'Q' ->
                if null x
                    then return []
                    else do
                        ps <- (readMaybe $ take 12 $ rpad 12 '0' x) <|> return 0
                        return [FractSecondTimeFact $ mkPico 0 ps]
            's' -> do
                raw <- ra
                return [UTCTimeFact $ posixSecondsToUTCTime $ fromInteger raw]
            'z' -> do
                a <- readSpec_z x
                return [ZoneTimeFact $ TimeZone a False ""]
            'Z' -> do
                a <- readSpec_Z l x
                return [ZoneTimeFact a]
            _ -> return []

makeTimeFacts :: TimeLocale -> [(Char, String)] -> Maybe [TimeFact]
makeTimeFacts l pairs = do
    factss <- for pairs $ \(c, x) -> makeTimeFact l c x
    return $ mconcat factss

instance ParseTime TimeOfDay where
    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
    parseTimeSpecifier _ = timeParseTimeSpecifier
    buildTime l pairs = do
        facts <- makeTimeFacts l pairs
        -- 'Nothing' indicates a parse failure,
        -- while 'Just []' means no information
        case timeFactGetUTC facts of
            Just t ->
                let
                    zone = fromMaybe utc $ timeFactGetZone facts
                    sf = fromMaybe 0 $ timeFactGetFractSecond facts
                    TimeOfDay h m s = localTimeOfDay $ utcToLocalTime zone t
                in
                    return $ TimeOfDay h m $ s + sf
            Nothing ->
                let
                    h = fromMaybe 0 $ timeFactGetHour facts
                    m = fromMaybe 0 $ timeFactGetMinute facts
                    si = fromMaybe 0 $ timeFactGetWholeSecond facts
                    sf = fromMaybe 0 $ timeFactGetFractSecond facts
                    s :: Pico
                    s = fromIntegral si + sf
                    h' = case timeFactGetAMPM facts of
                        Nothing -> h
                        Just AM -> mod h 12
                        Just PM -> if h < 12 then h + 12 else h
                in
                    return $ TimeOfDay h' m s

rpad :: Int -> a -> [a] -> [a]
rpad n c xs = xs ++ replicate (n - length xs) c

mkPico :: Integer -> Integer -> Pico
mkPico i f = fromInteger i + fromRational (f % 1000000000000)

instance ParseTime LocalTime where
    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
    parseTimeSpecifier _ = timeParseTimeSpecifier
    buildTime l xs = LocalTime <$> (buildTime l xs) <*> (buildTime l xs)

enumDiff :: Enum a => a -> a -> Int
enumDiff a b = (fromEnum a) - (fromEnum b)

getMilZoneHours :: Char -> Maybe Int
getMilZoneHours c
    | c < 'A' = Nothing
getMilZoneHours c
    | c <= 'I' = Just $ 1 + enumDiff c 'A'
getMilZoneHours 'J' = Nothing
getMilZoneHours c
    | c <= 'M' = Just $ 10 + enumDiff c 'K'
getMilZoneHours c
    | c <= 'Y' = Just $ (enumDiff 'N' c) - 1
getMilZoneHours 'Z' = Just 0
getMilZoneHours _ = Nothing

getMilZone :: Char -> Maybe TimeZone
getMilZone c =
    let
        yc = toUpper c
    in
        do
            hours <- getMilZoneHours yc
            return $ TimeZone (hours * 60) False [yc]

getKnownTimeZone :: TimeLocale -> String -> Maybe TimeZone
getKnownTimeZone locale x = find (\tz -> map toUpper x == timeZoneName tz) (knownTimeZones locale)

instance ParseTime TimeZone where
    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
    parseTimeSpecifier _ = timeParseTimeSpecifier
    buildTime l =
        let
            f :: Char -> String -> TimeZone -> Maybe TimeZone
            f 'z' str (TimeZone _ dst name) = do
                offset <- readSpec_z str
                return $ TimeZone offset dst name
            f 'Z' str _ = readSpec_Z l str
            f _ _ tz = Just tz
        in
            foldl (\mt (c, s) -> mt >>= f c s) (Just $ minutesToTimeZone 0)

readTzOffset :: String -> Maybe Int
readTzOffset str =
    let
        getSign '+' = Just 1
        getSign '-' = Just (-1)
        getSign _ = Nothing
        calc s h1 h2 m1 m2 = do
            sign <- getSign s
            h <- readMaybe [h1, h2]
            m <- readMaybe [m1, m2]
            return $ sign * (60 * h + m)
    in
        case str of
            (s : h1 : h2 : ':' : m1 : m2 : []) -> calc s h1 h2 m1 m2
            (s : h1 : h2 : m1 : m2 : []) -> calc s h1 h2 m1 m2
            _ -> Nothing

instance ParseTime ZonedTime where
    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
    parseTimeSpecifier _ = timeParseTimeSpecifier
    buildTime l xs =
        let
            f (ZonedTime (LocalTime _ tod) z) ('s', x) = do
                a <- readMaybe x
                let
                    s = fromInteger a
                    (_, ps) = properFraction (todSec tod) :: (Integer, Pico)
                    s' = s + fromRational (toRational ps)
                return $ utcToZonedTime z (posixSecondsToUTCTime s')
            f t _ = Just t
        in
            mfoldl f (ZonedTime <$> (buildTime l xs) <*> (buildTime l xs)) xs

instance ParseTime UTCTime where
    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
    parseTimeSpecifier _ = timeParseTimeSpecifier
    buildTime l xs = zonedTimeToUTC <$> buildTime l xs

instance ParseTime UniversalTime where
    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
    parseTimeSpecifier _ = timeParseTimeSpecifier
    buildTime l xs = localTimeToUT1 0 <$> buildTime l xs

--- Duration

buildTimeMonths :: [(Char, String)] -> Maybe Integer
buildTimeMonths xs = do
    tt <-
        for xs $ \(c, s) ->
            case c of
                'y' -> fmap ((*) 12) $ readMaybe s
                'b' -> readMaybe s
                'B' -> readMaybe s
                _ -> return 0
    return $ sum tt

buildTimeDays :: [(Char, String)] -> Maybe Integer
buildTimeDays xs = do
    tt <-
        for xs $ \(c, s) ->
            case c of
                'w' -> fmap ((*) 7) $ readMaybe s
                'd' -> readMaybe s
                'D' -> readMaybe s
                _ -> return 0
    return $ sum tt

buildTimeSeconds :: [(Char, String)] -> Maybe Pico
buildTimeSeconds xs = do
    tt <- for xs $ \(c, s) ->
        let
            readInt :: Integer -> Maybe Pico
            readInt t = do
                i <- readMaybe s
                return $ fromInteger $ i * t
        in
            case c of
                'h' -> readInt 3600
                'H' -> readInt 3600
                'm' -> readInt 60
                'M' -> readInt 60
                's' -> readMaybe s
                'S' -> readMaybe s
                _ -> return 0
    return $ sum tt

instance ParseTime NominalDiffTime where
    parseTimeSpecifier _ = durationParseTimeSpecifier
    buildTime _ xs = do
        dd <- buildTimeDays xs
        tt <- buildTimeSeconds xs
        return $ (fromInteger dd * 86400) + realToFrac tt

instance ParseTime DiffTime where
    parseTimeSpecifier _ = durationParseTimeSpecifier
    buildTime _ xs = do
        dd <- buildTimeDays xs
        tt <- buildTimeSeconds xs
        return $ (fromInteger dd * 86400) + realToFrac tt

instance ParseTime CalendarDiffDays where
    parseTimeSpecifier _ = durationParseTimeSpecifier
    buildTime _ xs = do
        mm <- buildTimeMonths xs
        dd <- buildTimeDays xs
        return $ CalendarDiffDays mm dd

instance ParseTime CalendarDiffTime where
    parseTimeSpecifier _ = durationParseTimeSpecifier
    buildTime locale xs = do
        mm <- buildTimeMonths xs
        tt <- buildTime locale xs
        return $ CalendarDiffTime mm tt