{-# LANGUAGE CPP #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-} {-# OPTIONS_GHC -fno-warn-orphans #-} #include "thyme.h" #if HLINT #include "cabal_macros.h" #endif -- | Calendar calculations. -- -- Note that 'UTCTime' is not Y294K-compliant, and 'Bounded' instances for -- the various calendar types reflect this fact. That said, the calendar -- calculations by themselves work perfectly fine for a wider range of -- dates, subject to the size of 'Int' for your platform. module Data.Thyme.Calendar ( -- * Day Day (..), modifiedJulianDay -- * Calendar , Year, Month, DayOfMonth , YearMonthDay (..), _ymdYear, _ymdMonth, _ymdDay , Years, Months, Days -- * Gregorian calendar -- $proleptic , isLeapYear , yearMonthDay, gregorian, gregorianValid, showGregorian , module Data.Thyme.Calendar ) where import Prelude hiding ((.)) #if !MIN_VERSION_base(4,8,0) import Control.Applicative #endif import Control.Arrow import Control.Category import Control.Lens import Control.Monad import Data.AdditiveGroup import Data.AffineSpace import Data.Thyme.Calendar.Internal import Data.Thyme.Clock.Internal import System.Random import Test.QuickCheck -- "Data.Thyme.Calendar.Internal" cannot import "Data.Thyme.Clock.Internal", -- therefore these orphan 'Bounded' instances must live here. instance Bounded Day where minBound = minBound ^. _utctDay maxBound = maxBound ^. _utctDay instance Bounded YearMonthDay where minBound = minBound ^. gregorian maxBound = maxBound ^. gregorian instance Random Day where randomR r = first (^. _utctDay) . randomR (range r) where -- upper bound is one Micro second before the next day range = toMidnight *** pred . toMidnight . succ toMidnight day = utcTime # UTCView day zeroV random = randomR (minBound, maxBound) instance Random YearMonthDay where randomR = randomIsoR gregorian random = first (^. gregorian) . random instance Arbitrary Day where arbitrary = ModifiedJulianDay <$> choose (join (***) toModifiedJulianDay (minBound, maxBound)) shrink (ModifiedJulianDay mjd) = ModifiedJulianDay <$> shrink mjd instance Arbitrary YearMonthDay where arbitrary = view gregorian <$> arbitrary shrink ymd = view gregorian <$> shrink (gregorian # ymd) instance CoArbitrary YearMonthDay where coarbitrary (YearMonthDay y m d) = coarbitrary y . coarbitrary m . coarbitrary d ------------------------------------------------------------------------ -- $proleptic -- -- Note that using the -- calendar for -- dates before its adoption (from 1582 onwards, but varies from one country -- to the next) produces -- , -- which may cause some confusion. -- | The number of days in a given month in the -- calendar. -- -- @ -- > 'gregorianMonthLength' 2005 2 -- 28 -- @ {-# INLINE gregorianMonthLength #-} gregorianMonthLength :: Year -> Month -> Days gregorianMonthLength = monthLength . isLeapYear -- | Add months, with days past the last day of the month clipped to the -- last day. -- -- See also 'addGregorianMonthsClip'. -- -- @ -- > 'gregorianMonthsClip' 1 '$' 'YearMonthDay' 2005 1 30 -- 'YearMonthDay' {'ymdYear' = 2005, 'ymdMonth' = 2, 'ymdDay' = 28} -- @ {-# INLINEABLE gregorianMonthsClip #-} gregorianMonthsClip :: Months -> YearMonthDay -> YearMonthDay gregorianMonthsClip n (YearMonthDay y m d) = YearMonthDay y' m' $ min (gregorianMonthLength y' m') d where ((+) y -> y', (+) 1 -> m') = divMod (m + n - 1) 12 -- | Add months, with days past the last day of the month rolling over to -- the next month. -- -- See also 'addGregorianMonthsRollover'. -- -- @ -- > 'gregorianMonthsRollover' 1 $ 'YearMonthDay' 2005 1 30 -- 'YearMonthDay' {'ymdYear' = 2005, 'ymdMonth' = 3, 'ymdDay' = 2} -- @ {-# ANN gregorianMonthsRollover "HLint: ignore Use if" #-} {-# INLINEABLE gregorianMonthsRollover #-} gregorianMonthsRollover :: Months -> YearMonthDay -> YearMonthDay gregorianMonthsRollover n (YearMonthDay y m d) = case d <= len of True -> YearMonthDay y' m' d False -> case m' < 12 of True -> YearMonthDay y' (m' + 1) (d - len) False -> YearMonthDay (y' + 1) 1 (d - len) where ((+) y -> y', (+) 1 -> m') = divMod (m + n - 1) 12 len = gregorianMonthLength y' m' -- | Add years, matching month and day, with /February 29th/ clipped to the -- /28th/ if necessary. -- -- See also 'addGregorianYearsClip'. -- -- @ -- > 'gregorianYearsClip' 2 $ 'YearMonthDay' 2004 2 29 -- 'YearMonthDay' {'ymdYear' = 2006, 'ymdMonth' = 2, 'ymdDay' = 28} -- @ {-# INLINEABLE gregorianYearsClip #-} gregorianYearsClip :: Years -> YearMonthDay -> YearMonthDay gregorianYearsClip n (YearMonthDay ((+) n -> y') 2 29) | not (isLeapYear y') = YearMonthDay y' 2 28 gregorianYearsClip n (YearMonthDay y m d) = YearMonthDay (y + n) m d -- | Add years, matching month and day, with /February 29th/ rolled over to -- /March 1st/ if necessary. -- -- See also 'addGregorianYearsRollover'. -- -- @ -- > 'gregorianYearsRollover' 2 $ 'YearMonthDay' 2004 2 29 -- 'YearMonthDay' {'ymdYear' = 2006, 'ymdMonth' = 3, 'ymdDay' = 1} -- @ {-# INLINEABLE gregorianYearsRollover #-} gregorianYearsRollover :: Years -> YearMonthDay -> YearMonthDay gregorianYearsRollover n (YearMonthDay ((+) n -> y') 2 29) | not (isLeapYear y') = YearMonthDay y' 3 1 gregorianYearsRollover n (YearMonthDay y m d) = YearMonthDay (y + n) m d -- * Compatibility -- | Add some 'Days' to a calendar 'Day' to get a new 'Day'. -- -- @ -- 'addDays' = 'flip' ('.+^') -- 'addDays' n d ≡ d '.+^' n -- @ -- -- See also the 'AffineSpace' instance for 'Day'. {-# INLINE addDays #-} addDays :: Days -> Day -> Day addDays = flip (.+^) -- | Subtract two calendar 'Day's for the difference in 'Days'. -- -- @ -- 'diffDays' = ('.-.') -- 'diffDays' a b = a '.-.' b -- @ -- -- See also the 'AffineSpace' instance for 'Day'. {-# INLINE diffDays #-} diffDays :: Day -> Day -> Days diffDays = (.-.) -- | Convert a 'Day' to its Gregorian 'Year', 'Month', and 'DayOfMonth'. -- -- @ -- 'toGregorian' ('view' 'gregorian' -> 'YearMonthDay' y m d) = (y, m, d) -- @ {-# INLINE toGregorian #-} toGregorian :: Day -> (Year, Month, DayOfMonth) toGregorian (view gregorian -> YearMonthDay y m d) = (y, m, d) -- | Construct a 'Day' from a Gregorian calendar date. -- Does not validate the input. -- -- @ -- 'fromGregorian' y m d = 'gregorian' 'Control.Lens.#' 'YearMonthDay' y m d -- @ {-# INLINE fromGregorian #-} fromGregorian :: Year -> Month -> DayOfMonth -> Day fromGregorian y m d = gregorian # YearMonthDay y m d -- | Construct a 'Day' from a Gregorian calendar date. -- Returns 'Nothing' for invalid input. -- -- @ -- 'fromGregorianValid' y m d = 'gregorianValid' ('YearMonthDay' y m d) -- @ {-# INLINE fromGregorianValid #-} fromGregorianValid :: Year -> Month -> DayOfMonth -> Maybe Day fromGregorianValid y m d = gregorianValid (YearMonthDay y m d) -- | Add some number of 'Months' to the given 'Day'; if the original -- 'DayOfMonth' exceeds that of the new 'Month', it will be clipped to the -- last day of the new 'Month'. -- -- @ -- 'addGregorianMonthsClip' n = 'gregorian' '%~' 'gregorianMonthsClip' n -- @ {-# INLINE addGregorianMonthsClip #-} addGregorianMonthsClip :: Months -> Day -> Day addGregorianMonthsClip n = gregorian %~ gregorianMonthsClip n -- | Add some number of 'Months' to the given 'Day'; if the original -- 'DayOfMonth' exceeds that of the new 'Month', it will be rolled over into -- the following 'Month'. -- -- @ -- 'addGregorianMonthsRollover' n = 'gregorian' '%~' 'gregorianMonthsRollover' n -- @ {-# INLINE addGregorianMonthsRollover #-} addGregorianMonthsRollover :: Months -> Day -> Day addGregorianMonthsRollover n = gregorian %~ gregorianMonthsRollover n -- | Add some number of 'Years' to the given 'Day', with /February 29th/ -- clipped to /February 28th/ if necessary. -- -- @ -- 'addGregorianYearsClip' n = 'gregorian' '%~' 'gregorianYearsClip' n -- @ {-# INLINE addGregorianYearsClip #-} addGregorianYearsClip :: Years -> Day -> Day addGregorianYearsClip n = gregorian %~ gregorianYearsClip n -- | Add some number of 'Years' to the given 'Day', with /February 29th/ -- rolled over to /March 1st/ if necessary. -- -- @ -- 'addGregorianYearsRollover' n = 'gregorian' '%~' 'gregorianYearsRollover' n -- @ {-# INLINE addGregorianYearsRollover #-} addGregorianYearsRollover :: Years -> Day -> Day addGregorianYearsRollover n = gregorian %~ gregorianYearsRollover n