#include "thyme.h"
#if HLINT
#include "cabal_macros.h"
#endif
module Data.Thyme.Clock.TAI
( AbsoluteTime
, taiEpoch
, LeapSecondTable
, utcDayLength
, absoluteTime
, parseTAIUTCDAT
) where
import Prelude
import Control.Applicative
import Control.DeepSeq
import Control.Lens
import Control.Monad
import Data.AffineSpace
import Data.Attoparsec.ByteString.Char8 ((<?>))
import qualified Data.Attoparsec.ByteString.Char8 as P
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as S
import Data.Char
import Data.Data
import Data.Either
import Data.Ix
#if MIN_VERSION_containers(0,5,0)
import qualified Data.Map.Strict as Map
#else
import qualified Data.Map as Map
#endif
import Data.Thyme.Calendar
import Data.Thyme.Clock.Internal
import Data.Thyme.Format.Internal
import Data.Thyme.LocalTime
import Data.Vector.Generic (Vector)
import Data.Vector.Generic.Mutable (MVector)
import qualified Data.Vector.Unboxed as VU
import qualified Data.Vector.Unboxed.Mutable as VUM
import Data.VectorSpace
import GHC.Generics (Generic)
import System.Locale
import System.Random (Random)
import Test.QuickCheck
newtype AbsoluteTime = AbsoluteTime DiffTime deriving (INSTANCES_MICRO)
instance Show AbsoluteTime where
showsPrec p tai = showsPrec p lt . (++) " TAI" where
lt = tai ^. from (absoluteTime (const zeroV)) . utcLocalTime utc
taiEpoch :: AbsoluteTime
taiEpoch = AbsoluteTime zeroV
instance AffineSpace AbsoluteTime where
type Diff AbsoluteTime = DiffTime
(.-.) = \ (AbsoluteTime a) (AbsoluteTime b) -> a ^-^ b
(.+^) = \ (AbsoluteTime a) d -> AbsoluteTime (a ^+^ d)
type LeapSecondTable = Either UTCTime AbsoluteTime -> DiffTime
utcDayLength :: LeapSecondTable -> Day -> DiffTime
utcDayLength table day@((.+^ 1) -> next) =
DiffTime posixDay ^+^ diff next ^-^ diff day where
diff d = table . Left $ utcTime # UTCTime d zeroV
NominalDiffTime posixDay = posixDayLength
absoluteTime :: LeapSecondTable -> Iso' UTCTime AbsoluteTime
absoluteTime table = iso toTAI fromTAI where
toTAI :: UTCTime -> AbsoluteTime
toTAI ut@(UTCRep (NominalDiffTime u)) =
AbsoluteTime (DiffTime u ^+^ table (Left ut))
fromTAI :: AbsoluteTime -> UTCTime
fromTAI tai@(AbsoluteTime a) = UTCRep (NominalDiffTime u) where
DiffTime u = a ^-^ table (Right tai)
parseTAIUTCDAT :: ByteString -> LeapSecondTable
parseTAIUTCDAT = parse $ do
y <- dec_ 5 <* P.skipSpace <?> "Year"
let mons = map toUpper . snd <$> months defaultTimeLocale
m <- succ <$> indexOf mons <* P.skipSpace <?> "Month"
d <- dec_ 2 <?> "Day"
tokens ["=", "JD"]
mjd <- subtract 2400000 <$> P.decimal
<* P.string ".5" <?> "Julian Date .5"
let ymd = YearMonthDay y m d
unless (gregorian # ymd == ModifiedJulianDay mjd) . fail $
show ymd ++ " is not Modified Julian Day " ++ show mjd
tokens ["TAI", "-", "UTC", "="]
b <- P.rational <?> "Base"
tokens ["S", "+", "(", "MJD", "-"]
o <- P.rational <?> "Offset"
tokens [".", ")", "X"]
c <- P.rational <* tokens ["S"] <?> "Coefficient"
let atUTC (UTCRep t) = fromSeconds' $ b + c * (toMJD t o)
let atTAI (AbsoluteTime t) = fromSeconds' $ b + c * (toMJD t o) / (1 + c)
let NominalDiffTime ((toRational mjd *^) -> begin) = posixDayLength
let beginUTC = UTCRep (NominalDiffTime begin)
let beginTAI = AbsoluteTime (DiffTime begin ^-^ atUTC beginUTC)
return ((beginUTC, atUTC), (beginTAI, atTAI))
where
toMJD t = toSeconds t / toSeconds posixDayLength
tokens = foldr (\ tok a -> P.skipSpace >> P.string tok >> a) P.skipSpace
parse row = pair . unzip . rights . map (P.parseOnly row) . S.lines
pair (look -> atUTC, look -> atTAI) = either atUTC atTAI
#if MIN_VERSION_containers(0,5,0)
look l = \ t -> maybe zeroV (($ t) . snd) $ Map.lookupLE t (Map.fromList l)
#else
look l = \ t -> case Map.splitLookup t (Map.fromList l) of
(lt, eq, _) -> maybe zeroV ($ t) $ eq <|> fst <$> Map.maxView lt
#endif