{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} module Data.Time.Calendar.Compat ( -- * Days Day(..),addDays,diffDays, -- * CalendarDiffTime CalendarDiffDays (..), calendarDay,calendarWeek,calendarMonth,calendarYear,scaleCalendarDiffDays, -- * Gregorian calendar toGregorian,fromGregorian,fromGregorianValid,showGregorian,gregorianMonthLength, -- calendrical arithmetic -- e.g. "one month after March 31st" addGregorianMonthsClip,addGregorianMonthsRollOver, addGregorianYearsClip,addGregorianYearsRollOver, addGregorianDurationClip,addGregorianDurationRollOver, diffGregorianDurationClip,diffGregorianDurationRollOver, -- re-exported from OrdinalDate isLeapYear , -- * Week DayOfWeek(..), dayOfWeek, ) where import Data.Time.Calendar import Data.Time.Format import Data.Time.Orphans () #if !MIN_VERSION_time(1,5,0) import System.Locale (TimeLocale (..)) #endif import Data.Data (Data, Typeable) import Data.Monoid (Monoid (..)) import Data.Semigroup (Semigroup (..)) ------------------------------------------------------------------------------- -- CalendarDiffTime ------------------------------------------------------------------------------- #if MIN_VERSION_time(1,9,0) && !MIN_VERSION_base(1,9,2) deriving instance Typeable CalendarDiffDays deriving instance Data CalendarDiffDays #endif #if !MIN_VERSION_time(1,9,0) data CalendarDiffDays = CalendarDiffDays { cdMonths :: Integer , cdDays :: Integer } deriving (Eq, Data #if __GLASGOW_HASKELL__ >= 802 #endif ,Typeable #if __GLASGOW_HASKELL__ >= 802 #endif ) -- | Additive instance Semigroup CalendarDiffDays where CalendarDiffDays m1 d1 <> CalendarDiffDays m2 d2 = CalendarDiffDays (m1 + m2) (d1 + d2) -- | Additive instance Monoid CalendarDiffDays where mempty = CalendarDiffDays 0 0 mappend = (<>) instance Show CalendarDiffDays where show (CalendarDiffDays m d) = "P" ++ show m ++ "M" ++ show d ++ "D" calendarDay :: CalendarDiffDays calendarDay = CalendarDiffDays 0 1 calendarWeek :: CalendarDiffDays calendarWeek = CalendarDiffDays 0 7 calendarMonth :: CalendarDiffDays calendarMonth = CalendarDiffDays 1 0 calendarYear :: CalendarDiffDays calendarYear = CalendarDiffDays 12 0 -- | Scale by a factor. Note that @scaleCalendarDiffDays (-1)@ will not perfectly invert a duration, due to variable month lengths. scaleCalendarDiffDays :: Integer -> CalendarDiffDays -> CalendarDiffDays scaleCalendarDiffDays k (CalendarDiffDays m d) = CalendarDiffDays (k * m) (k * d) #endif ------------------------------------------------------------------------------- -- Gregorian ------------------------------------------------------------------------------- #if !MIN_VERSION_time(1,9,0) -- | Add months (clipped to last day), then add days addGregorianDurationClip :: CalendarDiffDays -> Day -> Day addGregorianDurationClip (CalendarDiffDays m d) day = addDays d $ addGregorianMonthsClip m day -- | Add months (rolling over to next month), then add days addGregorianDurationRollOver :: CalendarDiffDays -> Day -> Day addGregorianDurationRollOver (CalendarDiffDays m d) day = addDays d $ addGregorianMonthsRollOver m day -- | Calendrical difference, with as many whole months as possible diffGregorianDurationClip :: Day -> Day -> CalendarDiffDays diffGregorianDurationClip day2 day1 = let (y1,m1,d1) = toGregorian day1 (y2,m2,d2) = toGregorian day2 ym1 = y1 * 12 + toInteger m1 ym2 = y2 * 12 + toInteger m2 ymdiff = ym2 - ym1 ymAllowed = if day2 >= day1 then if d2 >= d1 then ymdiff else ymdiff - 1 else if d2 <= d1 then ymdiff else ymdiff + 1 dayAllowed = addGregorianDurationClip (CalendarDiffDays ymAllowed 0) day1 in CalendarDiffDays ymAllowed $ diffDays day2 dayAllowed -- | Calendrical difference, with as many whole months as possible. -- Same as 'diffGregorianDurationClip' for positive durations. diffGregorianDurationRollOver :: Day -> Day -> CalendarDiffDays diffGregorianDurationRollOver day2 day1 = let (y1,m1,d1) = toGregorian day1 (y2,m2,d2) = toGregorian day2 ym1 = y1 * 12 + toInteger m1 ym2 = y2 * 12 + toInteger m2 ymdiff = ym2 - ym1 ymAllowed = if day2 >= day1 then if d2 >= d1 then ymdiff else ymdiff - 1 else if d2 <= d1 then ymdiff else ymdiff + 1 dayAllowed = addGregorianDurationRollOver (CalendarDiffDays ymAllowed 0) day1 in CalendarDiffDays ymAllowed $ diffDays day2 dayAllowed #endif ------------------------------------------------------------------------------- -- DayOfWeek ------------------------------------------------------------------------------- #if !MIN_VERSION_time(1,9,0) data DayOfWeek = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Show, Read, Typeable) -- | \"Circular\", so for example @[Tuesday ..]@ gives an endless sequence. -- Also: 'fromEnum' gives [1 .. 7] for [Monday .. Sunday], and 'toEnum' performs mod 7 to give a cycle of days. instance Enum DayOfWeek where toEnum i = case mod i 7 of 0 -> Sunday 1 -> Monday 2 -> Tuesday 3 -> Wednesday 4 -> Thursday 5 -> Friday _ -> Saturday fromEnum Monday = 1 fromEnum Tuesday = 2 fromEnum Wednesday = 3 fromEnum Thursday = 4 fromEnum Friday = 5 fromEnum Saturday = 6 fromEnum Sunday = 7 enumFromTo wd1 wd2 | wd1 == wd2 = [wd1] enumFromTo wd1 wd2 = wd1 : enumFromTo (succ wd1) wd2 enumFromThenTo wd1 wd2 wd3 | wd2 == wd3 = [wd1, wd2] enumFromThenTo wd1 wd2 wd3 = wd1 : enumFromThenTo wd2 (toEnum $ (2 * fromEnum wd2) - (fromEnum wd1)) wd3 dayOfWeek :: Day -> DayOfWeek dayOfWeek (ModifiedJulianDay d) = toEnum $ fromInteger $ d + 3 toSomeDay :: DayOfWeek -> Day toSomeDay d = ModifiedJulianDay (fromIntegral $ fromEnum d + 4) #if MIN_VERSION_time(1,8,0) #define FORMAT_OPTS tl mpo i #else #define FORMAT_OPTS tl mpo #endif instance FormatTime DayOfWeek where formatCharacter 'u' = fmap (\f FORMAT_OPTS d -> f FORMAT_OPTS (toSomeDay d)) (formatCharacter 'u') formatCharacter 'w' = fmap (\f FORMAT_OPTS d -> f FORMAT_OPTS (toSomeDay d)) (formatCharacter 'w') formatCharacter 'a' = fmap (\f FORMAT_OPTS d -> f FORMAT_OPTS (toSomeDay d)) (formatCharacter 'a') formatCharacter 'A' = fmap (\f FORMAT_OPTS d -> f FORMAT_OPTS (toSomeDay d)) (formatCharacter 'A') formatCharacter _ = Nothing #endif