packages feed

dataframe-0.3.0.3: src/DataFrame/IO/Parquet/Time.hs

{-# LANGUAGE NumericUnderscores #-}

module DataFrame.IO.Parquet.Time where

import Data.Int
import Data.Time
import Data.Word

import DataFrame.IO.Parquet.Binary

int96ToUTCTime :: [Word8] -> UTCTime
int96ToUTCTime bytes
    | length bytes /= 12 = error "INT96 must be exactly 12 bytes"
    | otherwise =
        let (nanosBytes, julianBytes) = splitAt 8 bytes
            nanosSinceMidnight = littleEndianWord64 nanosBytes
            julianDay = littleEndianWord32 julianBytes
         in julianDayAndNanosToUTCTime (fromIntegral julianDay) nanosSinceMidnight

julianDayAndNanosToUTCTime :: Integer -> Word64 -> UTCTime
julianDayAndNanosToUTCTime julianDay nanosSinceMidnight =
    let day = julianDayToDay julianDay
        secondsSinceMidnight = fromIntegral nanosSinceMidnight / 1_000_000_000
        diffTime = secondsToDiffTime (floor secondsSinceMidnight)
     in UTCTime day diffTime

julianDayToDay :: Integer -> Day
julianDayToDay julianDay =
    let a = julianDay + 32044
        b = (4 * a + 3) `div` 146097
        c = a - (146097 * b) `div` 4
        d = (4 * c + 3) `div` 1461
        e = c - (1461 * d) `div` 4
        m = (5 * e + 2) `div` 153
        day = e - (153 * m + 2) `div` 5 + 1
        month = m + 3 - 12 * (m `div` 10)
        year = 100 * b + d - 4800 + m `div` 10
     in fromGregorian year (fromIntegral month) (fromIntegral day)

-- I include this here even though it's unused because we'll likely use
-- it for the writer. Since int96 is deprecated this is only included for completeness anyway.
utcTimeToInt96 :: UTCTime -> [Word8]
utcTimeToInt96 (UTCTime day diffTime) =
    let julianDay = dayToJulianDay day
        nanosSinceMidnight = floor (realToFrac diffTime * 1_000_000_000)
        nanosBytes = word64ToLittleEndian nanosSinceMidnight
        julianBytes = word32ToLittleEndian (fromIntegral julianDay)
     in nanosBytes ++ julianBytes

dayToJulianDay :: Day -> Integer
dayToJulianDay day =
    let (year, month, dayOfMonth) = toGregorian day
        a = fromIntegral $ (14 - (fromIntegral month)) `div` 12
        y = fromIntegral $ year + 4800 - a
        m = fromIntegral $ month + 12 * (fromIntegral a) - 3
     in fromIntegral dayOfMonth + (153 * m + 2) `div` 5 + 365 * y + y `div` 4 - y `div` 100 + y `div` 400 - 32045