{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
module Data.Time.ISO8601.Duration (
Duration (..)
, DurDate (..)
, DurTime (..)
, DurYear (..)
, DurMonth (..)
, DurWeek (..)
, DurDay (..)
, DurHour (..)
, DurMinute (..)
, DurSecond (..)
, parseDuration
, duration
, formatDuration
, formatDurationB
, addDuration
) where
import Control.Applicative
import Data.Attoparsec.ByteString.Char8
import Data.ByteString (ByteString)
import Data.ByteString.Builder (Builder, toLazyByteString)
import qualified Data.ByteString.Lazy as LBS
import Data.Monoid ((<>))
import Data.String (IsString, fromString)
import Data.Time hiding (formatTime)
newtype DurSecond = DurSecond Integer deriving (Eq, Ord, Show)
data DurMinute = DurMinute Integer (Maybe DurSecond) deriving (Eq, Ord, Show)
data DurHour = DurHour Integer (Maybe DurMinute) deriving (Eq, Ord, Show)
data DurTime = DurTimeHour DurHour
| DurTimeMinute DurMinute
| DurTimeSecond DurSecond deriving (Eq, Ord, Show)
newtype DurDay = DurDay Integer deriving (Eq, Ord, Show)
newtype DurWeek = DurWeek Integer deriving (Eq, Ord, Show)
data DurMonth = DurMonth Integer (Maybe DurDay) deriving (Eq, Ord, Show)
data DurYear = DurYear Integer (Maybe DurMonth) deriving (Eq, Ord, Show)
data DurDate = DurDateDay DurDay (Maybe DurTime)
| DurDateMonth DurMonth (Maybe DurTime)
| DurDateYear DurYear (Maybe DurTime) deriving (Eq, Ord, Show)
data Duration = DurationDate DurDate
| DurationTime DurTime
| DurationWeek DurWeek deriving (Eq, Ord, Show)
durSecond :: Parser DurSecond
durMinute :: Parser DurMinute
durHour :: Parser DurHour
durTime :: Parser DurTime
durDay :: Parser DurDay
durWeek :: Parser DurWeek
durMonth :: Parser DurMonth
durYear :: Parser DurYear
durDate :: Parser DurDate
duration :: Parser Duration
durSecond = DurSecond <$> (decimal <* char 'S')
durMinute = DurMinute <$> (decimal <* char 'M') <*> optional durSecond
durHour = DurHour <$> (decimal <* char 'H') <*> optional durMinute
durTime = char 'T' *> ((DurTimeHour <$> durHour) <|>
(DurTimeMinute <$> durMinute) <|>
(DurTimeSecond <$> durSecond))
durDay = DurDay <$> (decimal <* char 'D')
durWeek = DurWeek <$> (decimal <* char 'W')
durMonth = DurMonth <$> (decimal <* char 'M') <*> optional durDay
durYear = DurYear <$> (decimal <* char 'Y') <*> optional durMonth
durDate = (DurDateDay <$> durDay <*> optional durTime) <|>
(DurDateMonth <$> durMonth <*> optional durTime) <|>
(DurDateYear <$> durYear <*> optional durTime)
duration = char 'P' *> ((DurationDate <$> durDate) <|>
(DurationTime <$> durTime) <|>
(DurationWeek <$> durWeek))
parseDuration :: ByteString -> Either String Duration
parseDuration = parseOnly (duration <* endOfInput)
formatDuration :: Duration -> ByteString
formatDuration = runBuilder . formatDurationB
formatDurationB :: Duration -> Builder
formatDurationB dur = "P" <> case dur of
DurationDate date -> formatDate date
DurationTime time -> formatTime time
DurationWeek week -> formatWeek week
where
formatSecond (DurSecond second) = show' second <> "S"
formatMinute (DurMinute minute mbSecond) =
show' minute <> "M" <> maybe "" formatSecond mbSecond
formatHour (DurHour hour mbMinute) =
show' hour <> "H" <> maybe "" formatMinute mbMinute
formatTime time = "T" <> case time of
DurTimeSecond second -> formatSecond second
DurTimeMinute minute -> formatMinute minute
DurTimeHour hour -> formatHour hour
formatDay (DurDay day) = show' day <> "D"
formatWeek (DurWeek week) = show' week <> "W"
formatMonth (DurMonth month mbDay) =
show' month <> "M" <> maybe "" formatDay mbDay
formatYear (DurYear year mbMonth) =
show' year <> "Y" <> maybe "" formatMonth mbMonth
formatDate date = case date of
DurDateDay day mbTime -> formatDay day <> maybe "" formatTime mbTime
DurDateMonth month mbTime -> formatMonth month <> maybe "" formatTime mbTime
DurDateYear year mbTime -> formatYear year <> maybe "" formatTime mbTime
runBuilder :: Builder -> ByteString
runBuilder = LBS.toStrict . toLazyByteString
show' :: (Show a, IsString b) => a -> b
show' = fromString . show
addDuration :: Duration -> UTCTime -> UTCTime
addDuration (DurationDate s) = addDurationDate s
addDuration (DurationTime s) = addDurationTime s
addDuration (DurationWeek s) = addDurationWeek s
addDurationDate :: DurDate -> UTCTime -> UTCTime
addDurationDate (DurDateDay d dt) =
maybe id addDurationTime dt . addDurDay d
addDurationDate (DurDateMonth m dt) =
maybe id addDurationTime dt . addDurMonth m
addDurationDate (DurDateYear y dt) =
maybe id addDurationTime dt . addDurYear y
addDurDay :: DurDay -> UTCTime -> UTCTime
addDurDay (DurDay s) (UTCTime d dt) = UTCTime (addDays s d) dt
addDurMonth :: DurMonth -> UTCTime -> UTCTime
addDurMonth (DurMonth s m) (UTCTime d dt) =
maybe id addDurDay m $
UTCTime (addGregorianMonthsRollOver s d) dt
addDurYear :: DurYear -> UTCTime -> UTCTime
addDurYear (DurYear s m) (UTCTime d dt) =
maybe id addDurMonth m $
UTCTime (addGregorianYearsRollOver s d) dt
addDurationTime :: DurTime -> UTCTime -> UTCTime
addDurationTime = addUTCTime . durTimeToNDT
where
durTimeToNDT (DurTimeHour s) = durHourToNDT s
durTimeToNDT (DurTimeMinute s) = durMinuteToNDT s
durTimeToNDT (DurTimeSecond s) = durSecondToNDT s
durHourToNDT (DurHour s m) =
fromIntegral (s * 60 * 60) + maybe 0 durMinuteToNDT m
durMinuteToNDT (DurMinute s m) =
fromIntegral (s * 60) + maybe 0 durSecondToNDT m
durSecondToNDT (DurSecond s) = fromIntegral s
addDurationWeek :: DurWeek -> UTCTime -> UTCTime
addDurationWeek (DurWeek w) = addDurDay (DurDay (w*7))