-- |
-- 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 year
    | year `mod` 4 /= 0   = False
    | year `mod` 100 /= 0 = True
    | year `mod` 400 == 0 = True
    | otherwise           = False

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

-- | return the number of days until the beggining of the month specified for a specific year.
daysUntilMonth :: Int -> Month -> Int
daysUntilMonth y m
    | isLeapYear y = leapYears !! fromEnum m
    | otherwise    = normalYears !! fromEnum m
  where normalYears = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ]
        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 y m
    | m == February && isLeapYear y = 29
    | otherwise                     = days !! fromEnum m
  where 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 y m d) = daysUntilMonth y m + d

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

-- | Return the number of day since 1 january 1
daysOfDate :: Date -> Int
daysOfDate (Date y m d) = daysBeforeYear y + daysUntilMonth y m + 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 $ Seconds (fromIntegral (daysOfDate date - epochDays) * secondsPerDay)
  where epochDays     = 719163
        secondsPerDay = 86400 -- julian day is 24h

-- | Return the Date associated with the unix epoch
dateFromUnixEpoch :: Elapsed -> Date
dateFromUnixEpoch e = dtDate $ dateTimeFromUnixEpoch e

-- | Return the number of seconds from a time structure
todToSeconds :: TimeOfDay -> Seconds
todToSeconds (TimeOfDay h m s _) = toSeconds h + toSeconds m + s

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