scalendar-1.0.0: src/Time/SCalendar/Types.hs
module Time.SCalendar.Types
( TimePeriod(..)
, Reservation(..)
, Cancellation(..)
, SCalendar(..)
, Calendar(..)
, Report(..)
, isIncluded
, getFrom
, getTo
, toTimeUnit
, makeTimePeriod
, makeReservation
, makeCancellation
, createCalendar
, createSCalendar
, oneDay
, powerOfTwo
) where
import Data.Time ( UTCTime(..)
, NominalDiffTime
, addUTCTime
, fromGregorianValid )
import Data.Text (Text)
import Data.Set (Set)
import qualified Data.Set as S (empty)
-- | This data type is either a TimeInterval of the form (start-date, end-date)
-- or a TimeUnit which, in this case, is a nominal day. The time unit of this calendar
-- library is a nominal day, that is, 86400 seconds. TimeIntervals as well as
-- TimeUnits are stored as UTCTime so that it is easy to transform results to local
-- time or store results in databases as timestamps.
data TimePeriod =
TimeInterval UTCTime UTCTime -- ^ TimeIntervals represent the number of days that a node
-- in a calendar covers from a start-date up to an end-date.
| TimeUnit UTCTime -- ^ TimeUnits are only encountered in the leaves of a calendar and represent
-- a specific day of the calendar.
deriving (Eq, Show)
-- | Check if a time-period `t1` is included in a time-period `t2`. Note that neither a
-- TimeUnit can be included in another TimeUnit nor a TimeInterval can be included
-- in a TimeUnit. If two TimeIntervals are equal they are said to be included in
-- one another.
isIncluded :: TimePeriod -> TimePeriod -> Bool
isIncluded (TimeUnit _) (TimeUnit _) = False
isIncluded (TimeUnit t) (TimeInterval from to) = from <= t && t <= to
isIncluded (TimeInterval _ _) (TimeUnit _) = False
isIncluded (TimeInterval ifrom ito) (TimeInterval ofrom oto)
= and [ ofrom <= ifrom, ifrom <= oto, ofrom <= ito, ito <= oto, ifrom <= ito ]
-- | Getter function to get the UTCTime start-date from a TimePeriod. For a TimeUnit
-- the start-sate and the end-date are equal.
getFrom :: TimePeriod -> UTCTime
getFrom (TimeUnit t) = t
getFrom (TimeInterval from _) = from
-- | Getter function to fet the UTCTime end-date from a TimePeriod. Again, for a TimeUnit
-- the start-sate and the end-date are equal.
getTo :: TimePeriod -> UTCTime
getTo (TimeUnit t) = t
getTo (TimeInterval _ to) = to
-- | This function transforms a TimeInterval into a TimeUnit in case that the start-date and
-- end-date of that TimeInterval are equal.
toTimeUnit :: TimePeriod -> TimePeriod
toTimeUnit i@(TimeUnit _) = i
toTimeUnit i@(TimeInterval t1 t2)
| t1 == t2 = TimeUnit t1
| otherwise = i
-- | A Reservation is the product of a set of identifiers and a TimePeriod over which the
-- resources identified by the set will be reserved.
data Reservation = Reservation
{ reservUnits :: Set Text -- ^ Set of identifiers which point to reservable resources.
, reservPeriod :: TimePeriod -- ^ TimePeriod over which the resources will be reserved.
} deriving (Eq, Show)
-- | A Cancellation is the product of a set of identifiers which point to resources previously
-- reserved in a Calendar and a TimePeriod over which those resources were reserved.
data Cancellation = Cancellation
{ cancUnits :: Set Text -- ^ Set of identifiers which point to resources to be cancelled.
, cancPeriod :: TimePeriod -- ^ TimePeriod over which the resources will be cancelled.
} deriving (Eq, Show)
-- | A Report represents a summary of important facts related to an SCalendar.
data Report = Report
{ reportPeriod :: TimePeriod -- ^ The TimePeriod which the report covers.
, totalUnits :: Set Text -- ^ The set of total identifiers reservable in the SCalendar this Report belongs to.
, reservedUnits :: Set Text -- ^ The set of total identifiers which have been reserved in a TimePeriod in
-- the SCalendar related to this Report.
, remainingUnits :: Set Text -- ^ The set of total identifiers which are still available in a Time<Period in the
-- SCalendar related to this Report.
} deriving (Eq, Show)
-- | A Calendar is a recursive tree-structure whose nodes are TimePeriods representing the interval
-- of time covered by them. TimeUnits are only encountered in the leaves since they represent
-- specific days, or time units, of the Calendar. The unit of time of this Calendar library is a nominal
-- day (or 86400 seconds). Each node of a Calendar also carries additional data according to the
-- "top-nodes" algorithm: a `Q` set and a `QN` set. For more information about the meaning of these
-- sets visit: <https://en.wikipedia.org/wiki/Top-nodes_algorithm>
data Calendar =
Unit TimePeriod (Set Text) (Set Text)
| Node TimePeriod (Set Text) (Set Text) Calendar Calendar
deriving (Eq, Show)
-- | An SCalendar is the product of a set of identifiers, which point to a set of available resources,
-- and a Calendar.
data SCalendar = SCalendar
{ calUnits :: Set Text -- ^ Set of resources which can be reserved for the TimePeriod covered by
-- the root node of the Calendar.
, calendar :: Calendar -- ^ Calendar which covers the complete period of time over which a set of
-- resources can be reserved.
} deriving (Eq, Show)
-- | Given a year, a month and a day this function creates a time period which covers the specified
-- number of days.
makeTimePeriod :: Integer -- ^ Year.
-> Int -- ^ Month.
-> Int -- ^ Day.
-> Int -- ^ Number of days covered by TimePeriod.
-> Maybe TimePeriod
makeTimePeriod _ _ _ numDays
| numDays < 0 = Nothing
makeTimePeriod year month day numDays = do
gregDay <- fromGregorianValid year month day
let from = UTCTime gregDay 0
to = (fromIntegral numDays * oneDay) `addUTCTime` from
return $ if numDays == 0
then TimeUnit from
else TimeInterval from to
-- | Given a TimePeriod and a set of identifiers this function creates a reservation.
makeReservation :: TimePeriod -- ^ TimePeriod which the rerservation will cover.
-> Set Text -- ^ Set of identifiers which point to the resources to be reserved from an SCalendar.
-> Maybe Reservation
makeReservation period units
| null units = Nothing
| not $ isValidInterval period' = Nothing
| otherwise = Just $ Reservation units period'
where
period' = toTimeUnit period
-- | Given a TimePeriod and a set of identifiers this function creates a cancellation.
makeCancellation :: TimePeriod -- ^ TimePeriod which the cancellation will cover.
-> Set Text -- ^ Set of identifiers which point to the resources to be cancelled from an SCalendar.
-> Maybe Cancellation
makeCancellation period units
| null units = Nothing
| not $ isValidInterval period' = Nothing
| otherwise = Just $ Cancellation units period'
where
period' = toTimeUnit period
-- | Given a year, a month, and a day this function creates a Calendar which covers the specified
-- number of days. The TimePeriod in the root node of a Calendar does not exactly span the
-- number of days specified in the function, but a number of days which is a power of 2 and
-- which is greater than or equal to the number of days specified.
createCalendar :: Integer -- ^ Year.
-> Int -- ^ Month.
-> Int -- ^ Day.
-> Int -- ^ Number of days covered by the Calendar.
-> Maybe Calendar
createCalendar year month day numDays
| numDays <= 1 = Nothing
| otherwise = do
gregDay <- fromGregorianValid year month day
let fstDay = UTCTime gregDay 0
return $ go fstDay power
where
power = powerOfTwo numDays
go from factor
| parentDist == 0 = Unit (TimeUnit from) S.empty S.empty
| otherwise =
Node (TimeInterval from ((oneDay * parentDist) `addUTCTime` from))
S.empty
S.empty
(go from (factor - 1))
(go ((oneDay * childDist) `addUTCTime` from) (factor - 1))
where
parentDist = (2^factor) - 1
childDist = 2^(factor - 1)
-- | This constructor additionally attaches a set of identifiers, which point to the available resources of the
-- calendar. Thus, this function creates an SCalendar which is basically a Calendar with a set of resources which
-- can be reserved over the period of time determined by the root node of the Calendar.
createSCalendar :: Integer -- ^ Year.
-> Int -- ^ Month.
-> Int -- ^ Day.
-> Int -- ^ Number of days covered by the Calendar.
-> Set Text -- ^ Set of resources which can be reserved for the TimePeriod covered by
-- the root node of the Calendar.
-> Maybe SCalendar
createSCalendar _ _ _ _ tUnits
| null tUnits = Nothing
createSCalendar year month day numDays tUnits = do
calendar <- createCalendar year month day numDays
return $ SCalendar tUnits calendar
-- HELPER FUNCTIONS
isValidInterval :: TimePeriod -> Bool
isValidInterval (TimeUnit _) = True
isValidInterval (TimeInterval from to) = from < to
powerOfTwo :: Int -> Int
powerOfTwo n =
let power = ceiling $ logBase 2 (fromIntegral $ abs n)
in if power > 1 then power else 2
oneDay :: NominalDiffTime
oneDay = 86400