diff --git a/Trace/Hpc/Mix.hs b/Trace/Hpc/Mix.hs
--- a/Trace/Hpc/Mix.hs
+++ b/Trace/Hpc/Mix.hs
@@ -22,7 +22,7 @@
         )
   where
 
-import Data.List
+import Data.List (intercalate)
 import Data.Maybe (catMaybes, fromMaybe)
 import Data.Time (UTCTime)
 import Data.Tree
diff --git a/Trace/Hpc/Tix.hs b/Trace/Hpc/Tix.hs
--- a/Trace/Hpc/Tix.hs
+++ b/Trace/Hpc/Tix.hs
@@ -18,7 +18,7 @@
 
 import System.FilePath (replaceExtension)
 
-import Trace.Hpc.Util (Hash, catchIO)
+import Trace.Hpc.Util (Hash, catchIO, readFileUtf8, writeFileUtf8)
 
 -- | 'Tix' is the storage format for our dynamic information about
 -- what boxes are ticked.
@@ -45,17 +45,15 @@
 -- | Read a @.tix@ File.
 readTix :: String
         -> IO (Maybe Tix)
-readTix tix_filename =
-  catchIO (do contents <- readFile $ tix_filename
-              return $ Just $ read contents)
-          (\ _ -> return $ Nothing)
+readTix tixFilename =
+  catchIO (fmap (Just . read) $ readFileUtf8 tixFilename)
+          (const $ return Nothing)
 
 -- | Write a @.tix@ File.
 writeTix :: String
          -> Tix
          -> IO ()
-writeTix name tix =
-  writeFile name (show tix)
+writeTix name tix = writeFileUtf8 name (show tix)
 
 -- | 'getTixFullName' takes a binary or @.tix@-file name,
 -- and normalizes it into a @.tix@-file name.
diff --git a/Trace/Hpc/Util.hs b/Trace/Hpc/Util.hs
--- a/Trace/Hpc/Util.hs
+++ b/Trace/Hpc/Util.hs
@@ -1,6 +1,8 @@
 {-# LANGUAGE CPP #-}
-#ifdef __GLASGOW_HASKELL__
+#if __GLASGOW_HASKELL__ >= 704
 {-# LANGUAGE Safe #-}
+#elif __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
 #endif
 -----------------------------------------
 -- Andy Gill and Colin Runciman, June 2006
@@ -16,13 +18,19 @@
        , HpcHash(..)
        , Hash
        , catchIO
+       , readFileUtf8
+       , writeFileUtf8
        ) where
 
+import Control.DeepSeq (deepseq)
 import qualified Control.Exception as Exception
 import Data.List(foldl')
 import Data.Char (ord)
 import Data.Bits (xor)
 import Data.Word
+import System.Directory (createDirectoryIfMissing)
+import System.FilePath (takeDirectory)
+import System.IO
 
 -- | 'HpcPos' is an Hpc local rendition of a Span.
 data HpcPos = P !Int !Int !Int !Int deriving (Eq, Ord)
@@ -51,13 +59,18 @@
 instance Read HpcPos where
   readsPrec _i pos = [(toHpcPos (read l1,read c1,read l2,read c2),after)]
       where
-         (before,after)   = span (/= ',') pos
+         (before,after) = span (/= ',') pos
+         parseError a   = error $ "Read HpcPos: Could not parse: " ++ show a
          (lhs0,rhs0)    = case span (/= '-') before of
                                (lhs,'-':rhs) -> (lhs,rhs)
                                (lhs,"")      -> (lhs,lhs)
-                               _ -> error "bad parse"
-         (l1,':':c1)      = span (/= ':') lhs0
-         (l2,':':c2)      = span (/= ':') rhs0
+                               _ -> parseError before
+         (l1,c1)        = case span (/= ':') lhs0 of
+                            (l,':':c) -> (l,c)
+                            _ -> parseError lhs0
+         (l2,c2)        = case span (/= ':') rhs0 of
+                            (l,':':c) -> (l,c)
+                            _ -> parseError rhs0
 
 ------------------------------------------------------------------------------
 
@@ -112,3 +125,23 @@
 
 catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a
 catchIO = Exception.catch
+
+
+-- | Read a file strictly, as opposed to how `readFile` does it using lazy IO, but also
+-- disregard system locale and assume that the file is encoded in UTF-8. Haskell source
+-- files are expected to be encoded in UTF-8 by GHC.
+readFileUtf8 :: FilePath -> IO String
+readFileUtf8 filepath =
+  withBinaryFile filepath ReadMode $ \h -> do
+    hSetEncoding h utf8  -- see #17073
+    contents <- hGetContents h
+    contents `deepseq` hClose h -- prevent lazy IO
+    return contents
+
+-- | Write file in UTF-8 encoding. Parent directory will be created if missing.
+writeFileUtf8 :: FilePath -> String -> IO ()
+writeFileUtf8 filepath str = do
+  createDirectoryIfMissing True (takeDirectory filepath)
+  withBinaryFile filepath WriteMode $ \h -> do
+    hSetEncoding h utf8  -- see #17073
+    hPutStr h str
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,10 @@
 # Changelog for [`hpc` package](http://hackage.haskell.org/package/hpc)
 
+## 0.6.1.0  *October 2019*
+
+  * Addition of `readFileUtf8` and `writeFileUtf8` functions.
+  * Ensure `.tix` files read and written in UTF-8, regadless of the system locale.
+
 ## 0.6.0.3  *May 2016*
 
   * Bundled with GHC 8.0.1
diff --git a/hpc.cabal b/hpc.cabal
--- a/hpc.cabal
+++ b/hpc.cabal
@@ -1,11 +1,11 @@
 name:         hpc
-version:      0.6.0.3
+version:      0.6.1.0
 -- NOTE: Don't forget to update ./changelog.md
 license:      BSD3
 license-file: LICENSE
 author:       Andy Gill
 maintainer:   ghc-devs@haskell.org
-bug-reports:  http://ghc.haskell.org/trac/ghc/newticket?component=Code%20Coverage
+bug-reports:  https://gitlab.haskell.org/ghc/ghc/issues/new
 category:     Control
 synopsis:     Code Coverage Library for Haskell
 build-type:   Simple
@@ -35,9 +35,10 @@
         Trace.Hpc.Reflect
 
     Build-Depends:
-        base       >= 4.4.1 && < 4.10,
-        containers >= 0.4.1 && < 0.6,
-        directory  >= 1.1   && < 1.3,
+        base       >= 4.4.1 && < 4.18,
+        containers >= 0.4.1 && < 0.7,
+        deepseq    >= 1.1   && < 1.5,
+        directory  >= 1.1   && < 1.4,
         filepath   >= 1     && < 1.5,
-        time       >= 1.2   && < 1.7
+        time       >= 1.2   && < 1.13
     ghc-options: -Wall
