-- |
-- Module      : Data.Hourglass.Calendar
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unknown
--
-- Misc calendar functions
--
module Data.Hourglass.Calendar
    ( isLeapYear
    , getWeekDay
    , getDayOfTheYear
    , daysInMonth
    , dateToUnixEpoch
    , dateFromUnixEpoch
    , todToSeconds
    , dateTimeToUnixEpoch
    , dateTimeFromUnixEpoch
    , dateTimeFromUnixEpochP
    ) where

import Data.Hourglass.Types
import Data.Hourglass.Internal

-- | Return if this year is a leap year (366 days)
-- or not (365 days in a year)
isLeapYear :: Int -> Bool
isLeapYear :: Int -> Bool
isLeapYear year :: Int
year
    | Int
year Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` 4 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= 0   = Bool
False
    | Int
year Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` 100 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= 0 = Bool
True
    | Int
year Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` 400 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 0 = Bool
True
    | Bool
otherwise           = Bool
False

-- | Return the day of the week a specific date fall in
getWeekDay :: Date -> WeekDay
getWeekDay :: Date -> WeekDay
getWeekDay date :: Date
date = Int -> WeekDay
forall a. Enum a => Int -> a
toEnum (Int
d Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` 7)
  where d :: Int
d = Date -> Int
daysOfDate Date
date

-- | return the number of days until the beggining of the month specified for a specific year.
daysUntilMonth :: Int -> Month -> Int
daysUntilMonth :: Int -> Month -> Int
daysUntilMonth y :: Int
y m :: Month
m
    | Int -> Bool
isLeapYear Int
y = [Int]
leapYears [Int] -> Int -> Int
forall a. [a] -> Int -> a
!! Month -> Int
forall a. Enum a => a -> Int
fromEnum Month
m
    | Bool
otherwise    = [Int]
normalYears [Int] -> Int -> Int
forall a. [a] -> Int -> a
!! Month -> Int
forall a. Enum a => a -> Int
fromEnum Month
m
  where normalYears :: [Int]
normalYears = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ]
        leapYears :: [Int]
leapYears   = [ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 ]

-- | Return the number of days in a month.
daysInMonth :: Int -> Month -> Int
daysInMonth :: Int -> Month -> Int
daysInMonth y :: Int
y m :: Month
m
    | Month
m Month -> Month -> Bool
forall a. Eq a => a -> a -> Bool
== Month
February Bool -> Bool -> Bool
&& Int -> Bool
isLeapYear Int
y = 29
    | Bool
otherwise                     = [Int]
days [Int] -> Int -> Int
forall a. [a] -> Int -> a
!! Month -> Int
forall a. Enum a => a -> Int
fromEnum Month
m
  where days :: [Int]
days = [31,28,31,30,31,30,31,31,30,31,30,31]

-- | return the day of the year where Jan 1 is 0
--
-- between 0 and 364. 365 for leap years
getDayOfTheYear :: Date -> Int
getDayOfTheYear :: Date -> Int
getDayOfTheYear (Date y :: Int
y m :: Month
m d :: Int
d) = Int -> Month -> Int
daysUntilMonth Int
y Month
m Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
d

-- | return the number of days before Jan 1st of the year
daysBeforeYear :: Int -> Int
daysBeforeYear :: Int -> Int
daysBeforeYear year :: Int
year = Int
y Int -> Int -> Int
forall a. Num a => a -> a -> a
* 365 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ (Int
y Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` 4) Int -> Int -> Int
forall a. Num a => a -> a -> a
- (Int
y Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` 100) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ (Int
y Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` 400)
  where y :: Int
y = Int
year Int -> Int -> Int
forall a. Num a => a -> a -> a
- 1

-- | Return the number of day since 1 january 1
daysOfDate :: Date -> Int
daysOfDate :: Date -> Int
daysOfDate (Date y :: Int
y m :: Month
m d :: Int
d) = Int -> Int
daysBeforeYear Int
y Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int -> Month -> Int
daysUntilMonth Int
y Month
m Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
d

-- | Return the number of seconds to unix epoch of a date considering hour=0,minute=0,second=0
dateToUnixEpoch :: Date -> Elapsed
dateToUnixEpoch :: Date -> Elapsed
dateToUnixEpoch date :: Date
date = Seconds -> Elapsed
Elapsed (Seconds -> Elapsed) -> Seconds -> Elapsed
forall a b. (a -> b) -> a -> b
$ Int64 -> Seconds
Seconds (Int -> Int64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Date -> Int
daysOfDate Date
date Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
epochDays) Int64 -> Int64 -> Int64
forall a. Num a => a -> a -> a
* Int64
secondsPerDay)
  where epochDays :: Int
epochDays     = 719163
        secondsPerDay :: Int64
secondsPerDay = 86400 -- julian day is 24h

-- | Return the Date associated with the unix epoch
dateFromUnixEpoch :: Elapsed -> Date
dateFromUnixEpoch :: Elapsed -> Date
dateFromUnixEpoch e :: Elapsed
e = DateTime -> Date
dtDate (DateTime -> Date) -> DateTime -> Date
forall a b. (a -> b) -> a -> b
$ Elapsed -> DateTime
dateTimeFromUnixEpoch Elapsed
e

-- | Return the number of seconds from a time structure
todToSeconds :: TimeOfDay -> Seconds
todToSeconds :: TimeOfDay -> Seconds
todToSeconds (TimeOfDay h :: Hours
h m :: Minutes
m s :: Seconds
s _) = Hours -> Seconds
forall i. TimeInterval i => i -> Seconds
toSeconds Hours
h Seconds -> Seconds -> Seconds
forall a. Num a => a -> a -> a
+ Minutes -> Seconds
forall i. TimeInterval i => i -> Seconds
toSeconds Minutes
m Seconds -> Seconds -> Seconds
forall a. Num a => a -> a -> a
+ Seconds
s

-- | Return the number of seconds to unix epoch of a date time
dateTimeToUnixEpoch :: DateTime -> Elapsed
dateTimeToUnixEpoch :: DateTime -> Elapsed
dateTimeToUnixEpoch (DateTime d :: Date
d t :: TimeOfDay
t) = Date -> Elapsed
dateToUnixEpoch Date
d Elapsed -> Elapsed -> Elapsed
forall a. Num a => a -> a -> a
+ Seconds -> Elapsed
Elapsed (TimeOfDay -> Seconds
todToSeconds TimeOfDay
t)