diff --git a/README b/README
--- a/README
+++ b/README
@@ -28,112 +28,15 @@
 If you want to record samples at a different rate than one per minute, you
 will have to pass the "--sample-rate" parameter to arbtt-capture. 
 
-Configuration
-=============
-
-Once arbtt-capture is running, it will start recording, without further
-configuration. The configuration is only needed to do an analysis of the
-recorded data. Thus, if you improve your categorization later, it will
-apply even to previous data samples!
-
-The configuration file needs to be placed in ~/.arbtt/categorize.cfg. An
-example file is included, which should be more enlighting than this rather
-formal description.
-
-Whitespace is non-significant and Haskell-style comments are allowed.
-
-Here is grammar of the file:
-
-Rules := [ "aliases" AliasSpec ] Rule ( ("," Rule)* | (";" Rule)* )
-
-Rule := "{" Rules "}" | Cond "==>" Rule | "if" Cond "then" Rule "else" Rule
-      | "tag" Tag
-
-Cond := "(" Cond ")" | "!" Cond | Cond "&&" Cond | Cond "||" Cond 
-      | "$" SVar "==" String | "$" SVar "/=" String
-      | "$" SVar "=~" "/" Regex "/" |
-      | "$" NVar NOp Number
-      | "$" TVar NOp TSpec
-      | "$" BVar
-      | "current window" Cond
-      | "any window" Cond
-
-NOp := "<=" | "<" | "==" | ">" | ">="
-
-SVar := "title" | "program"
-NVar := "idle"
-BVar := "active"
-TVar := "time" | "sampleage"
-
-The variables $title, $program, $active refer to the "window in scope". At
-first, no window is in scope. Only when evaluating the condition passed to
-"current window" or "any window", this changes.
-
-For "current window", the currently active window is in scopre. If there is
-no such window, the condition is false.
-
-For "any window", the condition is applied to each window, and if any
-window matches the condition, the result is true. If it matches more than
-one window, of which match the variables $1,.. (see below) will be taken.
-
-Tags are not enclosed in quotation marks, and contain only letters, dashes
-or underscores (e.g. work).
-
-They may be prepended with a category,
-separated by a colon (e.g. Project:arbtt). This category can later be used
-for pie chart like statistics. For each category, no more than one tags can
-be matched per sample. If multiple tags with the same category are
-generated by the rules, only the first one will be taken.
-
-A tag can also interpolate variables, including in the category part.
-Available variables are:
- $1, $2,..  Matches from the last successfully applied regular expression
-            in the enclosing condition
- $current.title
- $current.program
-	    The title or the name of the currently active program. If no
-	    program happens to be active, this tag will be ignored.
-
-The variable $time refers to the time-of-day of the sample (i.e. the time
-since 0:00 that day), while $sampleage refers to the timespan from when the
-sample was created until now, the time of evaluating the statistics. They
-can be compared with expressions of the type "hh:mm", for example:
- $time >=  8:00 && $time < 12:00 ==> tag time-of-day:morning,
-
-Statistics
-==========
-
-Run the statistics program arbtt-stats with the parameter --help to get an
-overview of the options.
-
-By default, any sample with the tag "inactive" are excluded from the
-statistics, and tags with a percentage lower than one will not be shown.
-
-The tags that affect the selection (-x, -o, --also-inactive) of samples can
-be mixed with the report options (-m) and the reports (-i, -t, -c), but
-will apply to all reports.
-
-The flag --filter (-f) allows you to sepecify an arbitrary complex
-condition using the same syntax for conditions that you can use in the
-categorize.cfg file.
-
-Some useful examples:
-
-  # Only consider the time when I was programming in Haskell 
-  arbtt-stats -o Editing-Haskell
-
-  # Tell me what evolution folders I spend my time in when I actually do
-  # work with e-Mail
-  arbtt-stats -o Program:evolution -c Evo-Folder
-
-  # Generate statistics about the last hour
-  arbtt-stats -f '$sampleage < 1:00'
-
-Data Dump
-=========
+Documentation
+============
 
-If you need to inspect the recorded sample, for example to think about
-interesting things to match, use arbtt-dump.
+Full documentation is now provided in the user manual in the doc/
+directory. If you have the docbook xsl toolchain installed, you can
+generate the HTML documentation by entering "make" in that directory.
+Otherwise, you can use the online version at
+http://darcs.nomeata.de/arbtt/doc/users_guide/
+Beware that this will also reflect the latest version.
 
 Development
 ===========
@@ -146,15 +49,13 @@
 
  * A graphical viewer that allows you to expore the tags in an appealing,
    interactive way. Possibly based on the Charts haskell library.
- * A better representation of the data in the log, to keep the size and 
-   parsing speed down. 
  * Looking forward and backwards in time when writing rules. (Information
    is already passed to the categorizing function, but not exposed to the
    syntax).
  * $total_idle time, which is the maximum idle time until it is reset. This
    would allow the user to catch the idle times more exactly.
  * Rules based on day of time, to create tags for worktime, weekend, late
-   at night.
+   at night. (Partially done)
  * Statistics based on time, to visualize trends.
  * Possibly more data sources?
 
diff --git a/arbtt.cabal b/arbtt.cabal
--- a/arbtt.cabal
+++ b/arbtt.cabal
@@ -1,5 +1,5 @@
 name:               arbtt
-version:            0.4
+version:            0.4.1
 license:            GPL
 license-file:       LICENSE
 category:           Desktop
@@ -37,26 +37,29 @@
         TimeLog
         UpgradeLog1
         Graphics.X11.XScreenSaver
+        System.Locale.SetLocale
 
 executable arbtt-stats
     main-is:            stats-main.hs
     hs-source-dirs:     src
     build-depends:
-        base == 4.*, parsec == 2.*, containers, pcre-light, tabular, setlocale
+        base == 4.*, parsec == 2.*, containers, pcre-light
     other-modules:
         Data
         Categorize
         TimeLog
         Stats
+        System.Locale.SetLocale
 
 executable arbtt-dump
     main-is:            dump-main.hs
     hs-source-dirs:     src
     build-depends:
-        base == 4.*, parsec == 2.*, containers, setlocale
+        base == 4.*, parsec == 2.*, containers
     other-modules:
         Data
         TimeLog
+        System.Locale.SetLocale
 
 source-repository head
     type:     darcs
diff --git a/src/Capture.hs b/src/Capture.hs
--- a/src/Capture.hs
+++ b/src/Capture.hs
@@ -8,21 +8,26 @@
 import Data.Maybe
 import Control.Applicative
 import Data.Time.Clock
+import System.IO
 
 captureData :: IO CaptureData
 captureData = do
-	dpy <- openDisplay ":0"
+	dpy <- openDisplay ""
         xSetErrorHandler
 	let rwin = defaultRootWindow dpy
 
 	a <- internAtom dpy "_NET_CLIENT_LIST" False
 	p <- getWindowProperty32 dpy a rwin
-	let cwins = maybe [] (map fromIntegral) p
 
+	wins <- case p of
+		Just wins -> return (map fromIntegral wins)
+		Nothing -> do hPutStrLn stderr "arbtt: ERROR: No _NET_CLIENT_LIST set for the root window"
+		              return []
+
 	(fsubwin,_) <- getInputFocus dpy
-	fwin <- followTreeUntil dpy (`elem` cwins) fsubwin
+	fwin <- followTreeUntil dpy (`elem` wins) fsubwin
 
-	winData <- mapM (\w -> (,,) (w == fwin) <$> getWindowTitle dpy w <*> getProgramName dpy w) cwins
+	winData <- mapM (\w -> (,,) (w == fwin) <$> getWindowTitle dpy w <*> getProgramName dpy w) wins
 
 	it <- fromIntegral `fmap` getXIdleTime dpy
 
@@ -30,10 +35,10 @@
 	return $ CaptureData winData it
 
 getWindowTitle :: Display -> Window -> IO String
-getWindowTitle dpy w = fmap (fromMaybe "") $ fetchName dpy w
+getWindowTitle dpy = fmap (fromMaybe "") . fetchName dpy
 
 getProgramName :: Display -> Window -> IO String
-getProgramName dpy w = fmap resName $ getClassHint dpy w
+getProgramName dpy = fmap resName . getClassHint dpy
 
 -- | Follows the tree of windows up until the condition is met or the root
 -- window is reached.
@@ -43,4 +48,3 @@
              | otherwise = do (r,p,_) <- queryTree dpy w
 	                      if p == 0 then return w
 			                else go p 
-
diff --git a/src/Categorize.hs b/src/Categorize.hs
--- a/src/Categorize.hs
+++ b/src/Categorize.hs
@@ -2,7 +2,6 @@
 
 import Data
 
-import Data.Maybe
 import qualified Text.Regex.PCRE.Light.Char8 as RE
 import qualified Data.Map as M
 import Control.Monad
@@ -45,16 +44,15 @@
 	case parse (do {r <- parseRules; eof ; return r}) filename content of
 	  Left err -> do
 	  	putStrLn "Parser error:"
-		putStrLn (show err)
+		print err
 		exitFailure
 	  Right cat -> return ((fmap . fmap) (mkSecond (postpare . cat)) . prepare time)
 
 applyCond :: String -> TimeLog (Ctx, ActivityData) -> TimeLog (Ctx, ActivityData)
 applyCond s = 
 	case parse (do {c <- parseCond; eof ; return c}) "commad line parameter" s of
-	  Left err -> do
-		error (show err)
-	  Right c -> filter (isJust . c . fst . tlData)
+	  Left err -> error (show err)
+	  Right c  -> filter (isJust . c . fst . tlData)
 
 prepare :: UTCTime -> TimeLog CaptureData -> TimeLog Ctx
 prepare time tl = go' [] tl tl
@@ -67,7 +65,7 @@
 -- | Here, we filter out tags appearing twice, and make sure that only one of
 --   each category survives
 postpare :: ActivityData -> ActivityData
-postpare = nubBy $ go
+postpare = nubBy go
   where go (Activity (Just c1) _) (Activity (Just c2) _) = c1 == c2
         go a1                     a2                     = a1 == a2
 
@@ -184,7 +182,7 @@
 		     time <- parseTime
 		     return $ checkTimeCmp op varname time
 		, do guard $ varname == "active"
-		     return $ checkActive
+		     return checkActive
 		]
 	, do reserved lang "current window"
 	     cond <- parseCond
@@ -208,7 +206,7 @@
 parseTime :: Parser NominalDiffTime
 parseTime = fmap fromIntegral $ lexeme lang $ do
                h <- digitToInt <$> digit
-	       mh <- optionMaybe (do{ digitToInt <$> digit })
+	       mh <- optionMaybe (digitToInt <$> digit)
 	       char ':'
                m1 <- digitToInt <$> digit
                m2 <- digitToInt <$> digit
diff --git a/src/Stats.hs b/src/Stats.hs
--- a/src/Stats.hs
+++ b/src/Stats.hs
@@ -5,8 +5,6 @@
 import Data.Maybe
 import Data.List
 import Data.Ord
-import Text.Tabular
-import qualified Text.Tabular.AsciiArt as TA
 import Text.Printf
 import qualified Data.Map as M
 import qualified Data.Set as S
@@ -24,6 +22,14 @@
 data ReportOption = MinPercentage Double
         deriving Eq
 
+-- Data format semantically representing the result of a report, including the
+-- title
+data ReportResults =
+	ListOfFields String [(String, String)]
+	| ListOfTimePercValues String [(String, String, Double)]
+	| PieChartOfTimePercValues  String [(String, String, Double)]
+
+
 applyFilters :: [Filter] -> TimeLog (Ctx, ActivityData) -> TimeLog (Ctx, ActivityData)
 applyFilters filters tle = 
         foldr (\flag -> case flag of 
@@ -44,12 +50,12 @@
 	{ firstDate :: UTCTime
 	, lastDate  :: UTCTime
 	, timeDiff :: NominalDiffTime
-	, totalTimeRec :: Integer
-	, totalTimeSel :: Integer
+	, totalTimeRec :: NominalDiffTime
+	, totalTimeSel :: NominalDiffTime
 	, fractionRec :: Double
 	, fractionSel :: Double
 	, fractionSelRec :: Double
-	, sums :: M.Map Activity Integer
+	, sums :: M.Map Activity NominalDiffTime
 	, allTags :: TimeLog (Ctx, ActivityData)
 	, tags :: TimeLog (Ctx, ActivityData)
 	}
@@ -60,25 +66,25 @@
 	  { firstDate = tlTime (head allTags)
 	  , lastDate = tlTime (last allTags)
 	  , timeDiff = diffUTCTime (lastDate c) (firstDate c)
-	  , totalTimeRec = sum (map tlRate allTags)
-	  , totalTimeSel = sum (map tlRate tags)
-	  , fractionRec = fromIntegral (totalTimeRec c) / (realToFrac (timeDiff c) * 1000)
-	  , fractionSel = fromIntegral (totalTimeSel c) / (realToFrac (timeDiff c) * 1000)
-	  , fractionSelRec = fromIntegral (totalTimeSel c) / fromIntegral (totalTimeRec c)
+	  , totalTimeRec = fromInteger (sum (map tlRate allTags))/1000
+	  , totalTimeSel = fromInteger (sum (map tlRate tags))/1000
+	  , fractionRec = realToFrac (totalTimeRec c) / (realToFrac (timeDiff c))
+	  , fractionSel = realToFrac (totalTimeSel c) / (realToFrac (timeDiff c))
+	  , fractionSelRec = realToFrac (totalTimeSel c) / realToFrac (totalTimeRec c)
 	  , sums = sumUp tags
 	  , allTags
 	  , tags
 	  } in c
 
 -- | Sums up each occurence of an 'Activity', weighted by the sampling rate
-sumUp :: TimeLog (Ctx, ActivityData) -> M.Map Activity Integer
-sumUp = foldr go (M.empty) 
+sumUp :: TimeLog (Ctx, ActivityData) -> M.Map Activity NominalDiffTime
+sumUp = foldr go M.empty
   where go tl m = foldr go' m (snd (tlData tl))
-          where go' act = M.insertWith (+) act (tlRate tl)
+          where go' act = M.insertWith (+) act (fromInteger (tlRate tl)/1000)
 
 
 listCategories :: TimeLog (Ctx, ActivityData) -> [Category]
-listCategories = S.toList . foldr go (S.empty) 
+listCategories = S.toList . foldr go S.empty
   where go tl m = foldr go' m (snd (tlData tl))
           where go' (Activity (Just cat) _) = S.insert cat
 	        go' _                       = id
@@ -88,95 +94,106 @@
 
 putReport :: [ReportOption] -> Calculations -> Report -> IO ()
 putReport opts c EachCategory = putReports opts c (map Category (listCategories (tags c)))
-putReport opts c r = let (h,t) = reportToTable opts c r
-  			in putStrLnUnderlined h >> putStr (TA.render id id id t)
+putReport opts c r = renderReport $ reportToTable opts c r
 
-reportToTable :: [ReportOption] -> Calculations -> Report -> (String, Table String String String)
+reportToTable :: [ReportOption] -> Calculations -> Report -> ReportResults
 reportToTable opts (Calculations {..}) r = case r of
- 	GeneralInfos -> ("General Information",
-		empty ^..^ colH "Value"
-		+.+ row "FirstRecord"
-		        [show firstDate]
-		+.+ row "LastRecord"
-		        [show lastDate]
-		+.+ row "Number of records"
-		        [show (length allTags)]
-		+.+ row "Total time recorded"
-		        [formatSeconds (fromIntegral totalTimeRec / 1000)]
-		+.+ row "Total time selected"
-		        [formatSeconds (fromIntegral totalTimeSel / 1000)]
-		+.+ row "Fraction of total time recorded"
-		        [printf "%3.0f%%" (fractionRec * 100) ]
-		+.+ row "Fraction of total time selected"
-		        [printf "%3.0f%%" (fractionSel * 100) ]
-		+.+ row "Fraction of recorded time selected"
-		        [printf "%3.0f%%" (fractionSelRec * 100) ]
-		)
+ 	GeneralInfos -> ListOfFields "General Information" $
+		[ ("FirstRecord", show firstDate)
+		, ("LastRecord",  show lastDate)
+		, ("Number of records", show (length allTags))
+		, ("Total time recorded",  showTimeDiff totalTimeRec)
+		, ("Total time selected",  showTimeDiff totalTimeSel)
+		, ("Fraction of total time recorded", printf "%3.0f%%" (fractionRec * 100))
+		, ("Fraction of total time selected", printf "%3.0f%%" (fractionSel * 100))
+		, ("Fraction of recorded time selected", printf "%3.0f%%" (fractionSelRec * 100))
+		]
 
- 	TotalTime -> ("Total time per tag",
-		foldr (\(tag,time) ->
-		      let perc = fromIntegral time/fromIntegral totalTimeSel*100 in
-		      if perc >= minPercentage
-	 	      then (+.+ row (show tag) [
-		      		formatSeconds (fromIntegral time/1000),
-				printf "%.1f%%" perc])
-		      else id
-		      )
-		(empty ^..^ colH "Time" ^..^ colH "Percentage")
-		(sortBy (comparing snd) $ M.toList sums)
-		)
+ 	TotalTime -> ListOfTimePercValues "Total time per tag" $
+		mapMaybe (\(tag,time) ->
+		      let perc = realToFrac time/realToFrac totalTimeSel in
+		      if perc*100 >= minPercentage
+	 	      then Just $ ( show tag
+		                  , showTimeDiff time
+				  , perc)
+		      else Nothing
+		      ) $
+		reverse $
+		sortBy (comparing snd) $
+		M.toList sums
 	
-	Category cat -> ("Statistics for category " ++ cat,
+	Category cat -> PieChartOfTimePercValues ("Statistics for category " ++ cat) $
          	let filteredSums = M.filterWithKey (\a _ -> isCategory cat a) sums
 	            uncategorizedTime = totalTimeSel - M.fold (+) 0 filteredSums
-          	    tooSmallSums = M.filter (\t -> fromIntegral t / fromIntegral totalTimeSel * 100 < minPercentage) filteredSums
+          	    tooSmallSums = M.filter (\t -> realToFrac t / realToFrac totalTimeSel * 100 < minPercentage) filteredSums
 	  	    tooSmallTimes = M.fold (+) 0 tooSmallSums
 		in
 
-		(if uncategorizedTime > 0
-		then (+.+ row "(unmatched time)" [
-                        formatSeconds (fromIntegral uncategorizedTime/1000),
-                        printf "%.1f%%" (fromIntegral uncategorizedTime/fromIntegral totalTimeSel*100::Double)])
-		else id
-		)
-		.	
+		mapMaybe (\(tag,time) ->
+		      let perc = realToFrac time/realToFrac totalTimeSel in
+		      if perc*100 >= minPercentage
+	 	      then Just ( show tag
+		      		, showTimeDiff time
+				, perc)
+		      else Nothing
+		      )
+          	      (reverse $ sortBy (comparing snd) $ M.toList filteredSums)
+		++
 		(
 		if tooSmallTimes > 0
-		then (+.+ row (printf "(%d entries omitted)" (M.size tooSmallSums)) [
-                        formatSeconds (fromIntegral tooSmallTimes/1000),
-                        printf "%.1f%%" (fromIntegral tooSmallTimes/fromIntegral totalTimeSel*100::Double) ])
-		else id
+		then [( printf "(%d entries omitted)" (M.size tooSmallSums)
+		      , showTimeDiff tooSmallTimes
+		      , realToFrac tooSmallTimes/realToFrac totalTimeSel
+		      )]
+		else []
 		)
-		$	
-		foldr (\(tag,time) ->
-		      let perc = fromIntegral time/fromIntegral totalTimeSel*100 in
-		      if perc >= minPercentage
-	 	      then (+.+ row (show tag) [
-		      		formatSeconds (fromIntegral time/1000),
-				printf "%.1f%%" perc])
-		      else id
-		      )
-
-		(empty ^..^ colH "Time" ^..^ colH "Percentage")
-
-          	(sortBy (comparing snd) $ M.toList filteredSums)
+		++	
+		(if uncategorizedTime > 0
+		then [( "(unmatched time)"
+                      , showTimeDiff uncategorizedTime
+                      , realToFrac uncategorizedTime/realToFrac totalTimeSel
+		      )]
+		else []
 		)
 
   where minPercentage = last $ mapMaybe (\f -> case f of {MinPercentage m -> Just m {- ; _ -> Nothing -} }) opts
 
 
-formatSeconds :: Double -> String
-formatSeconds s' = go $ zip [days,hours,mins,secs] ["d","h","m","s"]
-  where s = round s' :: Integer
+renderReport (ListOfFields title dats) = do
+	putStrLnUnderlined title
+	putStr $ tabulate False $ map (\(f,v) -> [f,v]) dats
+
+renderReport (ListOfTimePercValues title dats) = do
+	putStrLnUnderlined title
+	putStr $ tabulate True $ ["Tag","Time","Percentage"] : map (\(f,t,p) -> [f,t,printf "%.2f" (p*100)]) dats
+
+renderReport (PieChartOfTimePercValues title dats) = do
+	putStrLnUnderlined title
+	putStr $ tabulate True $ ["Tag","Time","Percentage"] : map (\(f,t,p) -> [f,t,printf "%.2f" (p*100)]) dats
+
+
+tabulate :: Bool -> [[String]] -> String
+tabulate titlerow rows = unlines $ addTitleRow $ map (intercalate " | " . zipWith (\l s -> take (l - length s) (repeat ' ') ++ s) colwidths) rows
+  where cols = transpose rows
+        colwidths = map (maximum . map length) cols
+	addTitleRow | titlerow  = \(l:ls) -> (map (\c -> if c == ' ' then '_' else c) l ++ "_")
+	                                     : ls
+	         -- | titlerow  = \(l:ls) -> l : (take (length l) (repeat '-')) : ls
+	            | otherwise = id
+
+showTimeDiff :: NominalDiffTime -> String
+showTimeDiff t = go False $ zip [days,hours,mins,secs] ["d","h","m","s"]
+  where s = round t :: Integer
         days  =  s `div` (24*60*60)
         hours = (s `div` (60*60)) `mod` 24
-        mins  = (s `div` (60)) `mod` 60
-	secs  = (s `mod` (60))
-	go | s == 0    = const "0s"
-	   | otherwise = concat . snd . mapAccumL go' False 
-	go' True  (a,u)             = (True, printf "%02d%s" a u)
-	go' False (a,u) | a > 0     = (True, printf "%2d%s" a u)
-	                | otherwise = (False, "")
+        mins  = (s `div` 60) `mod` 60
+	secs  =  s `mod` 60 
+	go False []         = "0s"
+	go True  []         = ""
+--	go True  vs         | all (==0) (map fst vs) = concat (replicate (length vs) "   ")
+	go True  ((a,u):vs)             = printf "%02d%s" a u ++ go True vs
+	go False ((a,u):vs) | a > 0     = printf "%2d%s" a u ++ go True vs
+	                    | otherwise =                       go False vs
 
 putStrLnUnderlined str = do
         putStrLn str
diff --git a/src/System/Locale/SetLocale.hsc b/src/System/Locale/SetLocale.hsc
new file mode 100644
--- /dev/null
+++ b/src/System/Locale/SetLocale.hsc
@@ -0,0 +1,61 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+{-
+This file is copied from the setlocale-0.0.3 package. Its author is Lukas Mai
+and it is placed in the Public Domain.
+-}
+
+module System.Locale.SetLocale (
+    Category(..),
+    categoryToCInt,
+    setLocale
+) where
+
+import Foreign.Ptr
+import Foreign.C.Types
+import Foreign.C.String
+
+import Data.Typeable
+
+-- | A type representing the various locale categories. See @man 7 locale@.
+data Category
+    = LC_ALL
+    | LC_COLLATE
+    | LC_CTYPE
+    | LC_MESSAGES
+    | LC_MONETARY
+    | LC_NUMERIC
+    | LC_TIME
+    deriving (Eq, Ord, Read, Show, Enum, Bounded)
+
+instance Typeable Category where
+    typeOf _ = mkTyConApp (mkTyCon "System.Locale.SetLocale.Category") []
+
+#include <locale.h>
+
+-- | Convert a 'Category' to the corresponding system-specific @LC_*@ code.
+-- You probably don't need this function.
+categoryToCInt :: Category -> CInt
+categoryToCInt LC_ALL = #const LC_ALL
+categoryToCInt LC_COLLATE = #const LC_COLLATE
+categoryToCInt LC_CTYPE = #const LC_CTYPE
+categoryToCInt LC_MESSAGES = #const LC_MESSAGES
+categoryToCInt LC_MONETARY = #const LC_MONETARY
+categoryToCInt LC_NUMERIC = #const LC_NUMERIC
+categoryToCInt LC_TIME = #const LC_TIME
+
+ptr2str :: Ptr CChar -> IO (Maybe String)
+ptr2str p
+    | p == nullPtr = return Nothing
+    | otherwise = fmap Just $ peekCString p
+
+str2ptr :: Maybe String -> (Ptr CChar -> IO a) -> IO a
+str2ptr Nothing  f = f nullPtr
+str2ptr (Just s) f = withCString s f
+
+foreign import ccall unsafe "locale.h setlocale" c_setlocale :: CInt -> Ptr CChar -> IO (Ptr CChar)
+
+-- | A Haskell version of @setlocale()@. See @man 3 setlocale@.
+setLocale :: Category -> Maybe String -> IO (Maybe String)
+setLocale cat str =
+    str2ptr str $ \p -> c_setlocale (categoryToCInt cat) p >>= ptr2str
diff --git a/src/TimeLog.hs b/src/TimeLog.hs
--- a/src/TimeLog.hs
+++ b/src/TimeLog.hs
@@ -15,7 +15,7 @@
 
 import qualified Data.ByteString.Lazy as BS
 
-magic = BS.pack $ map (fromIntegral.ord) $ "arbtt-timelog-v1\n"
+magic = BS.pack $ map (fromIntegral.ord) "arbtt-timelog-v1\n"
 
 -- | Runs the given action each delay milliseconds and appends the TimeLog to the
 -- given file.
@@ -33,7 +33,7 @@
 	when (not ex || force) $ BS.writeFile filename magic
 
 appendTimeLog :: Binary a => FilePath -> TimeLogEntry a -> IO ()
-appendTimeLog filename tle = BS.appendFile filename $ encode $ tle
+appendTimeLog filename = BS.appendFile filename . encode
 
 writeTimeLog :: Binary a => FilePath -> TimeLog a -> IO ()
 writeTimeLog filename tl = do createTimeLog True filename
diff --git a/src/UpgradeLog1.hs b/src/UpgradeLog1.hs
--- a/src/UpgradeLog1.hs
+++ b/src/UpgradeLog1.hs
@@ -48,7 +48,7 @@
                         renameFile captureFile oldFile
                         captures <- readTimeLog oldFile
                         writeTimeLog captureFile (upgrade captures)
-                        putStrLn $ "done."
+                        putStrLn   "done."
 
  where oldFile = captureFile ++ ".old"
 
diff --git a/src/capture-main.hs b/src/capture-main.hs
--- a/src/capture-main.hs
+++ b/src/capture-main.hs
@@ -38,10 +38,10 @@
      [ Option "h?"     ["help"]
               (NoArg Help)
 	      "show this help"
-     , Option ['V']    ["version"]
+     , Option "V"      ["version"]
               (NoArg Version)
 	      "show the version number"
-     , Option ['r']    ["sample-rate"]
+     , Option "r"      ["sample-rate"]
      	      (ReqArg (SetRate . read) "RATE")
 	      "set the sample rate in seconds (default: 60)"
      ]	     
diff --git a/src/dump-main.hs b/src/dump-main.hs
--- a/src/dump-main.hs
+++ b/src/dump-main.hs
@@ -24,7 +24,7 @@
      [ Option "h?"     ["help"]
               (NoArg Help)
 	      "show this help"
-     , Option ['V']     ["version"]
+     , Option "V"      ["version"]
               (NoArg Version)
 	      "show the version number"
      ]
diff --git a/src/stats-main.hs b/src/stats-main.hs
--- a/src/stats-main.hs
+++ b/src/stats-main.hs
@@ -36,38 +36,38 @@
 
 options :: [OptDescr Flag]
 options =
-     [ Option "h?"     ["help"]
+     [ Option "h?"      ["help"]
               (NoArg Help)
 	      "show this help"
-     , Option ['V']     ["version"]
+     , Option "V"       ["version"]
               (NoArg Version)
 	      "show the version number"
 --     , Option ['g']     ["graphical"] (NoArg Graphical)    "render the reports as graphical charts"
-     , Option ['x']     ["exclude"]
+     , Option "x"       ["exclude"]
               (ReqArg (Filter . Exclude . Activity Nothing) "TAG")
 	      "ignore samples containing this tag"
-     , Option ['o']     ["only"]
+     , Option "o"       ["only"]
               (ReqArg (Filter . Only . read) "TAG")
 	      "only consider samples containing this tag"
-     , Option []        ["also-inactive"]
+     , Option ""        ["also-inactive"]
               (NoArg (Filter AlsoInactive))
 	      "include samples with the tag \"inactive\""
-     , Option ['f']     ["filter"]
+     , Option "f"       ["filter"]
               (ReqArg (Filter . GeneralCond) "COND")
 	      "only consider samples matching the condition"
-     , Option ['m']     ["min-percentage"]
+     , Option "m"       ["min-percentage"]
               (ReqArg (ReportOption . MinPercentage . read) "PERC")
 	      "do not show tags with a percentage lower than PERC% (default: 1)"
-     , Option ['i']     ["information"]
+     , Option "i"       ["information"]
               (NoArg (Report GeneralInfos))
 	      "show general statistics about the data"
-     , Option ['t']     ["total-time"]
+     , Option "t"       ["total-time"]
               (NoArg (Report TotalTime))
 	      "show total time for each tag"
-     , Option ['c']     ["category"]
+     , Option "c"       ["category"]
               (ReqArg (Report . Category) "CATEGORY")
 	      "show statistics about category CATEGORY"
-     , Option []       ["each-category"]
+     , Option ""        ["each-category"]
               (NoArg (Report EachCategory))
 	      "show statistics about each category found"
      ]
@@ -91,9 +91,9 @@
 
   let categorizeFilename = dir </> "categorize.cfg"
   fileEx <- doesFileExist categorizeFilename
-  unless (fileEx) $ do
+  unless fileEx $ do
      putStrLn $ printf "Configuration file %s does not exist." categorizeFilename
-     putStrLn $ "Please see the example file and the README for more details"
+     putStrLn "Please see the example file and the README for more details"
      exitFailure
   categorizer <- readCategorizer categorizeFilename
 
