module Data.UTC.Type.Date
  ( Date ()
  ) where

import Control.Monad.Catch

import Data.Ratio

import Data.UTC.Class.Epoch
import Data.UTC.Class.IsDate
import Data.UTC.Class.IsUnixTime
import Data.UTC.Internal
import Data.UTC.Type.Exception

-- | This type represents dates in the __Proleptic Gregorian Calendar__.
--
--   * It can represent any date in the past and in the future by using
--     'Prelude.Integer' internally.
--   * The internal structure is not exposed to avoid the construction of invalid values.
--     Use 'Data.UTC.epoch' or a parser to construct values.
--   * The instance of 'Prelude.Show' is only meant for debugging purposes
--     and is subject to change.
--
-- > > show (epoch :: Date)
-- > 1970-01-01
data Date
   = Date
     { dYear           :: Integer
     , dMonth          :: Integer
     , dDay            :: Integer
     } deriving (Eq, Ord)

instance Show Date where
  show (Date yy mm dd)
    = concat
        [ if yy < 0
            then "-"
            else ""
        , if abs yy > 9999
            then show (abs yy)
            else fixedDecimal 4 (abs yy)
        , "-"
        , fixedDecimal 2 mm
        , "-"
        , fixedDecimal 2 dd
        ]

instance Epoch Date where
  epoch
    = Date
      { dYear           = 1970
      , dMonth          = 1
      , dDay            = 1
      }

instance IsUnixTime Date where
  unixSeconds t
    = (days       * secsPerDay    % 1)
    - deltaUnixEpochCommonEpoch
    where
      days = yearMonthDayToDays (year t, month t, day t)
  fromUnixSeconds u
    = return
    $ Date
      { dYear           = y
      , dMonth          = m
      , dDay            = d
      }
    where
      s         = u + deltaUnixEpochCommonEpoch
      (y, m, d) = daysToYearMonthDay (truncate s `div` secsPerDay)

instance IsDate Date where
  year
    = dYear
  month
    = dMonth
  day
    = dDay
  setYear x t
    = if isValidDate (x, month t, day t)
      then return $ t { dYear  = x }
      else throwM $ UtcException $ "IsDate Date: setYear "  ++ show x ++ " " ++ show t
  setMonth x t
    = if isValidDate (year t, x, day t)
      then return $ t { dMonth = x }
      else throwM $ UtcException $ "IsDate Date: setMonth " ++ show x ++ " " ++ show t
  setDay x t
    = if isValidDate (year t, month t, x)
      then return $ t { dDay   = x }
      else throwM $ UtcException $ "IsDate Date: setDay "   ++ show x ++ " " ++ show t