{-# LANGUAGE CPP #-}
module Data.Time.Format.ISO8601.Compat (
Format,
formatShowM,
formatShow,
formatReadP,
formatParseM,
ISO8601(..),
iso8601Show,
iso8601ParseM,
FormatExtension(..),
formatReadPExtension,
parseFormatExtension,
calendarFormat,
yearMonthFormat,
yearFormat,
centuryFormat,
expandedCalendarFormat,
expandedYearMonthFormat,
expandedYearFormat,
expandedCenturyFormat,
ordinalDateFormat,
expandedOrdinalDateFormat,
weekDateFormat,
yearWeekFormat,
expandedWeekDateFormat,
expandedYearWeekFormat,
timeOfDayFormat,
hourMinuteFormat,
hourFormat,
withTimeDesignator,
withUTCDesignator,
timeOffsetFormat,
timeOfDayAndOffsetFormat,
localTimeFormat,
zonedTimeFormat,
utcTimeFormat,
dayAndTimeFormat,
timeAndOffsetFormat,
durationDaysFormat,
durationTimeFormat,
alternativeDurationDaysFormat,
alternativeDurationTimeFormat,
intervalFormat,
recurringIntervalFormat,
) where
import Data.Time.Orphans ()
#if MIN_VERSION_time(1,9,0)
import Data.Time.Format.ISO8601
#else
import Control.Monad.Fail
import Prelude hiding (fail)
import Data.Monoid
import Data.Ratio
import Data.Fixed
import Text.ParserCombinators.ReadP
import Data.Format
import Data.Time
import Data.Time.Calendar.Compat
import Data.Time.Calendar.OrdinalDate.Compat
import Data.Time.Calendar.WeekDate.Compat
import Data.Time.LocalTime.Compat
import Data.Time.Calendar.Private
data FormatExtension =
ExtendedFormat |
BasicFormat
formatReadPExtension :: (FormatExtension -> Format t) -> ReadP t
formatReadPExtension ff = formatReadP (ff ExtendedFormat) +++ formatReadP (ff BasicFormat)
parseFormatExtension :: (
#if MIN_VERSION_base(4,9,0)
MonadFail m
#else
Monad m
#endif
) => (FormatExtension -> Format t) -> String -> m t
parseFormatExtension ff = parseReader $ formatReadPExtension ff
sepFormat :: String -> Format a -> Format b -> Format (a,b)
sepFormat sep fa fb = (fa <** literalFormat sep) <**> fb
dashFormat :: Format a -> Format b -> Format (a,b)
dashFormat = sepFormat "-"
colnFormat :: Format a -> Format b -> Format (a,b)
colnFormat = sepFormat ":"
extDashFormat :: FormatExtension -> Format a -> Format b -> Format (a,b)
extDashFormat ExtendedFormat = dashFormat
extDashFormat BasicFormat = (<**>)
extColonFormat :: FormatExtension -> Format a -> Format b -> Format (a,b)
extColonFormat ExtendedFormat = colnFormat
extColonFormat BasicFormat = (<**>)
expandedYearFormat' :: Int -> Format Integer
expandedYearFormat' n = integerFormat PosNegSign (Just n)
yearFormat' :: Format Integer
yearFormat' = integerFormat NegSign (Just 4)
monthFormat :: Format Int
monthFormat = integerFormat NoSign (Just 2)
dayOfMonthFormat :: Format Int
dayOfMonthFormat = integerFormat NoSign (Just 2)
dayOfYearFormat :: Format Int
dayOfYearFormat = integerFormat NoSign (Just 3)
weekOfYearFormat :: Format Int
weekOfYearFormat = literalFormat "W" **> integerFormat NoSign (Just 2)
dayOfWeekFormat :: Format Int
dayOfWeekFormat = integerFormat NoSign (Just 1)
hourFormat' :: Format Int
hourFormat' = integerFormat NoSign (Just 2)
data E14
instance HasResolution E14 where
resolution _ = 100000000000000
data E16
instance HasResolution E16 where
resolution _ = 10000000000000000
hourDecimalFormat :: Format (Fixed E16)
hourDecimalFormat = decimalFormat NoSign (Just 2)
minuteFormat :: Format Int
minuteFormat = integerFormat NoSign (Just 2)
minuteDecimalFormat :: Format (Fixed E14)
minuteDecimalFormat = decimalFormat NoSign (Just 2)
secondFormat :: Format Pico
secondFormat = decimalFormat NoSign (Just 2)
mapGregorian :: Format (Integer,(Int,Int)) -> Format Day
mapGregorian = mapMFormat (\(y,(m,d)) -> fromGregorianValid y m d) (\day -> (\(y,m,d) -> Just (y,(m,d))) $ toGregorian day)
mapOrdinalDate :: Format (Integer,Int) -> Format Day
mapOrdinalDate = mapMFormat (\(y,d) -> fromOrdinalDateValid y d) (Just . toOrdinalDate)
mapWeekDate :: Format (Integer,(Int,Int)) -> Format Day
mapWeekDate = mapMFormat (\(y,(w,d)) -> fromWeekDateValid y w d) (\day -> (\(y,w,d) -> Just (y,(w,d))) $ toWeekDate day)
mapTimeOfDay :: Format (Int,(Int,Pico)) -> Format TimeOfDay
mapTimeOfDay = mapMFormat (\(h,(m,s)) -> makeTimeOfDayValid h m s) (\(TimeOfDay h m s) -> Just (h,(m,s)))
calendarFormat :: FormatExtension -> Format Day
calendarFormat fe = mapGregorian $ extDashFormat fe yearFormat $ extDashFormat fe monthFormat dayOfMonthFormat
yearMonthFormat :: Format (Integer,Int)
yearMonthFormat = yearFormat <**> literalFormat "-" **> monthFormat
yearFormat :: Format Integer
yearFormat = yearFormat'
centuryFormat :: Format Integer
centuryFormat = integerFormat NegSign (Just 2)
expandedCalendarFormat :: Int -> FormatExtension -> Format Day
expandedCalendarFormat n fe = mapGregorian $ extDashFormat fe (expandedYearFormat n) $ extDashFormat fe monthFormat dayOfMonthFormat
expandedYearMonthFormat :: Int -> Format (Integer,Int)
expandedYearMonthFormat n = dashFormat (expandedYearFormat n) monthFormat
expandedYearFormat :: Int -> Format Integer
expandedYearFormat = expandedYearFormat'
expandedCenturyFormat :: Int -> Format Integer
expandedCenturyFormat n = integerFormat PosNegSign (Just n)
ordinalDateFormat :: FormatExtension -> Format Day
ordinalDateFormat fe = mapOrdinalDate $ extDashFormat fe yearFormat dayOfYearFormat
expandedOrdinalDateFormat :: Int -> FormatExtension -> Format Day
expandedOrdinalDateFormat n fe = mapOrdinalDate $ extDashFormat fe (expandedYearFormat n) dayOfYearFormat
weekDateFormat :: FormatExtension -> Format Day
weekDateFormat fe = mapWeekDate $ extDashFormat fe yearFormat $ extDashFormat fe weekOfYearFormat dayOfWeekFormat
yearWeekFormat :: FormatExtension -> Format (Integer,Int)
yearWeekFormat fe = extDashFormat fe yearFormat weekOfYearFormat
expandedWeekDateFormat :: Int -> FormatExtension -> Format Day
expandedWeekDateFormat n fe = mapWeekDate $ extDashFormat fe (expandedYearFormat n) $ extDashFormat fe weekOfYearFormat dayOfWeekFormat
expandedYearWeekFormat :: Int -> FormatExtension -> Format (Integer,Int)
expandedYearWeekFormat n fe = extDashFormat fe (expandedYearFormat n) weekOfYearFormat
timeOfDayFormat :: FormatExtension -> Format TimeOfDay
timeOfDayFormat fe = mapTimeOfDay $ extColonFormat fe hourFormat' $ extColonFormat fe minuteFormat secondFormat
fromRationalRound :: Rational -> NominalDiffTime
fromRationalRound r = fromRational $ round (r * 1000000000000) % 1000000000000
hourMinuteFormat :: FormatExtension -> Format TimeOfDay
hourMinuteFormat fe = let
toTOD (h,m) = case timeToDaysAndTimeOfDay $ fromRationalRound $ toRational $ (fromIntegral h) * 3600 + m * 60 of
(0,tod) -> Just tod
_ -> Nothing
fromTOD tod = let
mm = (realToFrac $ daysAndTimeOfDayToTime 0 tod) / 60
in Just $ quotRemBy 60 mm
in mapMFormat toTOD fromTOD $ extColonFormat fe hourFormat' $ minuteDecimalFormat
hourFormat :: Format TimeOfDay
hourFormat = let
toTOD h = case timeToDaysAndTimeOfDay $ fromRationalRound $ toRational $ h * 3600 of
(0,tod) -> Just tod
_ -> Nothing
fromTOD tod = Just $ (realToFrac $ daysAndTimeOfDayToTime 0 tod) / 3600
in mapMFormat toTOD fromTOD $ hourDecimalFormat
withTimeDesignator :: Format t -> Format t
withTimeDesignator f = literalFormat "T" **> f
withUTCDesignator :: Format t -> Format t
withUTCDesignator f = f <** literalFormat "Z"
timeOffsetFormat :: FormatExtension -> Format TimeZone
timeOffsetFormat fe = let
toTimeZone (sign,(h,m)) = minutesToTimeZone $ sign * (h * 60 + m)
fromTimeZone tz = let
mm = timeZoneMinutes tz
hm = quotRem (abs mm) 60
in (signum mm,hm)
in isoMap toTimeZone fromTimeZone $
mandatorySignFormat <**> extColonFormat fe (integerFormat NoSign (Just 2)) (integerFormat NoSign (Just 2))
timeOfDayAndOffsetFormat :: FormatExtension -> Format (TimeOfDay,TimeZone)
timeOfDayAndOffsetFormat fe = timeOfDayFormat fe <**> timeOffsetFormat fe
localTimeFormat :: Format Day -> Format TimeOfDay -> Format LocalTime
localTimeFormat fday ftod = isoMap (\(day,tod) -> LocalTime day tod) (\(LocalTime day tod) -> (day,tod)) $ fday <**> withTimeDesignator ftod
zonedTimeFormat :: Format Day -> Format TimeOfDay -> FormatExtension -> Format ZonedTime
zonedTimeFormat fday ftod fe = isoMap (\(lt,tz) -> ZonedTime lt tz) (\(ZonedTime lt tz) -> (lt,tz)) $ timeAndOffsetFormat (localTimeFormat fday ftod) fe
utcTimeFormat :: Format Day -> Format TimeOfDay -> Format UTCTime
utcTimeFormat fday ftod = isoMap (localTimeToUTC utc) (utcToLocalTime utc) $ withUTCDesignator $ localTimeFormat fday ftod
dayAndTimeFormat :: Format Day -> Format time -> Format (Day,time)
dayAndTimeFormat fday ft = fday <**> withTimeDesignator ft
timeAndOffsetFormat :: Format t -> FormatExtension -> Format (t,TimeZone)
timeAndOffsetFormat ft fe = ft <**> timeOffsetFormat fe
intDesignator :: (Eq t,Show t,Read t,Num t) => Char -> Format t
intDesignator c = optionalFormat 0 $ integerFormat NoSign Nothing <** literalFormat [c]
decDesignator :: (Eq t,Show t,Read t,Num t) => Char -> Format t
decDesignator c = optionalFormat 0 $ decimalFormat NoSign Nothing <** literalFormat [c]
daysDesigs :: Format CalendarDiffDays
daysDesigs = let
toCD (y,(m,(w,d))) = CalendarDiffDays (y * 12 + m) (w * 7 + d)
fromCD (CalendarDiffDays mm d) = (quot mm 12,(rem mm 12,(0,d)))
in isoMap toCD fromCD $
intDesignator 'Y' <**> intDesignator 'M' <**> intDesignator 'W' <**> intDesignator 'D'
durationDaysFormat :: Format CalendarDiffDays
durationDaysFormat = (**>) (literalFormat "P") $ specialCaseShowFormat (mempty,"0D") $ daysDesigs
durationTimeFormat :: Format CalendarDiffTime
durationTimeFormat = let
toCT (cd,(h,(m,s))) = mappend (calendarTimeDays cd) (calendarTimeTime $ daysAndTimeOfDayToTime 0 $ TimeOfDay h m s)
fromCT (CalendarDiffTime mm t) = let
(d,TimeOfDay h m s) = timeToDaysAndTimeOfDay t
in (CalendarDiffDays mm d,(h,(m,s)))
in (**>) (literalFormat "P") $ specialCaseShowFormat (mempty,"0D") $ isoMap toCT fromCT $
(<**>) daysDesigs $ optionalFormat (0,(0,0)) $ literalFormat "T" **> intDesignator 'H' <**> intDesignator 'M' <**> decDesignator 'S'
alternativeDurationDaysFormat :: FormatExtension -> Format CalendarDiffDays
alternativeDurationDaysFormat fe = let
toCD (y,(m,d)) = CalendarDiffDays (y * 12 + m) d
fromCD (CalendarDiffDays mm d) = (quot mm 12,(rem mm 12,d))
in isoMap toCD fromCD $ (**>) (literalFormat "P") $
extDashFormat fe (clipFormat (0,9999) $ integerFormat NegSign $ Just 4) $
extDashFormat fe (clipFormat (0,12) $ integerFormat NegSign $ Just 2) $
(clipFormat (0,30) $ integerFormat NegSign $ Just 2)
alternativeDurationTimeFormat :: FormatExtension -> Format CalendarDiffTime
alternativeDurationTimeFormat fe = let
toCT (cd,(h,(m,s))) = mappend (calendarTimeDays cd) (calendarTimeTime $ daysAndTimeOfDayToTime 0 $ TimeOfDay h m s)
fromCT (CalendarDiffTime mm t) = let
(d,TimeOfDay h m s) = timeToDaysAndTimeOfDay t
in (CalendarDiffDays mm d,(h,(m,s)))
in isoMap toCT fromCT $
(<**>) (alternativeDurationDaysFormat fe) $
withTimeDesignator $
extColonFormat fe (clipFormat (0,24) $ integerFormat NegSign (Just 2)) $
extColonFormat fe (clipFormat (0,60) $ integerFormat NegSign (Just 2)) $
(clipFormat (0,60) $ decimalFormat NegSign (Just 2))
intervalFormat :: Format a -> Format b -> Format (a,b)
intervalFormat = sepFormat "/"
recurringIntervalFormat :: Format a -> Format b -> Format (Int,a,b)
recurringIntervalFormat fa fb = isoMap (\(r,(a,b)) -> (r,a,b)) (\(r,a,b) -> (r,(a,b))) $ sepFormat "/" (literalFormat "R" **> integerFormat NoSign Nothing) $ intervalFormat fa fb
class ISO8601 t where
iso8601Format :: Format t
iso8601Show :: ISO8601 t => t -> String
iso8601Show = formatShow iso8601Format
iso8601ParseM :: (
#if MIN_VERSION_base(4,9,0)
MonadFail m
#else
Monad m
#endif
,ISO8601 t) => String -> m t
iso8601ParseM = formatParseM iso8601Format
instance ISO8601 Day where
iso8601Format = calendarFormat ExtendedFormat
instance ISO8601 TimeOfDay where
iso8601Format = timeOfDayFormat ExtendedFormat
instance ISO8601 TimeZone where
iso8601Format = timeOffsetFormat ExtendedFormat
instance ISO8601 LocalTime where
iso8601Format = localTimeFormat iso8601Format iso8601Format
instance ISO8601 ZonedTime where
iso8601Format = zonedTimeFormat iso8601Format iso8601Format ExtendedFormat
instance ISO8601 UTCTime where
iso8601Format = utcTimeFormat iso8601Format iso8601Format
instance ISO8601 CalendarDiffDays where
iso8601Format = durationDaysFormat
instance ISO8601 CalendarDiffTime where
iso8601Format = durationTimeFormat
#endif