{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DeriveDataTypeable #-} -- | -- Module : Time.Types -- License : BSD-style -- Maintainer : Vincent Hanquez -- -- Basic times units and types. -- -- While pratically some units could hold infinite values, for practical -- and efficient purpose they are limited to int64 types for seconds -- and int types for years. -- -- Most units use the unix epoch referential, but by no means reduce portability. -- the unix referential works under the Windows platform or any other platforms. -- module Time.Types ( -- * Time units NanoSeconds(..) , Seconds(..) , Minutes(..) , Hours(..) , TimeInterval(..) -- * Time enumeration , Month(..) , WeekDay(..) -- * Timezone , TimezoneOffset(..) , timezoneOffsetToSeconds , timezone_UTC -- * Computer friendly format -- ** Unix elapsed , Elapsed(..) , ElapsedP(..) -- * Human friendly format -- ** Calendar time , Date(..) , TimeOfDay(..) , DateTime(..) ) where import Data.Int import Data.Data import Data.Ratio import Control.DeepSeq import Data.Hourglass.Utils (pad2) -- | Represent any time interval that has an -- equivalent value to a number of seconds. class TimeInterval i where toSeconds :: i -> Seconds fromSeconds :: Seconds -> (i, Seconds) -- | Nanoseconds newtype NanoSeconds = NanoSeconds Int64 deriving (Read,Eq,Ord,Num,Data,Typeable,NFData) instance Show NanoSeconds where show (NanoSeconds v) = shows v "ns" instance TimeInterval NanoSeconds where toSeconds (NanoSeconds ns) = Seconds (ns `div` 1000000000) fromSeconds (Seconds s) = (NanoSeconds (s * 1000000000), 0) -- | Number of seconds without a referential. -- -- Can hold a number between [-2^63,2^63-1], which should -- be good for some billions of years. -- -- However, because of limitation in the calendar conversion -- currently used, seconds should be in the range [-2^55,2^55-1], -- which is good for only 1 billion of year. newtype Seconds = Seconds Int64 deriving (Read,Eq,Ord,Enum,Num,Real,Integral,Data,Typeable,NFData) instance Show Seconds where show (Seconds s) = shows s "s" instance TimeInterval Seconds where toSeconds = id fromSeconds s = (s,0) -- | Number of minutes without a referential. newtype Minutes = Minutes Int64 deriving (Read,Eq,Ord,Enum,Num,Real,Integral,Data,Typeable,NFData) instance Show Minutes where show (Minutes s) = shows s "m" instance TimeInterval Minutes where toSeconds (Minutes m) = Seconds (m * 60) fromSeconds (Seconds s) = (Minutes m, Seconds s') where (m, s') = s `divMod` 60 -- | Number of hours without a referential. newtype Hours = Hours Int64 deriving (Read,Eq,Ord,Enum,Num,Real,Integral,Data,Typeable,NFData) instance Show Hours where show (Hours s) = shows s "h" instance TimeInterval Hours where toSeconds (Hours h) = Seconds (h * 3600) fromSeconds (Seconds s) = (Hours h, Seconds s') where (h, s') = s `divMod` 3600 -- | A number of seconds elapsed since the unix epoch. newtype Elapsed = Elapsed Seconds deriving (Read,Eq,Ord,Num,Data,Typeable,NFData) instance Show Elapsed where show (Elapsed s) = show s -- | A number of seconds and nanoseconds elapsed since the unix epoch. data ElapsedP = ElapsedP {-# UNPACK #-} !Elapsed {-# UNPACK #-} !NanoSeconds deriving (Read,Eq,Ord,Data,Typeable) instance Show ElapsedP where show (ElapsedP e ns) = shows e ('.' : show ns) instance NFData ElapsedP where rnf e = e `seq` () instance Num ElapsedP where (+) = addElapsedP (-) = subElapsedP (ElapsedP e1 ns1) * (ElapsedP e2 ns2) = ElapsedP (e1*e2) (ns1*ns2) negate (ElapsedP e ns) = ElapsedP (negate e) ns abs (ElapsedP e ns) = ElapsedP (abs e) ns signum (ElapsedP e ns) = ElapsedP (signum e) ns fromInteger i = ElapsedP (Elapsed (fromIntegral i)) 0 addElapsedP :: ElapsedP -> ElapsedP -> ElapsedP addElapsedP (ElapsedP e1 (NanoSeconds ns1)) (ElapsedP e2 (NanoSeconds ns2)) = let notNormalizedNS = ns1 + ns2 (retainedNS, ns) = notNormalizedNS `divMod` 1000000000 in ElapsedP (e1 + e2 + (Elapsed $ Seconds retainedNS)) (NanoSeconds ns) subElapsedP :: ElapsedP -> ElapsedP -> ElapsedP subElapsedP (ElapsedP e1 (NanoSeconds ns1)) (ElapsedP e2 (NanoSeconds ns2)) = let notNormalizedNS = ns1 - ns2 notNormalizedS = e1 - e2 in if notNormalizedNS < 0 then ElapsedP (notNormalizedS - oneSecond) (NanoSeconds (1000000000 + notNormalizedNS)) else ElapsedP notNormalizedS (NanoSeconds notNormalizedNS) where oneSecond :: Elapsed oneSecond = Elapsed $ Seconds 1 instance Real ElapsedP where -- FIXME toRational (ElapsedP (Elapsed (Seconds s)) (NanoSeconds 0)) = fromIntegral s toRational (ElapsedP (Elapsed (Seconds s)) (NanoSeconds ns)) = fromIntegral s + (1000000000 % fromIntegral ns) -- | Month of the year data Month = January | February | March | April | May | June | July | August | September | October | November | December deriving (Show,Read,Eq,Ord,Enum,Data,Typeable,Bounded) -- | Day of the week -- -- the enumeration starts on Sunday. data WeekDay = Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday deriving (Show,Read,Eq,Ord,Enum,Data,Typeable,Bounded) -- | Offset against UTC in minutes to obtain from UTC time, local time. -- -- * a positive number represent a location East of UTC. -- -- * a negative number represent a location West of UTC. -- -- LocalTime t (-300) = t represent a time at UTC-5 -- LocalTime t (+480) = t represent a time at UTC+8 -- -- should be between -11H and +14H -- -- Example: -- in AUSEDT (UTC+1000 with daylight = UTC+1100), local time is 15:47; -- Thus, UTC time is 04:47, and TimezoneOffset is +660 (minutes) -- newtype TimezoneOffset = TimezoneOffset { timezoneOffsetToMinutes :: Int -- ^ return the number of minutes } deriving (Eq,Ord,Data,Typeable,NFData) -- | Return the number of seconds associated with a timezone timezoneOffsetToSeconds :: TimezoneOffset -> Seconds timezoneOffsetToSeconds (TimezoneOffset ofs) = Seconds (fromIntegral ofs * 60) instance Show TimezoneOffset where show (TimezoneOffset tz) = concat [if tz < 0 then "-" else "+", pad2 tzH, pad2 tzM] where (tzH, tzM) = abs tz `divMod` 60 -- | The UTC timezone. offset of 0 timezone_UTC :: TimezoneOffset timezone_UTC = TimezoneOffset 0 -- | human date representation using common calendar data Date = Date { dateYear :: {-# UNPACK #-} !Int -- ^ year (Common Era) , dateMonth :: !Month -- ^ month of the year , dateDay :: {-# UNPACK #-} !Int -- ^ day of the month, between 1 to 31 } deriving (Show,Read,Eq,Ord,Data,Typeable) instance NFData Date where rnf (Date y m d) = y `seq` m `seq` d `seq` () -- | human time representation of hour, minutes, seconds in a day. data TimeOfDay = TimeOfDay { todHour :: {-# UNPACK #-} !Hours -- ^ hours, between 0 and 23 , todMin :: {-# UNPACK #-} !Minutes -- ^ minutes, between 0 and 59 , todSec :: {-# UNPACK #-} !Seconds -- ^ seconds, between 0 and 59. 60 when having leap second */ , todNSec :: {-# UNPACK #-} !NanoSeconds -- ^ nanoseconds, between 0 and 999999999 */ } deriving (Show,Read,Eq,Ord,Data,Typeable) instance NFData TimeOfDay where rnf (TimeOfDay h m s ns) = h `seq` m `seq` s `seq` ns `seq` () -- | Date and Time data DateTime = DateTime { dtDate :: Date , dtTime :: TimeOfDay } deriving (Show,Read,Eq,Ord,Data,Typeable) instance NFData DateTime where rnf (DateTime d t) = rnf d `seq` rnf t `seq` ()