module Data.Time.Format.ISO8601
(
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
#if MIN_VERSION_base(4,9,0)
import Control.Monad.Fail
import Prelude hiding (fail)
#endif
#if MIN_VERSION_base(4,8,0)
#else
import Data.Monoid
#endif
import Data.Ratio
import Data.Fixed
import Text.ParserCombinators.ReadP
import Data.Format
import Data.Time
import Data.Time.Calendar.OrdinalDate
import Data.Time.Calendar.WeekDate
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