lens-1.2: src/Data/Time/Calendar/Lens.hs
{-# 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 <ekmett@gmail.com>
-- 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