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
import Control.Monad.Fail
import Data.Fixed
import Data.Format
import Data.Ratio
import Data.Time
import Data.Time.Calendar.OrdinalDate
import Data.Time.Calendar.Private
import Data.Time.Calendar.WeekDate
import Prelude hiding (fail)
import Text.ParserCombinators.ReadP
data FormatExtension
=
ExtendedFormat
| BasicFormat
formatReadPExtension :: (FormatExtension -> Format t) -> ReadP t
formatReadPExtension ff = formatReadP (ff ExtendedFormat) +++ formatReadP (ff BasicFormat)
parseFormatExtension :: (MonadFail m) => (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 :: (MonadFail m, 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