diff --git a/COPYRIGHT b/COPYRIGHT
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1,4 +1,4 @@
-Copyright (C) 2009 John Goerzen <jgoerzen@complete.org>
+Copyright (C) 2009-2010 John Goerzen <jgoerzen@complete.org>
 
     This library is free software; you can redistribute it and/or
     modify it under the terms of the GNU Lesser General Public
diff --git a/Makefile b/Makefile
new file mode 100644
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+all: setup
+	@echo "Please use Cabal to build this package; not make."
+	./setup configure
+	./setup build
+
+setup: Setup.lhs
+	ghc --make -o setup Setup.lhs
+
+install: setup
+	./setup install
+
+clean:
+	-./setup clean
+	-runghc Setup.lhs clean
+
+.PHONY: test
+test: test-ghc test-hugs
+	@echo ""
+	@echo "All tests pass."
+
+test-hugs:
+	@echo " ****** Running hugs tests"
+	runhugs -98 +o -P$(PWD):$(PWD)/testsrc: testsrc/runtests.hs
+
+test-ghc:
+	@echo " ****** Building GHC tests"
+	runghc Setup.lhs configure -f buildtests
+	runghc Setup.lhs build
+	@echo " ****** Running GHC tests"
+	./dist/build/runtests/runtests
diff --git a/convertible.cabal b/convertible.cabal
--- a/convertible.cabal
+++ b/convertible.cabal
@@ -1,11 +1,16 @@
 Name: convertible
-Version: 1.0.8
+Version: 1.0.9
 License: LGPL
 Maintainer: John Goerzen <jgoerzen@complete.org>
 Author: John Goerzen
-Copyright: Copyright (c) 2009-2009 John Goerzen
+Copyright: Copyright (c) 2009-2010 John Goerzen
 license-file: COPYRIGHT
-extra-source-files: COPYING, utils/genCinstances.hs
+extra-source-files: COPYING, utils/genCinstances.hs,
+                    Makefile,
+                    testsrc/TestInfrastructure.hs,
+                    testsrc/TestMap.hs,
+                    testsrc/TestNum.hs,
+                    testsrc/TestTime.hs
 homepage: http://hackage.haskell.org/cgi-bin/hackage-scripts/package/convertible
 Category: Data
 synopsis: Typeclasses and instances for converting between types
diff --git a/testsrc/TestInfrastructure.hs b/testsrc/TestInfrastructure.hs
new file mode 100644
--- /dev/null
+++ b/testsrc/TestInfrastructure.hs
@@ -0,0 +1,17 @@
+{-
+Copyright (C) 2009 John Goerzen <jgoerzen@complete.org>
+
+All rights reserved.
+
+For license and copyright information, see the file COPYRIGHT
+-}
+module TestInfrastructure where
+import qualified Test.QuickCheck as QC
+import qualified Test.HUnit as HU
+import Test.HUnit.Tools
+
+q :: QC.Testable a => String -> a -> HU.Test
+q = qc2hu 250
+
+qverbose :: QC.Testable a => String -> a -> HU.Test
+qverbose = qc2huVerbose 250
diff --git a/testsrc/TestMap.hs b/testsrc/TestMap.hs
new file mode 100644
--- /dev/null
+++ b/testsrc/TestMap.hs
@@ -0,0 +1,24 @@
+{-
+Copyright (C) 2009 John Goerzen <jgoerzen@complete.org>
+
+All rights reserved.
+
+For license and copyright information, see the file COPYRIGHT
+-}
+
+module TestMap where
+import TestInfrastructure
+import Data.Convertible
+import Test.QuickCheck
+import Test.QuickCheck.Tools
+import Test.QuickCheck.Instances
+import qualified Data.Map as Map
+
+propListMap :: [(Int, Int)] -> Result
+propListMap x = safeConvert x @?= Right (Map.fromList x)
+
+propMapList :: Map.Map Int Int -> Result
+propMapList x = safeConvert x @?= Right (Map.toList x)
+
+allt = [q "[(Int, Int)] -> Map" propListMap,
+        q "Map -> [(Int, Int)]" propMapList]
diff --git a/testsrc/TestNum.hs b/testsrc/TestNum.hs
new file mode 100644
--- /dev/null
+++ b/testsrc/TestNum.hs
@@ -0,0 +1,104 @@
+{-
+Copyright (C) 2009 John Goerzen <jgoerzen@complete.org>
+
+All rights reserved.
+
+For license and copyright information, see the file COPYRIGHT
+-}
+
+module TestNum where
+import TestInfrastructure
+import Data.Convertible
+import Test.QuickCheck
+import Test.QuickCheck.Tools
+import Data.Word
+
+prop_int_to_integer :: Int -> Result
+prop_int_to_integer x =
+    safeConvert x @?= Right ((fromIntegral x)::Integer)
+
+prop_integer_to_int_pass :: Integer -> Property
+prop_integer_to_int_pass x =
+    (x <= fromIntegral (maxBound :: Int)) &&
+    (x >= fromIntegral (minBound :: Int)) ==> 
+                                          safeConvert x @?= Right ((fromIntegral x)::Int)
+
+prop_integer_to_word8 :: Integer -> Result
+prop_integer_to_word8 x =
+    safeConvert x @?= if x >= fromIntegral (minBound :: Word8) &&
+                         x <= fromIntegral (maxBound :: Word8)
+                      then Right ((fromIntegral x)::Word8)
+                      else Left $ ConvertError (show x) "Integer" "Word8" "Input value outside of bounds: (0,255)"
+
+prop_integer_to_word8_safe :: Integer -> Property
+prop_integer_to_word8_safe x =
+    x <= fromIntegral (maxBound :: Word8) &&
+    x >= fromIntegral (minBound :: Word8) ==>
+      safeConvert x @?= Right ((fromIntegral x)::Word8)
+
+prop_integer_to_word8_unsafe :: Integer -> Property
+prop_integer_to_word8_unsafe x =
+    x < fromIntegral (minBound :: Word8) ||
+    x > fromIntegral (maxBound :: Word8) ==>
+      ((safeConvert x)::ConvertResult Word8) @?= (Left $ ConvertError (show x) "Integer" "Word8" "Input value outside of bounds: (0,255)")
+
+prop_double_to_word8 :: Double -> Result
+prop_double_to_word8 x =
+    safeConvert x @?= if truncate x >= toInteger (minBound :: Word8) &&
+                         truncate x <= toInteger (maxBound :: Word8)
+                      then Right ((truncate x)::Word8)
+                      else Left $ ConvertError (show x) "Double" "Word8" "Input value outside of bounds: (0,255)"
+
+prop_double_to_word8_safe :: Double -> Property
+prop_double_to_word8_safe x =
+    x <= fromIntegral (maxBound :: Word8) &&
+    x >= fromIntegral (minBound :: Word8) ==>
+      safeConvert x @?= Right ((truncate x)::Word8)
+
+prop_double_to_word8_unsafe :: Double -> Property
+prop_double_to_word8_unsafe x =
+    truncate x < toInteger (minBound :: Word8) ||
+    truncate x > toInteger (maxBound :: Word8) ==>
+      ((safeConvert x)::ConvertResult Word8) @?= (Left $ ConvertError (show x) "Double" "Word8" "Input value outside of bounds: (0,255)")
+
+propIntDouble :: Int -> Result
+propIntDouble x =
+    safeConvert x @?= Right ((fromIntegral x)::Double)
+
+propIntChar :: Int -> Result
+propIntChar x =
+    safeConvert x @?= if x >= fromEnum (minBound :: Char) &&
+                         x <= fromEnum (maxBound :: Char)
+                      then Right ((toEnum x)::Char)
+                      else Left $ ConvertError (show x) "Int" "Char" "Input value outside of bounds: ('\\NUL','\\1114111')"
+
+propCharInt :: Int -> Property
+propCharInt x =
+    x >= fromEnum (minBound :: Char) && x <= fromEnum (maxBound :: Char) ==>
+    safeConvert c @?= Right ((fromEnum c)::Int)
+    where c = (toEnum x)::Char
+
+propIntIntegerInt :: Int -> Result
+propIntIntegerInt x =
+    Right x @=? do r1 <- ((safeConvert x)::ConvertResult Integer)
+                   ((safeConvert r1)::ConvertResult Int)
+    
+propDoubleRationalDouble :: Double -> Result
+propDoubleRationalDouble x =
+    Right x @=? do r1 <- ((safeConvert x)::ConvertResult Rational)
+                   ((safeConvert r1)::ConvertResult Double)
+
+allt = [q "Int -> Integer" prop_int_to_integer,
+        q "Integer -> Int (safe bounds)" prop_integer_to_int_pass,
+        q "Integer -> Word8 (general)" prop_integer_to_word8,
+        q "Integer -> Word8 (safe bounds)" prop_integer_to_word8_safe,
+        q "Integer -> Word8 (unsafe bounds)" prop_integer_to_word8_unsafe,
+        q "Double -> Word8 (general)" prop_double_to_word8,
+        q "Double -> Word8 (safe bounds)" prop_double_to_word8_safe,
+        q "Double -> Word8 (unsafe bounds)" prop_double_to_word8_unsafe,
+        q "Int -> Double" propIntDouble,
+        q "Int -> Char" propIntChar,
+        q "Char -> Int" propCharInt,
+        q "identity Int -> Integer -> Int" propIntIntegerInt,
+        q "identity Double -> Rational -> Double" propDoubleRationalDouble
+       ]
diff --git a/testsrc/TestTime.hs b/testsrc/TestTime.hs
new file mode 100644
--- /dev/null
+++ b/testsrc/TestTime.hs
@@ -0,0 +1,203 @@
+{-
+Copyright (C) 2009 John Goerzen <jgoerzen@complete.org>
+
+All rights reserved.
+
+For license and copyright information, see the file COPYRIGHT
+-}
+
+module TestTime where
+import TestInfrastructure
+import Data.Convertible
+import Test.QuickCheck
+import Test.QuickCheck.Tools
+import Test.QuickCheck.Instances
+import qualified System.Time as ST
+import Data.Time
+import Data.Time.Clock.POSIX
+import Data.Ratio
+import Foreign.C.Types
+
+instance Arbitrary ST.ClockTime where
+    arbitrary = do r1 <- arbitrary
+                   r2 <- sized $ \n -> choose (0, 1000000000000 - 1)
+                   return (ST.TOD r1 r2)
+    coarbitrary (ST.TOD a b) = coarbitrary a . coarbitrary b
+
+instance Arbitrary ST.CalendarTime where
+    arbitrary = do r <- arbitrary
+                   return $ convert (r::POSIXTime)
+
+instance Arbitrary NominalDiffTime where
+    arbitrary = do r <- arbitrary
+                   return $ convert (r::ST.ClockTime)
+
+instance Arbitrary UTCTime where
+    arbitrary = do r <- arbitrary
+                   return $ convert (r::POSIXTime)
+
+instance Arbitrary ZonedTime where
+    arbitrary = do r <- arbitrary
+                   return $ convert (r::POSIXTime)
+
+instance Eq ZonedTime where
+    a == b = zonedTimeToUTC a == zonedTimeToUTC b
+
+propCltCalt :: ST.ClockTime -> Result
+propCltCalt x =
+    safeConvert x @?= Right (ST.toUTCTime x)
+
+propCltCaltClt :: ST.ClockTime -> Result
+propCltCaltClt x =
+    Right x @=? do r1 <- ((safeConvert x)::ConvertResult ST.CalendarTime)
+                   safeConvert r1
+
+propCltPT :: ST.ClockTime -> Result
+propCltPT x@(ST.TOD y z) =
+    safeConvert x @?= Right (r::POSIXTime)
+    where r = fromRational $ fromInteger y + fromRational (z % 1000000000000)
+
+propPTClt :: POSIXTime -> Result
+propPTClt x =
+    safeConvert x @?= Right (r::ST.ClockTime)
+    where r = ST.TOD rsecs rpico
+          rsecs = floor x
+          rpico = truncate $ abs $ 1000000000000 * (x - (fromIntegral rsecs))
+
+propCaltPT :: ST.CalendarTime -> Result
+propCaltPT x =
+    safeConvert x @?= expected
+        where expected = do r <- safeConvert x
+                            (safeConvert (r :: ST.ClockTime))::ConvertResult POSIXTime
+
+propCltPTClt :: ST.ClockTime -> Result
+propCltPTClt x =
+    Right (toTOD x) @=? case do r1 <- (safeConvert x)::ConvertResult POSIXTime
+                                safeConvert r1
+                        of Left x -> Left x
+                           Right y -> Right $ toTOD y
+    where toTOD (ST.TOD x y) = (x, y)
+{-
+    Right x @=? do r1 <- (safeConvert x)::ConvertResult POSIXTime
+                   safeConvert r1
+-}
+
+propPTZTPT :: POSIXTime -> Result
+propPTZTPT x =
+    Right x @=? do r1 <- safeConvert x
+                   safeConvert (r1 :: ZonedTime)
+
+propPTCltPT :: POSIXTime -> Result
+propPTCltPT x =
+    Right x @=? do r1 <- (safeConvert x)::ConvertResult ST.ClockTime
+                   safeConvert r1
+
+propPTCalPT :: POSIXTime -> Result
+propPTCalPT x =
+    Right x @=? do r1 <- safeConvert x
+                   safeConvert (r1::ST.CalendarTime)
+
+propUTCCaltUTC :: UTCTime -> Result
+propUTCCaltUTC x =
+    Right x @=? do r1 <- safeConvert x
+                   safeConvert (r1::ST.CalendarTime)
+
+propPTUTC :: POSIXTime -> Result
+propPTUTC x =
+    safeConvert x @?= Right (posixSecondsToUTCTime x)
+propUTCPT :: UTCTime -> Result
+propUTCPT x =
+    safeConvert x @?= Right (utcTimeToPOSIXSeconds x)
+
+propCltUTC :: ST.ClockTime -> Result
+propCltUTC x =
+    safeConvert x @?= Right (posixSecondsToUTCTime . convert $ x)
+
+propZTCTeqZTCaltCt :: ZonedTime -> Result
+propZTCTeqZTCaltCt x =
+    route1 @=? route2
+    where route1 = (safeConvert x)::ConvertResult ST.ClockTime
+          route2 = do calt <- safeConvert x
+                      safeConvert (calt :: ST.CalendarTime)
+
+propCaltZTCalt :: ST.ClockTime -> Result
+propCaltZTCalt x =
+    Right x @=? do zt <- ((safeConvert calt)::ConvertResult ZonedTime)
+                   calt' <- ((safeConvert zt)::ConvertResult ST.CalendarTime)
+                   return (ST.toClockTime calt')
+    where calt = ST.toUTCTime x
+
+propCaltZTCalt2 :: ST.CalendarTime -> Result
+propCaltZTCalt2 x =
+    Right x @=? do zt <- safeConvert x
+                   safeConvert (zt :: ZonedTime)
+
+propZTCaltCtZT :: ZonedTime -> Result
+propZTCaltCtZT x =
+    Right x @=? do calt <- safeConvert x
+                   ct <- safeConvert (calt :: ST.CalendarTime)
+                   safeConvert (ct :: ST.ClockTime)
+
+propZTCtCaltZT :: ZonedTime -> Result
+propZTCtCaltZT x =
+    Right x @=? do ct <- safeConvert x
+                   calt <- safeConvert (ct :: ST.ClockTime)
+                   safeConvert (calt :: ST.CalendarTime)
+
+propZTCaltZT :: ZonedTime -> Result
+propZTCaltZT x =
+    Right x @=? do calt <- safeConvert x
+                   safeConvert (calt :: ST.CalendarTime)
+
+propZTCtCaltCtZT :: ZonedTime -> Result
+propZTCtCaltCtZT x =
+    Right x @=? do ct <- safeConvert x
+                   calt <- safeConvert (ct :: ST.ClockTime)
+                   ct' <- safeConvert (calt :: ST.CalendarTime)
+                   safeConvert (ct' :: ST.ClockTime)
+
+propUTCZT :: UTCTime -> Bool
+propUTCZT x =
+          x == zonedTimeToUTC (convert x)
+
+propUTCZTUTC :: UTCTime -> Result
+propUTCZTUTC x =
+    Right x @=? do r1 <- ((safeConvert x)::ConvertResult ZonedTime)
+                   safeConvert r1
+
+propNdtTdNdt :: NominalDiffTime -> Result
+propNdtTdNdt x =
+    Right x @=? do r1 <- ((safeConvert x)::ConvertResult ST.TimeDiff)
+                   safeConvert r1
+
+propPTCPT :: POSIXTime -> Result
+propPTCPT x =
+    Right testval @=? do r1 <- safeConvert testval
+                         safeConvert (r1 :: CTime)
+        where testval = (convert ((truncate x)::Integer))::POSIXTime      -- CTime doesn't support picosecs
+
+allt = [q "ClockTime -> CalendarTime" propCltCalt,
+        q "ClockTime -> CalendarTime -> ClockTime" propCltCaltClt,
+        q "ClockTime -> POSIXTime" propCltPT,
+        q "POSIXTime -> ClockTime" propPTClt,
+        q "CalendarTime -> POSIXTime" propCaltPT,
+        q "identity ClockTime -> POSIXTime -> ClockTime" propCltPTClt,
+        q "identity POSIXTime -> ClockTime -> POSIXTime" propPTCltPT,
+        q "identity POSIXTime -> ZonedTime -> POSIXTime" propPTZTPT,
+        q "identity POSIXTime -> CalendarTime -> POSIXTime" propPTCalPT,
+        q "identity UTCTime -> CalendarTime -> UTCTime" propUTCCaltUTC,
+        q "POSIXTime -> UTCTime" propPTUTC,
+        q "UTCTime -> POSIXTime" propUTCPT,
+        q "ClockTime -> UTCTime" propCltUTC,
+        q "ZonedTime -> ClockTime == ZonedTime -> CalendarTime -> ClockTime" propZTCTeqZTCaltCt,
+        q "identity CalendarTime -> ZonedTime -> CalendarTime" propCaltZTCalt,
+        q "identity CalendarTime -> ZonedTime -> CalenderTime, test 2" propCaltZTCalt2,
+        q "identity ZonedTime -> CalendarTime -> ZonedTime" propZTCaltZT,
+        q "ZonedTime -> CalendarTime -> ClockTime -> ZonedTime" propZTCaltCtZT,
+        q "ZonedTime -> ClockTime -> CalendarTime -> ZonedTime" propZTCtCaltZT,
+        q "ZonedTime -> ColckTime -> CalendarTime -> ClockTime -> ZonedTime" propZTCtCaltCtZT,
+        q "UTCTime -> ZonedTime" propUTCZT,
+        q "UTCTime -> ZonedTime -> UTCTime" propUTCZTUTC,
+        q "identity NominalDiffTime -> TimeDiff -> NominalDiffTime" propNdtTdNdt,
+        q "identity POSIXTime -> CTime -> POSIXTime" propPTCPT
+       ]
