-----------------------------------------------------------------------------
-- |
-- Module      :  Data.HodaTime.Calendar.Gregorian
-- Copyright   :  (C) 2017 Jason Johnson
-- License     :  BSD-style (see the file LICENSE)
-- Maintainer  :  Jason Johnson <jason.johnson.081@gmail.com>
-- Stability   :  experimental
-- Portability :  POSIX, Windows
--
-- This is the module for 'CalendarDate' and 'CalendarDateTime' in the 'Gregorian' calendar.
----------------------------------------------------------------------------
module Data.HodaTime.Calendar.Gregorian
(
  -- * Constructors
   calendarDate
  ,ncalendarDate
  ,fromNthDay
  ,fromWeekDate
  -- * Types
  ,Month(..)
  ,DayOfWeek(..)
  ,Gregorian
)
where

import Data.HodaTime.Calendar.Gregorian.Internal hiding (fromWeekDate)
import Data.HodaTime.CalendarDateTime.Internal (CalendarDate(..), NCalendarDate(..), DayNth, DayOfMonth, Year, WeekNumber)
import qualified Data.HodaTime.Calendar.Gregorian.Internal as GI
import Control.Monad (guard)

-- Constructors

-- TODO: smart constructors hard coded to Maybe, make them like LocalTime

-- | Smart constuctor for Gregorian calendar date.
calendarDate :: DayOfMonth -> Month Gregorian -> Year -> Maybe (CalendarDate Gregorian)
calendarDate :: Int -> Month Gregorian -> Int -> Maybe (CalendarDate Gregorian)
calendarDate Int
d Month Gregorian
m Int
y = do
  Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0 Bool -> Bool -> Bool
&& Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Month Gregorian -> Int -> Int
maxDaysInMonth Month Gregorian
m Int
y
  let days :: Int32
days = Int -> Int32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Int32) -> Int -> Int32
forall a b. (a -> b) -> a -> b
$ Int -> Month Gregorian -> Int -> Int
yearMonthDayToDays Int
y Month Gregorian
m Int
d
  Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Int32
days Int32 -> Int32 -> Bool
forall a. Ord a => a -> a -> Bool
> Int32
forall a. Integral a => a
invalidDayThresh
  CalendarDate Gregorian -> Maybe (CalendarDate Gregorian)
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (CalendarDate Gregorian -> Maybe (CalendarDate Gregorian))
-> CalendarDate Gregorian -> Maybe (CalendarDate Gregorian)
forall a b. (a -> b) -> a -> b
$ Int32 -> Word8 -> Word8 -> Word32 -> CalendarDate Gregorian
forall calendar.
Int32 -> Word8 -> Word8 -> Word32 -> CalendarDate calendar
CalendarDate Int32
days (Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
d) (Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Word8)
-> (Month Gregorian -> Int) -> Month Gregorian -> Word8
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Month Gregorian -> Int
forall a. Enum a => a -> Int
fromEnum (Month Gregorian -> Word8) -> Month Gregorian -> Word8
forall a b. (a -> b) -> a -> b
$ Month Gregorian
m) (Int -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
y)

-- | Smart constuctor for Gregorian calendar date.
ncalendarDate :: DayOfMonth -> Month Gregorian -> Year -> Maybe (NCalendarDate Gregorian)
ncalendarDate :: Int -> Month Gregorian -> Int -> Maybe (NCalendarDate Gregorian)
ncalendarDate Int
d Month Gregorian
m Int
y = do
  Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0 Bool -> Bool -> Bool
&& Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Month Gregorian -> Int -> Int
maxDaysInMonth Month Gregorian
m Int
y
  let (Int
cycles, Int
centuries, Int
days) = Int -> Month Gregorian -> Int -> (Int, Int, Int)
yearMonthDayToCycleCenturyDays Int
y Month Gregorian
m Int
d
  Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Int
days Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
forall a. Integral a => a
invalidDayThresh
  NCalendarDate Gregorian -> Maybe (NCalendarDate Gregorian)
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (NCalendarDate Gregorian -> Maybe (NCalendarDate Gregorian))
-> NCalendarDate Gregorian -> Maybe (NCalendarDate Gregorian)
forall a b. (a -> b) -> a -> b
$ Int8 -> Word8 -> Word32 -> NCalendarDate Gregorian
forall calendar. Int8 -> Word8 -> Word32 -> NCalendarDate calendar
NCalendarDate (Int -> Int8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
cycles) (Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
centuries) (Int -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
days)

-- | Smart constuctor for Gregorian calendar date based on relative month day.
fromNthDay :: DayNth -> DayOfWeek Gregorian -> Month Gregorian -> Year -> Maybe (CalendarDate Gregorian)
fromNthDay :: DayNth
-> DayOfWeek Gregorian
-> Month Gregorian
-> Int
-> Maybe (CalendarDate Gregorian)
fromNthDay DayNth
nth DayOfWeek Gregorian
dow Month Gregorian
m Int
y = do
  Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0 Bool -> Bool -> Bool
&& Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
mdim
  Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Int
days Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
forall a. Integral a => a
invalidDayThresh
  CalendarDate Gregorian -> Maybe (CalendarDate Gregorian)
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (CalendarDate Gregorian -> Maybe (CalendarDate Gregorian))
-> CalendarDate Gregorian -> Maybe (CalendarDate Gregorian)
forall a b. (a -> b) -> a -> b
$ Int32 -> Word8 -> Word8 -> Word32 -> CalendarDate Gregorian
forall calendar.
Int32 -> Word8 -> Word8 -> Word32 -> CalendarDate calendar
CalendarDate (Int -> Int32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
days) (Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
d) (Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Word8)
-> (Month Gregorian -> Int) -> Month Gregorian -> Word8
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Month Gregorian -> Int
forall a. Enum a => a -> Int
fromEnum (Month Gregorian -> Word8) -> Month Gregorian -> Word8
forall a b. (a -> b) -> a -> b
$ Month Gregorian
m) (Int -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
y)
  where
    nth' :: Int
nth' = DayNth -> Int
forall a. Enum a => a -> Int
fromEnum DayNth
nth Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
4
    mdim :: Int
mdim = Month Gregorian -> Int -> Int
maxDaysInMonth Month Gregorian
m Int
y
    d :: Int
d = Int -> Int -> Month Gregorian -> Int -> Int
nthDayToDayOfMonth Int
nth' (DayOfWeek Gregorian -> Int
forall a. Enum a => a -> Int
fromEnum DayOfWeek Gregorian
dow) Month Gregorian
m Int
y
    days :: Int
days = Int -> Month Gregorian -> Int -> Int
yearMonthDayToDays Int
y Month Gregorian
m Int
d

-- | Smart constuctor for Gregorian calendar date based on week date.  Note that this method assumes weeks start on Sunday and the first week of the year is the one
--   which has at least one day in the new year.  For ISO compliant behavior use this constructor from the ISO module
fromWeekDate :: WeekNumber -> DayOfWeek Gregorian -> Year -> Maybe (CalendarDate Gregorian)
fromWeekDate :: Int -> DayOfWeek Gregorian -> Int -> Maybe (CalendarDate Gregorian)
fromWeekDate = Int
-> DayOfWeek Gregorian
-> Int
-> DayOfWeek Gregorian
-> Int
-> Maybe (Date Gregorian)
GI.fromWeekDate Int
1 DayOfWeek Gregorian
Sunday