packages feed

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

{-# LANGUAGE NumericUnderscores #-}

module DataFrame.IO.Parquet.Time where

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 + 32_044
        b = (4 * a + 3) `div` 146_097
        c = a - (146_097 * 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
            - 32_045