-- | convert  between 'UTCTime', 'UTCTimeBin' and 'DatePart'
module Data.Time.Hora.Part
        (-- * FromUTC
        FromUTC(..),
        fromUtc',
        -- * ToUTC
        ToUTC(..)) where

import Data.Fixed
import Data.Time.Calendar
import Data.Time.Clock
import Data.Time.Hora.Span
import Data.Time.Hora.Type
import Data.Time.LocalTime as L


class FromUTC a where
     fromUtc::UTCTime -> a

-- | returns DatePart a in UTC timezone
instance Integral a => FromUTC (DatePart a) where
   fromUtc::Integral a => UTCTime -> DatePart a
   fromUtc t0 =
       let day1 = utctDay t0::Day
           dt1 = utctDayTime t0::DiffTime
           (y1,m1,d1) = toGregorian day1
           tod1 = timeToTimeOfDay dt1::TimeOfDay
           pico4 = todSec tod1::Fixed E12
           (sec5, MkFixed pico5) = properFraction pico4
       in DatePart {
               year = fi y1,
               month = fi m1,
               day = fi d1,
               hour = fi $ todHour tod1,
               minute = fi $ todMin tod1,
               second = fi sec5,
               pico = fi pico5
               }


instance FromUTC UTCTimeBin where
      fromUtc::UTCTime -> UTCTimeBin
      fromUtc t0 = UTCTimeBin day1 pico1
            where day1 = toModifiedJulianDay $ utctDay t0
                  pico1 = diffTimeToPicoseconds $ utctDayTime t0


{- | specified time zone

Tz (DatePart a)  parts show local date & time  

see also "Data.Time.Hora.Zone"  -}
fromUtc'::(Tz' tz, Integral a) =>
    tz -> UTCTime -> Tz (DatePart a)
fromUtc' tz0 utc0 =
    let tz1 = tz' tz0 utc0
        lt2 = L.utcToLocalTime tz1 utc0
        day2 = localDay lt2
        time2 = localTimeOfDay lt2
        (y3,m3,d3) = toGregorian day2
        d4 = DatePart{
                     year = fi y3,
                     month = fi m3,
                     day = fi d3,
                     hour = fi $ todHour time2,
                     minute = fi $ todMin time2,
                     second = fi sec5,
                     pico = fi pico5
                   }
        pico4 = todSec time2::Fixed E12
        (sec5, MkFixed pico5) = properFraction pico4
    in Tz tz1 d4


{-| convert 'DatePart' -> 'UTCTime'

Invalid date returns Nothing -}
class ToUTC a where
    toUtc::a -> Maybe UTCTime


instance Integral a => ToUTC (DatePart a) where
    toUtc dp0 =
        let h1 = hour dp0 * 60 * 60 --  as second 
            min1 = minute dp0 * 60  --  as second
            s2 = second dp0 + h1 + min1
            diff1 = secondsToDiffTime $ fi s2
            diff2 = picosecondsToDiffTime $ fi $ pico dp0

            mday1 = fromGregorianValid (fi $ year dp0)
                            (fi $ month dp0) $ fi $ day dp0
        in mday1 >>= \day1 -> Just $ UTCTime day1 $ diff1 + diff2
-- ^ assumes DatePart is UTC 


instance Integral a => ToUTC (Tz (DatePart a)) where
    toUtc (Tz tz0 dp0) =
        let s1 = toPico $ Sec $ second dp0
            mtod1 = makeTimeOfDayValid (fi $ hour dp0)
                                        (fi $ minute dp0)
                                        (timeSpanPico $ Pico s1 + (Pico $ fi $ pico dp0))
            mday1 = fromGregorianValid (fi $ year dp0)
                            (fi $ month dp0) $ fi $ day dp0
        in mday1 >>= \day1 ->
            mtod1 >>= \tod1 ->
                let lt1 = LocalTime day1 tod1
                    zt1 = ZonedTime lt1 tz0
                in Just $ zonedTimeToUTC zt1

instance ToUTC UTCTimeBin where
        toUtc (UTCTimeBin day0 pico0) = Just $ UTCTime day1 diff1
            where day1 = ModifiedJulianDay day0
                  diff1 = picosecondsToDiffTime pico0

fi::TwoInt a b => a -> b
fi = fromIntegral