{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE LiberalTypeSynonyms #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Time.Calendar.Lens -- Copyright : (C) 2012 Edward Kmett -- License : BSD-style (see the file LICENSE) -- Maintainer : Edward Kmett -- Stability : provisional -- Portability : LiberalTypeSynonyms -- -- Provides fairly ad hoc overloading to access different notions of a 'Day'. -- -- To convert from a 'Day': -- -- > myDay^.gregorian.year -- > myDay^.julian.year -- ---------------------------------------------------------------------------- module Data.Time.Calendar.Lens ( modifiedJulianDay , TraverseDay(..) , HasYear(..) , HasMonth(..) , HasWeek(..) , HasDay(..) , Gregorian(..) , gregorian , JulianYearAndDay(..) , julianYearAndDay , WeekDate(..) , weekDate , OrdinalDate(..) , ordinalDate ) where import Control.Applicative import Control.Lens import Data.Data import Data.Time.Calendar import Data.Time.Calendar.Julian import Data.Time.Calendar.WeekDate import Data.Time.Calendar.OrdinalDate -- | Provide ad hoc overloading for traversing the modified Julian day class TraverseDay t where -- | Convert the type to a modified Julian day if possible and traverse it. -- -- Traverses nothing if the date isn't valid. traverseDay :: Simple Traversal t Day -- | Returns the modified Julian Day as a standard count of days, -- with zero being the day 1858-11-17. modifiedJulianDay :: Simple Iso Day Integer modifiedJulianDay = iso toModifiedJulianDay ModifiedJulianDay instance TraverseDay Day where traverseDay = id -- | Ad hoc overloading for accessing the year class HasYear t where -- | Get the year of a date year :: Simple Lens t Integer -- | Ad hoc overloading for accessing the month class HasMonth t where -- | Get the month of a date month :: Simple Lens t Int -- | Ad hoc overloading for accessing the week (what it is relative to may vary from type to type) class HasWeek t where -- | Get the week of a date week :: Simple Lens t Int -- | Ad hoc overloading for accessing the day (what it is relative to may vary from type to type) class HasDay t where -- | Get the day of a date day :: Simple Lens t Int -- | Date in the proleptic Gregorian calendar. data Gregorian = Gregorian { gregorianYear :: !Integer -- ^ year , gregorianMonth :: !Int -- ^ month (1-12) , gregorianDay :: !Int -- ^ day (1-31) } deriving (Eq,Ord,Show,Read,Typeable,Data) uncurry3 :: (a -> b -> c -> d) -> (a,b,c) -> d uncurry3 f (a,b,c) = f a b c -- | Convert to/from a /valid/ date in the proleptic Gregorian calendar gregorian :: Simple Iso Day Gregorian gregorian = iso (uncurry3 Gregorian . toGregorian) $ \(Gregorian y m d) -> fromGregorian y m d instance TraverseDay Gregorian where traverseDay f g@(Gregorian y m d) = case fromGregorianValid y m d of Nothing -> pure g Just j -> (\i -> case toGregorian i of (y', m', d') -> Gregorian y' m' d') <$> f j instance HasYear Gregorian where year f (Gregorian y m d) = (\y' -> Gregorian y' m d) <$> f y instance HasMonth Gregorian where month f (Gregorian y m d) = (\m' -> Gregorian y m' d) <$> f m -- | Day of month instance HasDay Gregorian where day f (Gregorian y m d) = Gregorian y m <$> f d -- | Proleptic Julian year and day format. data JulianYearAndDay = JulianYearAndDay { julianYearAndDayYear :: !Integer -- ^ year (in the proleptic Julian calendar) , julianYearAndDayDay :: !Int -- ^ day of the year, with 1 for Jan 1, and 365 (or 366 in leap years) for Dec 31. } deriving (Eq,Ord,Show,Read,Typeable,Data) -- | Convert to/from a /valid/ proleptic Julian year and day. julianYearAndDay :: Simple Iso Day JulianYearAndDay julianYearAndDay = iso (uncurry JulianYearAndDay . toJulianYearAndDay) $ \(JulianYearAndDay y d) -> fromJulianYearAndDay y d instance TraverseDay JulianYearAndDay where traverseDay f j@(JulianYearAndDay y d) = case fromJulianYearAndDayValid y d of Nothing -> pure j Just k -> (\i -> case toJulianYearAndDay i of (y', d') -> JulianYearAndDay y' d') <$> f k instance HasYear JulianYearAndDay where year f (JulianYearAndDay y d) = (`JulianYearAndDay` d) <$> f y -- | Day of year instance HasDay JulianYearAndDay where day f (JulianYearAndDay y d) = JulianYearAndDay y <$> f d -- | ISO 8601 Week Date format. -- -- The first week of a year is the first week to contain at least four days in the corresponding Gregorian year. data WeekDate = WeekDate { weekDateYear :: !Integer -- ^ year. Note: that "Week" years are not quite the same as Gregorian years, as the first day of the year is always a Monday. , weekDateWeek :: !Int -- ^ week number (1-53) , weekDateDay :: !Int -- ^ day of week (1 for Monday to 7 for Sunday). } deriving (Eq,Ord,Show,Read,Typeable,Data) -- | Convert to/from a valid WeekDate weekDate :: Simple Iso Day WeekDate weekDate = iso (uncurry3 WeekDate . toWeekDate) $ \(WeekDate y w d) -> fromWeekDate y w d instance TraverseDay WeekDate where traverseDay f wd@(WeekDate y w d) = case fromWeekDateValid y w d of Nothing -> pure wd Just k -> (\i -> case toWeekDate i of (y', w', d') -> WeekDate y' w' d') <$> f k instance HasYear WeekDate where year f (WeekDate y w d) = (\y' -> WeekDate y' w d) <$> f y instance HasWeek WeekDate where week f (WeekDate y w d) = (\w' -> WeekDate y w' d) <$> f w -- | Day of week instance HasDay WeekDate where day f (WeekDate y w d) = WeekDate y w <$> f d -- | ISO 8601 Ordinal Date format data OrdinalDate = OrdinalDate { ordinalDateYear :: !Integer -- ^ year (proleptic Gregorian calendar) , ordinalDateDay :: !Int -- ^ day of the year, with 1 for Jan 1, and 365 (or 366 in leap years) for Dec 31. } deriving (Eq,Ord,Show,Read,Typeable,Data) -- | Convert to/from a valid ISO 8601 Ordinal Date format. ordinalDate :: Simple Iso Day OrdinalDate ordinalDate = iso (uncurry OrdinalDate . toOrdinalDate) $ \(OrdinalDate y d) -> fromOrdinalDate y d instance TraverseDay OrdinalDate where traverseDay f od@(OrdinalDate y d) = case fromOrdinalDateValid y d of Nothing -> pure od Just k -> (\i -> case toOrdinalDate i of (y', d') -> OrdinalDate y' d') <$> f k instance HasYear OrdinalDate where year f (OrdinalDate y d) = (`OrdinalDate` d) <$> f y instance HasDay OrdinalDate where day f (OrdinalDate y d) = OrdinalDate y <$> f d