diff --git a/ip.cabal b/ip.cabal
--- a/ip.cabal
+++ b/ip.cabal
@@ -1,5 +1,5 @@
 name:                ip
-version:             0.9.1
+version:             0.9.2
 synopsis:            Library for IP and MAC addresses
 homepage:            https://github.com/andrewthad/haskell-ip#readme
 license:             BSD3
diff --git a/src/Net/IPv6/Text.hs b/src/Net/IPv6/Text.hs
--- a/src/Net/IPv6/Text.hs
+++ b/src/Net/IPv6/Text.hs
@@ -1,18 +1,45 @@
 {-# LANGUAGE BangPatterns #-}
 
 module Net.IPv6.Text
-  ( parser
+  ( encode
+  , parser
   ) where
 
 import Prelude hiding (print)
 import Net.Types (IPv6(..))
+import Net.IPv6 (toWord16s)
 import Data.Bits
+import Data.List (intercalate, group)
 import Data.Word
 import Control.Applicative
+import Data.Text (Text)
 import qualified Data.Text as Text
 import qualified Data.Attoparsec.Text as Atto
-import Debug.Trace
+import Numeric (showHex)
 
+-- | Encodes the IP, using zero-compression on the leftmost-longest string of
+-- zeroes in the address.
+encode :: IPv6 -> Text
+encode ip = toText [w1, w2, w3, w4, w5, w6, w7, w8]
+  where
+  (w1, w2, w3, w4, w5, w6, w7, w8) = toWord16s ip
+  toText ws = Text.pack $ intercalate ":" $ expand 0 longestZ grouped
+    where
+    expand _ 8 _ = ["::"]
+    expand _ _ [] = []
+    expand i longest ((x, len):wsNext)
+        -- zero-compressed group:
+        | x == 0 && len == longest =
+            -- first and last need an extra colon since there's nothing
+            -- to concat against
+            (if i == 0 || (i+len) == 8 then ":" else "")
+            : expand (i+len) 0 wsNext
+        -- normal group:
+        | otherwise = replicate len (showHex x "") ++ expand (i+len) longest wsNext
+    longestZ = maximum . (0:) . map snd . filter ((==0) . fst) $ grouped
+    grouped = map (\x -> (head x, length x)) (group ws)
+
+        
 parser :: Atto.Parser IPv6
 parser = do
   s <- start
@@ -41,27 +68,12 @@
             _ <- Atto.anyChar -- should be a colon
             if colonIndex == currentIndex
               then fmap ResWord Atto.hexadecimal <|> pure ResDone
-                -- do
-                -- md <- Atto.peekChar
-                -- case md of
-                --   Nothing -> return ResDone
-                --   Just d -> fmap ResWord Atto.hexadecimal <|> pure
-								-- 		then return ResColon
-								-- 		else fmap ResWord Atto.hexadecimal
               else do
                 d <- Atto.peekChar'
                 if d == ':'
                   then return ResColon
                   else fmap ResWord Atto.hexadecimal
-              -- else if colonIndex == currentIndex
-              --   then fmap ResWord Atto.hexadecimal <|> (ResDone <$ Atto.anyChar)
-              --   else fmap ResWord Atto.hexadecimal
           else return ResDone
-    -- fmap ResWord Atto.hexadecimal <|> (ResColon <$ Atto.char ':')
-    -- ) <|> pure ResDone
-    -- r <- (do _ <- Atto.char ':'
-    --          fmap ResWord Atto.hexadecimal <|> (ResColon <$ Atto.char ':')
-    --      ) <|> pure ResDone
     case r of
       ResDone -> pure (S colonIndex currentIndex ws)
       ResColon -> if alreadySet colonIndex
@@ -70,7 +82,12 @@
       ResWord w -> restrictTo16 msg w >> go colonIndex (currentIndex + 1) (w : ws)
 
 toIPv6 :: S -> Maybe IPv6
-toIPv6 (S colonIndex total input) = go 0 0 input
+toIPv6 (S colonIndex total input) = case compare total 8 of
+  EQ -> if colonIndex == (-1)
+    then go 0 0 input
+    else Nothing
+  GT -> Nothing
+  LT -> go 0 0 input
   where
   revColonIndex = total - colonIndex
   spacesToSkip = 8 - total
@@ -111,9 +128,9 @@
 --   The third is a reversed list of the 16s
 --   that comprise the ipv6 address.
 data S = S
-  { sDoubleColon :: {-# UNPACK #-} !Int
-  , sTotal :: {-# UNPACK #-} !Int
-  , sRevWords :: ![Word64]
+  { _sDoubleColon :: {-# UNPACK #-} !Int
+  , _sTotal :: {-# UNPACK #-} !Int
+  , _sRevWords :: ![Word64]
   } deriving (Show,Read)
 
 data Res
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -10,6 +10,7 @@
 import Numeric                              (showHex)
 import Test.QuickCheck.Property             (failed,succeeded,Result(..))
 import Data.Word
+import Data.Bifunctor
 
 import Net.Types (IPv4(..),IPv4Range(..),Mac(..),IPv6(..))
 import qualified Data.Text as Text
@@ -84,6 +85,8 @@
       ]
     , testGroup "IPv6 encode/decode"
       [ testCase "Parser Test Cases" testIPv6Parser
+      , testCase "Encode test cases" testIPv6Encode
+      , testCase "Parser Failure Test Cases" testIPv6ParserFailure
       ]
     ]
   , testGroup "IP Range Operations"
@@ -184,6 +187,56 @@
         (IPv6Text.parser <* AT.endOfInput)
         (Text.pack str)
       )
+
+testIPv6ParserFailure :: Assertion
+testIPv6ParserFailure = do
+  go "1111:2222:3333:4444:5555:6666::7777:8888"
+  go "1111:2222:3333:4444:5555:6666:7777:8888:9999"
+  go "1111:2222:3333:4444:5555:6666:7777:8888::9999"
+  where
+  go str =
+    Left ()
+    @?= bimap (\_ -> ()) HexIPv6
+      (AT.parseOnly
+        (IPv6Text.parser <* AT.endOfInput)
+        (Text.pack str)
+      )
+
+testIPv6Encode :: Assertion
+testIPv6Encode = do
+
+    -- degenerate cases:
+    "::" `roundTripsTo` "::"
+    "1234::" `roundTripsTo` "1234::"
+    "::1234" `roundTripsTo` "::1234"
+
+    -- zero-compression works:
+    "1234:1234:0000:0000:0000:0000:3456:3434" `roundTripsTo` "1234:1234::3456:3434"
+
+    -- picks first case:
+    "1234:0000:1234:0000:1234:0000:0123:1234" `roundTripsTo` "1234::1234:0:1234:0:123:1234"
+
+    -- picks longest case:
+    "1234:0000:1234:0000:0:0000:0123:1234" `roundTripsTo` "1234:0:1234::123:1234"
+
+    -- can exclude all but first and last:
+    "1234::1234" `roundTripsTo` "1234::1234"
+
+    -- prefers leftmost part to zero-compress:
+    "1:2:0:0:5::8" `roundTripsTo` "1:2::5:0:0:8"
+
+    -- can work with no zeroes:
+    "1:2:3:4:5:6:7:8" `roundTripsTo` "1:2:3:4:5:6:7:8"
+
+    -- works with only first or last:
+    "::2:3:4:5:6:7:8" `roundTripsTo` "::2:3:4:5:6:7:8"
+    "1:2:3:4:5:6:7::" `roundTripsTo` "1:2:3:4:5:6:7::"
+
+   where
+   roundTripsTo s sExpected =
+     case AT.parseOnly (IPv6Text.parser <* AT.endOfInput) (Text.pack s) of
+        Right result -> IPv6Text.encode result @?= Text.pack sExpected
+        Left failMsg -> fail failMsg -- parse shouldn't fail here
 
 textBadIPv4 :: [String]
 textBadIPv4 =
