packages feed

hodatime-1.0.0.0: src/Data/HodaTime/Calendar/Gregorian/CacheTable.hs

module Data.HodaTime.Calendar.Gregorian.CacheTable
(
   DTCacheTable(..)
  ,cacheTable
  ,decodeYear
  ,decodeMonth
  ,decodeDay
  ,decodeHour
  ,decodeMinute
  ,decodeSecond
)
where

import Data.Word (Word16)
import Data.Bits (shift, (.|.), (.&.), shiftR)
import Data.Array.Unboxed (array, UArray)

type DTCacheDaysTable = UArray Int Word16
type DTCacheHoursTable = UArray Int Word16

data DTCacheTable = DTCacheTable DTCacheDaysTable DTCacheHoursTable

-- TODO: The start date is offset but otherwise the months are all in their
--        unrotated form (i.e. Jan/Feb are in their normal year, not the previous year).
--        does this hurt anything?  It's nice for decoding but maybe some math expects
--        Jan/Feb to be (year - 1)

-- The Cache Table holds years and hours in the following format:
-- +-----+----+----+  +----+----+----+
-- |0-100|1-12|1-31|  |0-11|0-59|0-59|
-- +-----+----+----+  +----+----+----+
--    7     4   5        4    6    6
-- Meaning we can store 100 years of days and 12 hours of seconds in 16 bits each
cacheTable :: DTCacheTable
cacheTable = DTCacheTable days hours where
  toArray xs = array (0, length xs - 1) $ zip [0..] xs
  days = toArray $ firstYear ++ years ++ lastYear
  firstYear = [ encodeDate 0 m d | m <- [2..11], d <- daysInMonth m 0]
  years = [ encodeDate y m d | y <- [1..99], m <- [0..11], d <- daysInMonth m y]
  lastYear = [ encodeDate 100 m d | m <- [0, 1], d <- daysInMonth m 100]
  hours = toArray [ encodeTime h m s | h <- [0..11], m <- [0..59], s <- [0..59]]

-- encode

yearShift :: Num a => a
yearShift = 9

monthShift :: Num a => a
monthShift = 5

encodeDate :: Word16 -> Word16 -> Word16 -> Word16
encodeDate y m d = shift y yearShift .|. shift m monthShift .|. d

-- Annoying to semi replicate this logic but otherwise we have to move much of this code to Internal to use the enums
daysInMonth :: Word16 -> Word16 -> [Word16]
daysInMonth 1 y
  | isLeap                                      = [1..29]
  | otherwise                                   = [1..28]
  where
    isLeap
      | 0 == y `mod` 100                        = False           -- 400+ is not possible here
      | otherwise                               = 0 == y `mod` 4
daysInMonth n _
  | n == 3 || n == 5 || n == 8 || n == 10       = [1..30]
  | otherwise                                   = [1..31]

hourShift :: Num a => a
hourShift = 12

minuteShift :: Num a => a
minuteShift = 6

encodeTime :: Word16 -> Word16 -> Word16 -> Word16
encodeTime h m s = shift h hourShift .|. shift m minuteShift .|. s

-- decode dates

yearMask :: Num a => a
yearMask = 65024

decodeYear :: Word16 -> Word16
decodeYear = flip shiftR yearShift . (.&.) yearMask

monthMask :: Num a => a
monthMask = 480

decodeMonth :: Word16 -> Word16
decodeMonth = flip shiftR monthShift . (.&.) monthMask

dayMask :: Num a => a
dayMask = 31

decodeDay :: Word16 -> Word16
decodeDay = (.&.) dayMask

-- decode time

hourMask :: Num a => a
hourMask = 61440

decodeHour :: Word16 -> Word16
decodeHour = flip shiftR hourShift . (.&.) hourMask

minuteMask :: Num a => a
minuteMask = 4032

decodeMinute :: Word16 -> Word16
decodeMinute = flip shiftR minuteShift . (.&.) minuteMask

secondMask :: Num a => a
secondMask = 63

decodeSecond :: Word16 -> Word16
decodeSecond = (.&.) secondMask