packages feed

postgresql-simple-interval 0.2025.9.5 → 1.0.0.0

raw patch · 4 files changed

+181/−17 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Database.PostgreSQL.Simple.Interval: scale :: Rational -> Interval -> Maybe Interval
+ Database.PostgreSQL.Simple.Interval: scaleSaturating :: Rational -> Interval -> Interval
+ Database.PostgreSQL.Simple.Interval.Unstable: scale :: Rational -> Interval -> Maybe Interval
+ Database.PostgreSQL.Simple.Interval.Unstable: scaleSaturating :: Rational -> Interval -> Interval

Files

postgresql-simple-interval.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: postgresql-simple-interval-version: 0.2025.9.5+version: 1.0.0.0 synopsis: A simple interval type for PostgreSQL. description:   This library provides a simple interval type for PostgreSQL. It supports
source/library/Database/PostgreSQL/Simple/Interval.hs view
@@ -37,12 +37,14 @@     Unstable.fromTimeSaturating,      -- * Arithmetic-    Unstable.negate,     Unstable.add,+    Unstable.negate,+    Unstable.scale,      -- ** Saturating-    Unstable.negateSaturating,     Unstable.addSaturating,+    Unstable.negateSaturating,+    Unstable.scaleSaturating,   ) where 
source/library/Database/PostgreSQL/Simple/Interval/Unstable.hs view
@@ -376,13 +376,7 @@ -- >>> negate (MkInterval (-2147483648) 0 0) -- Nothing negate :: Interval -> Maybe Interval-negate x =-  let safeNegate :: (Bits.Bits a, Integral a) => a -> Maybe a-      safeNegate = Bits.toIntegralSized . Prelude.negate . toInteger-   in MkInterval-        <$> safeNegate (months x)-        <*> safeNegate (days x)-        <*> safeNegate (microseconds x)+negate = scale (-1)  -- | Like 'Database.PostgreSQL.Simple.Interval.Unstable.negate' but uses -- saturating arithmetic rather than returning 'Maybe'.@@ -392,13 +386,7 @@ -- >>> negateSaturating (MkInterval (-2147483648) 0 0) -- MkInterval {months = 2147483647, days = 0, microseconds = 0} negateSaturating :: Interval -> Interval-negateSaturating x =-  let safeNegate :: (Bounded a, Integral a) => a -> a-      safeNegate = toIntegralSaturating . Prelude.negate . toInteger-   in MkInterval-        (safeNegate $ months x)-        (safeNegate $ days x)-        (safeNegate $ microseconds x)+negateSaturating = scaleSaturating (-1)  -- | Adds two intervals. Returns 'Nothing' if the result would overflow. --@@ -490,6 +478,76 @@  fromMicro :: Fixed.Micro -> Integer fromMicro (Fixed.MkFixed x) = x++-- | Scales an interval by the given ratio.+--+-- >>> scale 0.5 (MkInterval 2 4 8)+-- Just (MkInterval {months = 1, days = 2, microseconds = 4})+-- >>> scale 2 (MkInterval 2 4 8)+-- Just (MkInterval {months = 4, days = 8, microseconds = 16})+--+-- Each component is rounded.+--+-- >>> scale 0.4 (MkInterval 0 0 1) -- rounds down+-- Just (MkInterval {months = 0, days = 0, microseconds = 0})+-- >>> scale 0.5 (MkInterval 0 0 1) -- rounds half to even+-- Just (MkInterval {months = 0, days = 0, microseconds = 0})+-- >>> scale 0.6 (MkInterval 0 0 1) -- rounds up+-- Just (MkInterval {months = 0, days = 0, microseconds = 1})+--+-- Fractional days are converted into microseconds, assuming 24 hours per day.+--+-- >>> scale 0.5 (MkInterval 0 1 0)+-- Just (MkInterval {months = 0, days = 0, microseconds = 43200000000})+--+-- Fractional months are converted into days, assuming 30 days per month.+--+-- >>> scale 0.5 (MkInterval 1 0 0)+-- Just (MkInterval {months = 0, days = 15, microseconds = 0})+--+-- If this conversion produces fractional days, those are converted into+-- microseconds.+--+-- >>> scale 0.05 (MkInterval 1 0 0)+-- Just (MkInterval {months = 0, days = 1, microseconds = 43200000000})+--+-- Returns 'Nothing' if any component would overflow. See 'scaleSaturating' for+-- a version that uses saturating arithmetic instead.+--+-- >>> scale 2 (MkInterval 0 0 4611686018427387904)+-- Nothing+--+-- Note that due to rounding and conversion, scaling down and then up will not+-- necessarily return the original interval.+--+-- >>> fmap (scale 2) (scale 0.5 (MkInterval 0 0 1))+-- Just (Just (MkInterval {months = 0, days = 0, microseconds = 0}))+-- >>> fmap (scale 2) (scale 0.5 (MkInterval 1 0 0))+-- Just (Just (MkInterval {months = 0, days = 30, microseconds = 0}))+scale :: Rational -> Interval -> Maybe Interval+scale r i = do+  let (m :: Integer, mf) = properFraction . (*) r . toRational $ months i+  let (d :: Integer, uf) = properFraction . (+) (mf * 30) . (*) r . toRational $ days i+  let u :: Integer = round . (+) (uf * 86400000000) . (*) r . toRational $ microseconds i+  MkInterval+    <$> Bits.toIntegralSized m+    <*> Bits.toIntegralSized d+    <*> Bits.toIntegralSized u++-- | Like 'scale' but uses saturating arithmetic rather than returning+-- 'Nothing' on overflow.+--+-- >>> scaleSaturating 2 (MkInterval 0 0 4611686018427387904)+-- MkInterval {months = 0, days = 0, microseconds = 9223372036854775807}+scaleSaturating :: Rational -> Interval -> Interval+scaleSaturating r i =+  let (m :: Integer, mf) = properFraction . (*) r . toRational $ months i+      (d :: Integer, uf) = properFraction . (+) (mf * 30) . (*) r . toRational $ days i+      u :: Integer = round . (+) (uf * 86400000000) . (*) r . toRational $ microseconds i+   in MkInterval+        (toIntegralSaturating m)+        (toIntegralSaturating d)+        (toIntegralSaturating u)  -- | Renders an interval to a 'Builder'. This always has the same format: -- @"\@ A mon B day C hour D min E sec F us"@, where @A@, @B@, @C@, @D@, @E@,
source/test-suite/Main.hs view
@@ -268,6 +268,110 @@     H.it "works with saturating months" $ do       I.fromTimeSaturating (Time.CalendarDiffDays 2147483648 0) 0 `H.shouldBe` I.MkInterval maxBound 0 0 +  H.describe "scale" $ do+    H.describe "microseconds" $ do+      H.it "scales up" $ do+        I.scale 2 (I.MkInterval 0 0 1) `H.shouldBe` Just (I.MkInterval 0 0 2)++      H.it "scales down" $ do+        I.scale 0.5 (I.MkInterval 0 0 2) `H.shouldBe` Just (I.MkInterval 0 0 1)++      H.it "rounds down" $ do+        I.scale 0.4 (I.MkInterval 0 0 1) `H.shouldBe` Just (I.MkInterval 0 0 0)++      H.it "rounds up" $ do+        I.scale 0.6 (I.MkInterval 0 0 1) `H.shouldBe` Just (I.MkInterval 0 0 1)++      H.it "rounds half down to even" $ do+        I.scale 0.5 (I.MkInterval 0 0 1) `H.shouldBe` Just (I.MkInterval 0 0 0)++      H.it "rounds half up to even" $ do+        I.scale 0.5 (I.MkInterval 0 0 3) `H.shouldBe` Just (I.MkInterval 0 0 2)++      H.it "fails with overflow" $ do+        I.scale 2 (I.MkInterval 0 0 4611686018427387904) `H.shouldBe` Nothing++    H.describe "days" $ do+      H.it "scales up" $ do+        I.scale 2 (I.MkInterval 0 1 0) `H.shouldBe` Just (I.MkInterval 0 2 0)++      H.it "scales down" $ do+        I.scale 0.5 (I.MkInterval 0 2 0) `H.shouldBe` Just (I.MkInterval 0 1 0)++      H.it "converts fractional day into microseconds" $ do+        I.scale 0.5 (I.MkInterval 0 1 0) `H.shouldBe` Just (I.MkInterval 0 0 43200000000)++      H.it "fails with overflow" $ do+        I.scale 2 (I.MkInterval 0 1073741824 0) `H.shouldBe` Nothing++    H.describe "months" $ do+      H.it "scales up" $ do+        I.scale 2 (I.MkInterval 1 0 0) `H.shouldBe` Just (I.MkInterval 2 0 0)++      H.it "scales down" $ do+        I.scale 0.5 (I.MkInterval 2 0 0) `H.shouldBe` Just (I.MkInterval 1 0 0)++      H.it "converts fractional month into days" $ do+        I.scale 0.5 (I.MkInterval 1 0 0) `H.shouldBe` Just (I.MkInterval 0 15 0)++      H.it "converts fractional month into days and microseconds" $ do+        I.scaleSaturating 0.05 (I.MkInterval 1 0 0) `H.shouldBe` I.MkInterval 0 1 43200000000++      H.it "fails with overflow" $ do+        I.scale 2 (I.MkInterval 1073741824 0 0) `H.shouldBe` Nothing++  H.describe "scaleSaturating" $ do+    H.describe "microseconds" $ do+      H.it "scales up" $ do+        I.scaleSaturating 2 (I.MkInterval 0 0 1) `H.shouldBe` I.MkInterval 0 0 2++      H.it "scales down" $ do+        I.scaleSaturating 0.5 (I.MkInterval 0 0 2) `H.shouldBe` I.MkInterval 0 0 1++      H.it "rounds down" $ do+        I.scaleSaturating 0.4 (I.MkInterval 0 0 1) `H.shouldBe` I.MkInterval 0 0 0++      H.it "rounds up" $ do+        I.scaleSaturating 0.6 (I.MkInterval 0 0 1) `H.shouldBe` I.MkInterval 0 0 1++      H.it "rounds half down to even" $ do+        I.scaleSaturating 0.5 (I.MkInterval 0 0 1) `H.shouldBe` I.MkInterval 0 0 0++      H.it "rounds half up to even" $ do+        I.scaleSaturating 0.5 (I.MkInterval 0 0 3) `H.shouldBe` I.MkInterval 0 0 2++      H.it "saturates with overflow" $ do+        I.scaleSaturating 2 (I.MkInterval 0 0 4611686018427387904) `H.shouldBe` I.MkInterval 0 0 9223372036854775807++    H.describe "days" $ do+      H.it "scales up" $ do+        I.scaleSaturating 2 (I.MkInterval 0 1 0) `H.shouldBe` I.MkInterval 0 2 0++      H.it "scales down" $ do+        I.scaleSaturating 0.5 (I.MkInterval 0 2 0) `H.shouldBe` I.MkInterval 0 1 0++      H.it "converts fractional day into microseconds" $ do+        I.scaleSaturating 0.5 (I.MkInterval 0 1 0) `H.shouldBe` I.MkInterval 0 0 43200000000++      H.it "saturates with overflow" $ do+        I.scaleSaturating 2 (I.MkInterval 0 1073741824 0) `H.shouldBe` I.MkInterval 0 2147483647 0++    H.describe "months" $ do+      H.it "scales up" $ do+        I.scaleSaturating 2 (I.MkInterval 1 0 0) `H.shouldBe` I.MkInterval 2 0 0++      H.it "scales down" $ do+        I.scaleSaturating 0.5 (I.MkInterval 2 0 0) `H.shouldBe` I.MkInterval 1 0 0++      H.it "converts fractional month into days" $ do+        I.scaleSaturating 0.5 (I.MkInterval 1 0 0) `H.shouldBe` I.MkInterval 0 15 0++      H.it "converts fractional month into days and microseconds" $ do+        I.scaleSaturating 0.05 (I.MkInterval 1 0 0) `H.shouldBe` I.MkInterval 0 1 43200000000++      H.it "saturates with overflow" $ do+        I.scaleSaturating 2 (I.MkInterval 1073741824 0 0) `H.shouldBe` I.MkInterval 2147483647 0 0+   H.describe "render" $ do     H.it "works with zero" $ do       let actual = Builder.toLazyByteString $ I.render I.zero