packages feed

http-date-0.0.12: Network/HTTP/Date/Converter.hs

{-# LANGUAGE BangPatterns #-}

module Network.HTTP.Date.Converter (
    epochTimeToHTTPDate,
    httpDateToUTC,
    utcToHTTPDate,
) where

import Data.Time
import Data.Time.Calendar.WeekDate
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Data.Word
import Foreign.Marshal.Array
import Foreign.Ptr
import Foreign.Storable
import Network.HTTP.Date.Types
import System.IO.Unsafe (unsafeDupablePerformIO, unsafePerformIO)
import System.Posix.Types

-- |
--   Translating 'EpochTime' to 'HTTPDate'.
epochTimeToHTTPDate :: EpochTime -> HTTPDate
epochTimeToHTTPDate x
    | x < 0 = utcToHTTPDate $ posixSecondsToUTCTime $ realToFrac x
    | otherwise =
        defaultHTTPDate
            { hdYear = y
            , hdMonth = m
            , hdDay = d
            , hdHour = h
            , hdMinute = n
            , hdSecond = s
            , hdWkday = w
            }
  where
    w64 :: Word64
    w64 = fromIntegral $ fromEnum x
    (days', secs') = w64 `quotRem` 86400
    days = fromIntegral days'
    secs = fromIntegral secs'
    -- 1970/1/1 is Thu (4)
    w = (days + 3) `rem` 7 + 1
    (y, m, d) = toYYMMDD days
    (h, n, s) = toHHMMSS secs

-- | Translating 'HTTPDate' to 'UTCTime'.
--
--   Since 0.0.7.
httpDateToUTC :: HTTPDate -> UTCTime
httpDateToUTC x = UTCTime (fromGregorian y m d) (secondsToDiffTime s)
  where
    y = fromIntegral $ hdYear x
    m = hdMonth x
    d = hdDay x
    s =
        fromIntegral $
            (hdHour x `rem` 24) * 3600
                + (hdMinute x `rem` 60) * 60
                + (hdSecond x `rem` 60)

-- | Translating 'UTCTime' to 'HTTPDate'.
--
--   Since 0.0.7.
utcToHTTPDate :: UTCTime -> HTTPDate
utcToHTTPDate x =
    defaultHTTPDate
        { hdYear = fromIntegral y
        , hdMonth = m
        , hdDay = d
        , hdHour = h
        , hdMinute = n
        , hdSecond = truncate s
        , hdWkday = fromEnum (w :: Int)
        }
  where
    (y, m, d) = toGregorian day
    (h, n, s) = (todHour tod, todMin tod, todSec tod)
    (_, _, w) = toWeekDate day
    day = localDay time
    tod = localTimeOfDay time
    time = utcToLocalTime utc x

toYYMMDD :: Int -> (Int, Int, Int)
toYYMMDD x = (yy, mm, dd)
  where
    (y, d) = x `quotRem` 365
    cy = 1970 + y
    cy' = cy - 1
    leap = cy' `quot` 4 - cy' `quot` 100 + cy' `quot` 400 - 477
    (yy, days) = adjust cy d leap
    (mm, dd) = findMonth days
    adjust !ty td aj
        | td >= aj = (ty, td - aj)
        | isLeap (ty - 1) =
            if td + 366 >= aj
                then (ty - 1, td + 366 - aj)
                else adjust (ty - 1) (td + 366) aj
        | otherwise =
            if td + 365 >= aj
                then (ty - 1, td + 365 - aj)
                else adjust (ty - 1) (td + 365) aj
    isLeap year =
        year `rem` 4 == 0
            && ( year `rem` 400 == 0
                    || year `rem` 100 /= 0
               )
    (mnths, daysArr) =
        if isLeap yy
            then (leapMonth, leapDayInMonth)
            else (normalMonth, normalDayInMonth)
    findMonth n =
        unsafeDupablePerformIO $
            (,) <$> peekElemOff mnths n <*> peekElemOff daysArr n

----------------------------------------------------------------

normalMonthDays :: [Int]
normalMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

leapMonthDays :: [Int]
leapMonthDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

mkPtrInt :: [Int] -> Ptr Int
mkPtrInt = unsafePerformIO . newArray . concat . zipWith (flip replicate) [1 ..]

mkPtrInt2 :: [Int] -> Ptr Int
mkPtrInt2 = unsafePerformIO . newArray . concatMap (enumFromTo 1)

normalMonth :: Ptr Int
normalMonth = mkPtrInt normalMonthDays

normalDayInMonth :: Ptr Int
normalDayInMonth = mkPtrInt2 normalMonthDays

leapMonth :: Ptr Int
leapMonth = mkPtrInt leapMonthDays

leapDayInMonth :: Ptr Int
leapDayInMonth = mkPtrInt2 leapMonthDays

----------------------------------------------------------------

toHHMMSS :: Int -> (Int, Int, Int)
toHHMMSS x = (hh, mm, ss)
  where
    (hhmm, ss) = x `quotRem` 60
    (hh, mm) = hhmm `quotRem` 60