{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} module Data.Geo.Coordinate.Seconds( Seconds , AsSeconds(..) , remSeconds ) where import Control.Applicative(Applicative) import Control.Category(Category(id)) import Control.Lens(prism', Optic', Choice) import Data.Bool((&&)) import Data.Eq(Eq) import Data.Int(Int) import Data.Fixed(divMod') import Data.Ord(Ord((>), (>=), (<))) import Data.Maybe(Maybe(Just, Nothing)) import Data.List((++)) import Data.Tuple(snd) import Prelude(Double, Show(showsPrec), showParen, showString) import Text.Printf(printf) -- $setup -- >>> import Control.Lens((#), (^?), (^.)) -- >>> import Data.Foldable(all) -- >>> import Prelude(Eq(..)) newtype Seconds = Seconds Double deriving (Eq, Ord) -- | A show instance that prints to 4 decimal places. -- This is to take floating-point rounding errors into account. instance Show Seconds where showsPrec n (Seconds d) = showParen (n > 10) (showString ("Seconds " ++ printf "%0.4f" d)) class AsSeconds p f s where _Seconds :: Optic' p f s Seconds instance AsSeconds p f Seconds where _Seconds = id -- | A prism on seconds to a double between 0 inclusive and 60 exclusive. -- -- >>> (7 :: Double) ^? _Seconds -- Just (Seconds 7.0000) -- -- >>> (0 :: Double) ^? _Seconds -- Just (Seconds 0.0000) -- -- >>> (59 :: Double) ^? _Seconds -- Just (Seconds 59.0000) -- -- >>> (59.99 :: Double) ^? _Seconds -- Just (Seconds 59.9900) -- -- >>> (60 :: Double) ^? _Seconds -- Nothing -- -- prop> all (\m -> _Seconds # m == (n :: Double)) (n ^? _Seconds) instance (Choice p, Applicative f) => AsSeconds p f Double where _Seconds = prism' (\(Seconds d) -> d) (\d -> if d >= 0 && d < 60 then Just (Seconds d) else Nothing) -- | Setting a value `>= 60` will get that value `(`rem` 60)`. -- -- >>> remSeconds 7 -- Seconds 7.0000 -- -- >>> remSeconds 0 -- Seconds 0.0000 -- -- >>> remSeconds (-0.0001) -- Seconds 59.9999 -- -- >>> remSeconds 60 -- Seconds 0.0000 -- -- >>> remSeconds 59.99999 -- Seconds 60.0000 -- -- >>> remSeconds 59.999 -- Seconds 59.9990 remSeconds :: Double -> Seconds remSeconds x = Seconds (snd (x `divMod'` 60.0 :: (Int, Double)))