diff --git a/postgresql-types.cabal b/postgresql-types.cabal
--- a/postgresql-types.cabal
+++ b/postgresql-types.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: postgresql-types
-version: 0.1.5.1
+version: 0.1.6.0
 category: PostgreSQL, Codecs
 synopsis: Precise PostgreSQL types representation and driver-agnostic codecs
 description:
diff --git a/src/library/PostgresqlTypes/Timestamp.hs b/src/library/PostgresqlTypes/Timestamp.hs
--- a/src/library/PostgresqlTypes/Timestamp.hs
+++ b/src/library/PostgresqlTypes/Timestamp.hs
@@ -8,6 +8,7 @@
     -- * Constructors
     normalizeFromMicroseconds,
     normalizeFromLocalTime,
+    refineFromLocalTime,
   )
 where
 
@@ -154,6 +155,20 @@
   let diffTime = Time.diffLocalTime localTime postgresTimestampEpoch
       micros = round (diffTime * 1_000_000)
    in normalizeFromMicroseconds micros
+
+-- | Construct a PostgreSQL 'Timestamp' from 'Time.LocalTime', validating that
+-- it is representable exactly (both within range and without loss of
+-- sub-microsecond precision). Returns 'Nothing' if the value is outside the
+-- valid range or cannot be round-tripped without loss.
+refineFromLocalTime :: Time.LocalTime -> Maybe Timestamp
+refineFromLocalTime localTime =
+  let diffTime = Time.diffLocalTime localTime postgresTimestampEpoch
+      micros = round (diffTime * 1_000_000)
+   in if micros >= minMicroseconds
+        && micros <= maxMicroseconds
+        && toLocalTime (Timestamp micros) == localTime
+        then Just (Timestamp micros)
+        else Nothing
 
 -- | Format a LocalTime for PostgreSQL timestamp text format.
 -- PostgreSQL requires specific formatting for extreme dates:
diff --git a/src/library/PostgresqlTypes/Timestamptz.hs b/src/library/PostgresqlTypes/Timestamptz.hs
--- a/src/library/PostgresqlTypes/Timestamptz.hs
+++ b/src/library/PostgresqlTypes/Timestamptz.hs
@@ -8,6 +8,7 @@
     -- * Constructors
     normalizeFromMicroseconds,
     normalizeFromUtcTime,
+    refineFromUtcTime,
   )
 where
 
@@ -165,6 +166,20 @@
   let diffTime = Time.diffUTCTime utcTime postgresUtcEpoch
       micros = round (diffTime * 1_000_000)
    in normalizeFromMicroseconds micros
+
+-- | Construct a PostgreSQL 'Timestamptz' from 'Time.UTCTime', validating that
+-- it is representable exactly (both within range and without loss of
+-- sub-microsecond precision). Returns 'Nothing' if the value is outside the
+-- valid range or cannot be round-tripped without loss.
+refineFromUtcTime :: Time.UTCTime -> Maybe Timestamptz
+refineFromUtcTime utcTime =
+  let diffTime = Time.diffUTCTime utcTime postgresUtcEpoch
+      micros = round (diffTime * 1_000_000)
+   in if micros >= minMicroseconds
+        && micros <= maxMicroseconds
+        && toUtcTime (Timestamptz micros) == utcTime
+        then Just (Timestamptz micros)
+        else Nothing
 
 -- | Format a UTCTime for PostgreSQL timestamptz text format.
 -- PostgreSQL requires specific formatting for extreme dates:
diff --git a/src/unit-tests/PostgresqlTypes/TimestampSpec.hs b/src/unit-tests/PostgresqlTypes/TimestampSpec.hs
--- a/src/unit-tests/PostgresqlTypes/TimestampSpec.hs
+++ b/src/unit-tests/PostgresqlTypes/TimestampSpec.hs
@@ -6,6 +6,7 @@
 import Test.Hspec
 import Test.QuickCheck
 import qualified UnitTests.Scripts as Scripts
+import Prelude
 
 spec :: Spec
 spec = do
@@ -30,6 +31,19 @@
             localTime = Time.LocalTime day tod
             pgTimestamp = Timestamp.normalizeFromLocalTime localTime
         Timestamp.toLocalTime pgTimestamp `shouldBe` localTime
+
+    describe "refineFromLocalTime" do
+      it "accepts a LocalTime within range and precision" do
+        let day = Time.fromGregorian 2023 6 15
+            tod = Time.TimeOfDay 12 30 45
+            localTime = Time.LocalTime day tod
+        fmap Timestamp.toLocalTime (Timestamp.refineFromLocalTime localTime) `shouldBe` Just localTime
+
+      it "rejects a LocalTime with sub-microsecond precision" do
+        let day = Time.fromGregorian 2023 6 15
+            tod = Time.TimeOfDay 12 30 45.1234565
+            localTime = Time.LocalTime day tod
+        Timestamp.refineFromLocalTime localTime `shouldBe` Nothing
 
   describe "Accessors" do
     describe "toLocalTime" do
diff --git a/src/unit-tests/PostgresqlTypes/TimestamptzSpec.hs b/src/unit-tests/PostgresqlTypes/TimestamptzSpec.hs
--- a/src/unit-tests/PostgresqlTypes/TimestamptzSpec.hs
+++ b/src/unit-tests/PostgresqlTypes/TimestamptzSpec.hs
@@ -6,6 +6,7 @@
 import Test.Hspec
 import Test.QuickCheck
 import qualified UnitTests.Scripts as Scripts
+import Prelude
 
 spec :: Spec
 spec = do
@@ -30,6 +31,19 @@
             utcTime = Time.UTCTime day diffTime
             pgTimestamptz = Timestamptz.normalizeFromUtcTime utcTime
         Timestamptz.toUtcTime pgTimestamptz `shouldBe` utcTime
+
+    describe "refineFromUtcTime" do
+      it "accepts a UTCTime within range and precision" do
+        let day = Time.fromGregorian 2023 6 15
+            diffTime = Time.secondsToDiffTime 45045 -- 12:30:45
+            utcTime = Time.UTCTime day diffTime
+        fmap Timestamptz.toUtcTime (Timestamptz.refineFromUtcTime utcTime) `shouldBe` Just utcTime
+
+      it "rejects a UTCTime with sub-microsecond precision" do
+        let day = Time.fromGregorian 2023 6 15
+            diffTime = Time.picosecondsToDiffTime (45045 * 1_000_000_000_000 + 123_456_500)
+            utcTime = Time.UTCTime day diffTime
+        Timestamptz.refineFromUtcTime utcTime `shouldBe` Nothing
 
   describe "Accessors" do
     describe "toUtcTime" do
