diff --git a/arbtt.cabal b/arbtt.cabal
--- a/arbtt.cabal
+++ b/arbtt.cabal
@@ -1,5 +1,5 @@
 name:               arbtt
-version:            0.8.1.4
+version:            0.9
 license:            GPL
 license-file:       LICENSE
 category:           Desktop
@@ -22,6 +22,7 @@
     you understand the consequences of a full-time logger and be careful with this
     data.
 homepage:           http://arbtt.nomeata.de/
+bug-reports:        https://bitbucket.org/nomeata/arbtt/issues
 
 extra-source-files:
     categorize.cfg, arbtt-capture.desktop, README.md,
@@ -48,10 +49,10 @@
         TimeLog
         UpgradeLog1
         LeftFold
-    
+
     ghc-options: -rtsopts
 
-    if os(windows) 
+    if os(windows)
         extra-libraries: psapi
         cpp-options:    -DWIN32
         ghc-options:    -optl-mwindows
@@ -98,7 +99,7 @@
         Text.Regex.PCRE.Light.Text
         TermSize
     ghc-options: -rtsopts
-    if os(windows) 
+    if os(windows)
         cpp-options:    -DWIN32
     else
         other-modules:
@@ -127,7 +128,7 @@
         DumpFormat
         Data.List.TakeR
     ghc-options: -rtsopts
-    if os(windows) 
+    if os(windows)
         cpp-options:    -DWIN32
     else
         other-modules:
@@ -151,7 +152,7 @@
         CommonStartup
         TimeLog
     ghc-options: -rtsopts
-    if os(windows) 
+    if os(windows)
         cpp-options:    -DWIN32
     else
         other-modules:
@@ -174,7 +175,7 @@
         CommonStartup
         TimeLog
     ghc-options: -rtsopts
-    if os(windows) 
+    if os(windows)
         cpp-options:    -DWIN32
     else
         other-modules:
@@ -195,7 +196,7 @@
       , tasty-golden >= 2.2.0.2  && <= 2.3
       , tasty-hunit >= 0.2  && < 0.11
       , HUnit == 1.2.*
-      , process-extras == 0.2.*
+      , process-extras == 0.2.* || == 0.3.*
       , deepseq
       , binary >= 0.5
       , bytestring
diff --git a/doc/arbtt.xml b/doc/arbtt.xml
--- a/doc/arbtt.xml
+++ b/doc/arbtt.xml
@@ -330,16 +330,16 @@
 
     <figure id="grammar">
         <title>The formal grammar of <filename>categorize.cfg</filename></title>
-	<productionset>
-	  <production id="g-rules">
-	    <lhs>Rules</lhs>
-	    <rhs>
-	      [ <nonterminal def="#g-aliasspec"/> ]
-	      <nonterminal def="#g-rule"/> ( (<quote>,</quote>
-	      <nonterminal def="#g-rule"/>)* | ( <quote>;</quote>
-	      <nonterminal def="#g-rule"/>)* )
-	    </rhs>
-	  </production>
+    <productionset>
+      <production id="g-rules">
+        <lhs>Rules</lhs>
+        <rhs>
+          [ <nonterminal def="#g-aliasspec"/> ]
+          <nonterminal def="#g-rule"/> ( (<quote>,</quote>
+          <nonterminal def="#g-rule"/>)* | ( <quote>;</quote>
+          <nonterminal def="#g-rule"/>)* )
+        </rhs>
+      </production>
 
           <production id="g-aliasspec">
             <lhs>AliasSpec</lhs>
@@ -353,9 +353,9 @@
             <rhs>Literal <quote>-&gt;</quote> Literal</rhs>
           </production>
 
-	  <production id="g-rule">
-	    <lhs>Rule</lhs>
-	    <rhs><quote>{</quote> <nonterminal def="#g-rules"/>
+      <production id="g-rule">
+        <lhs>Rule</lhs>
+        <rhs><quote>{</quote> <nonterminal def="#g-rules"/>
                  <quote>}</quote>
             </rhs>
             <rhs>
@@ -368,11 +368,11 @@
             <rhs>
               <quote>tag</quote> <nonterminal def="#g-tag"/>
             </rhs>
-	  </production>
+      </production>
 
-	  <production id="g-cond">
-	    <lhs>Cond</lhs>
-	    <rhs><quote>(</quote> <nonterminal def="#g-cond"/>
+      <production id="g-cond">
+        <lhs>Cond</lhs>
+        <rhs><quote>(</quote> <nonterminal def="#g-cond"/>
                  <quote>)</quote>
             </rhs>
             <rhs><quote>!</quote> <nonterminal def="#g-cond"/> |
@@ -381,67 +381,67 @@
                  <nonterminal def="#g-cond"/> <quote>||</quote> <nonterminal
                  def="#g-cond"/>
             </rhs>
-	    <rhs> <quote>$active</quote> </rhs>
+        <rhs> <quote>$active</quote> </rhs>
             <rhs> <nonterminal def="#g-string"/> <nonterminal def="#g-cmpop"/>
-	   	 <nonterminal def="#g-string"/> </rhs>
+         <nonterminal def="#g-string"/> </rhs>
             <rhs> <nonterminal def="#g-string"/> <nonterminal def="#g-cmpop"/>
-	   	 <quote>[</quote> <nonterminal def="#g-listofstring"/>
+         <quote>[</quote> <nonterminal def="#g-listofstring"/>
                  <quote>]</quote>
                  </rhs>
             <rhs> <nonterminal def="#g-string"/> <quote>=~</quote>
-	    	 <nonterminal def="#g-regex"/></rhs>
+             <nonterminal def="#g-regex"/></rhs>
             <rhs> <nonterminal def="#g-string"/> <quote>=~</quote>
-	   	 <quote>[</quote> <nonterminal def="#g-listofregex"/>
+         <quote>[</quote> <nonterminal def="#g-listofregex"/>
                  <quote>]</quote>
                  </rhs>
             <rhs> <nonterminal def="#g-number"/> <nonterminal def="#g-cmpop"/>
-	   	 <nonterminal def="#g-number"/> </rhs>
+         <nonterminal def="#g-number"/> </rhs>
             <rhs> <nonterminal def="#g-timediff"/> <nonterminal def="#g-cmpop"/>
-	   	 <nonterminal def="#g-timediff"/> </rhs>
+         <nonterminal def="#g-timediff"/> </rhs>
             <rhs> <nonterminal def="#g-date"/> <nonterminal def="#g-cmpop"/>
-	   	 <nonterminal def="#g-date"/> </rhs>
+         <nonterminal def="#g-date"/> </rhs>
             <rhs> <quote>current window</quote> <nonterminal def="#g-cond"/> </rhs>
             <rhs> <quote>any window</quote> <nonterminal def="#g-cond"/> </rhs>
-	  </production>
+      </production>
 
-	  <production id="g-string">
-	    <lhs>String</lhs>
-	    <rhs> <quote>$title</quote> </rhs>
-	    <rhs> <quote>$program</quote> </rhs>
-	    <rhs> <quote>$desktop</quote> </rhs>
-	    <rhs> <quote>format</quote> <nonterminal def="#g-date" /> </rhs>
-	    <rhs> <quote>"</quote> string literal <quote>"</quote> </rhs>
-	  </production>
+      <production id="g-string">
+        <lhs>String</lhs>
+        <rhs> <quote>$title</quote> </rhs>
+        <rhs> <quote>$program</quote> </rhs>
+        <rhs> <quote>$desktop</quote> </rhs>
+        <rhs> <quote>format</quote> <nonterminal def="#g-date" /> </rhs>
+        <rhs> <quote>"</quote> string literal <quote>"</quote> </rhs>
+      </production>
 
-	  <production id="g-listofstring">
-	    <lhs>ListOfString</lhs>
-	    <rhs> <quote>"</quote> string literal <quote>"</quote> </rhs>
-	    <rhs> <quote>"</quote> string literal <quote>"</quote> , <nonterminal def="#g-listofstring"/> </rhs>
-	  </production>
+      <production id="g-listofstring">
+        <lhs>ListOfString</lhs>
+        <rhs> <quote>"</quote> string literal <quote>"</quote> </rhs>
+        <rhs> <quote>"</quote> string literal <quote>"</quote> , <nonterminal def="#g-listofstring"/> </rhs>
+      </production>
 
-	  <production id="g-number">
-	    <lhs>Number</lhs>
-	    <rhs> <quote>$idle</quote> </rhs>
-	    <rhs> <quote>day of week</quote> <nonterminal def="#g-date" /> </rhs>
-	    <rhs> <quote>day of month</quote> <nonterminal def="#g-date" /> </rhs>
-	    <rhs> <quote>month</quote> <nonterminal def="#g-date" /> </rhs>
-	    <rhs> <quote>year</quote> <nonterminal def="#g-date" /> </rhs>
-	    <rhs> number literal </rhs>
-	  </production>
+      <production id="g-number">
+        <lhs>Number</lhs>
+        <rhs> <quote>$idle</quote> </rhs>
+        <rhs> <quote>day of week</quote> <nonterminal def="#g-date" /> </rhs>
+        <rhs> <quote>day of month</quote> <nonterminal def="#g-date" /> </rhs>
+        <rhs> <quote>month</quote> <nonterminal def="#g-date" /> </rhs>
+        <rhs> <quote>year</quote> <nonterminal def="#g-date" /> </rhs>
+        <rhs> number literal </rhs>
+      </production>
 
-	  <production id="g-date">
-	    <lhs>Date</lhs>
-	    <rhs> <quote>$date</quote> </rhs>
-	    <!-- <rhs> <quote>$now</quote> </rhs> -->
-	  </production>
+      <production id="g-date">
+        <lhs>Date</lhs>
+        <rhs> <quote>$date</quote> </rhs>
+        <!-- <rhs> <quote>$now</quote> </rhs> -->
+      </production>
 
-	  <production id="g-timediff">
-	    <lhs>TimeDiff</lhs>
-	    <rhs> <quote>$time</quote> </rhs>
-	    <rhs> <quote>$sampleage</quote> </rhs>
-	    <!-- <rhs> <nonterminal def="#g-date"/> <quote>-</quote> <nonterminal def="#g-date"/></rhs> -->
+      <production id="g-timediff">
+        <lhs>TimeDiff</lhs>
+        <rhs> <quote>$time</quote> </rhs>
+        <rhs> <quote>$sampleage</quote> </rhs>
+        <!-- <rhs> <nonterminal def="#g-date"/> <quote>-</quote> <nonterminal def="#g-date"/></rhs> -->
             <rhs>( Digit )* Digit <quote>:</quote> Digit Digit</rhs>
-	  </production>
+      </production>
 
           <production id="g-tag">
             <lhs>Tag</lhs>
@@ -457,11 +457,11 @@
               character.</lineannotation> </rhs>
           </production>
 
-	  <production id="g-listofregex">
-	    <lhs>ListOfRegex</lhs>
-	    <rhs> <quote>"</quote> <nonterminal def="#g-regex"/> <quote>"</quote> </rhs>
-	    <rhs> <quote>"</quote> <nonterminal def="#g-regex"/> <quote>"</quote> , <nonterminal def="#g-listofregex"/> </rhs>
-	  </production>
+      <production id="g-listofregex">
+        <lhs>ListOfRegex</lhs>
+        <rhs> <quote>"</quote> <nonterminal def="#g-regex"/> <quote>"</quote> </rhs>
+        <rhs> <quote>"</quote> <nonterminal def="#g-regex"/> <quote>"</quote> , <nonterminal def="#g-listofregex"/> </rhs>
+      </production>
 
           <production id="g-cmpop">
             <lhs>CmpOp</lhs>
@@ -469,7 +469,7 @@
             | <quote>&gt;</quote> | <quote>&gt;=</quote></rhs>
           </production>
 
-	</productionset>
+    </productionset>
       </figure>
       <para>
         A <literal>String</literal> refers to a double-quoted string of
@@ -507,9 +507,524 @@
         expressions.
       </para>
   </sect2>
-
 </sect1>
 
+<sect1 id="effective-use">
+  <title>Effective Use of Arbtt</title>
+  <para>
+    Now that the syntax has been described and the toolbox laid out,
+    how do you practically go about using and configuring arbtt?
+  </para>
+  <sect2>
+    <title>Enabling data collection</title>
+    <para>
+      After installing arbtt, you need to configure it to run. There
+      are many ways you can run the <literal>arbtt-capture</literal>
+      daemon. One standard way is to include the command
+      <programlisting>
+arbtt-capture &amp;
+      </programlisting>
+      in your desktop environments startup script, e.g.
+      <filename>~/.xinitrc</filename> or similar.
+    </para>
+    <para>
+      Another trick is add it as a <ulink
+      url="https://en.wikipedia.org/wiki/Cron"><literal>cron</literal></ulink>
+      job. To do so, edit your crontab file (<literal>crontab -e</literal>) and
+      add a line like this:
+    </para>
+    <programlisting>
+DISPLAY=:0
+@reboot arbtt-capture --logfile=/home/username/doc/arbtt/capture.log
+</programlisting>
+    <para>
+      At boot, <literal>arbtt-capture</literal> will be run in the
+      background and will capture a snapshot of the X metadata for
+      active windows every 60 seconds (the default). If you want more
+      fine-grained time data at the expense of doubling storage use,
+      you could increase the sampling rate with an option like
+      <literal>--sample-rate=30</literal>. To be resilient to any errors
+      or segfaults, you could also wrap it in an infinite loop to restart
+      the daemon should it ever crash, with a command like
+    </para>
+    <programlisting>
+DISPLAY=:0
+@reboot while true; do arbtt-capture --sample-rate=30; sleep 1m; done
+</programlisting>
+  </sect2>
+  <sect2>
+    <title>Checking data availability</title>
+    <para>
+      arbtt tracks <ulink url="https://en.wikipedia.org/wiki/X_Window_System_protocols_and_architecture#Attributes_and_properties"
+      >X</ulink> properties like window title, class, and running
+      program, and you write rules to classify those strings as
+      you wish; but this assumes that the necessary data is present in
+      those properties.
+    </para>
+    <para>
+      For some programs, this is the case. For example, web browsers
+      like Firefox typically set the X title to the
+      HTML <literal>&lt;title&gt;</literal> element of the web page in the
+      currently-focused tab, which is enough for classification.
+    </para>
+    <para>
+      Some programs do not set titles or class, and all arbtt sees is
+      empty strings like <literal>&quot;&quot;</literal>; or they may set the title/class
+      to a constant like <literal>&quot;Liferea&quot;</literal>, which may be acceptable if
+      that program is used for only one purpose, but if it is used for
+      many purposes, then you cannot write a rule matching it without
+      producing highly-misleading time analyses. (For example, a web
+      browser may be used for countless purposes, ranging from work to
+      research to music to writing to programming; but if the web
+      browser's title/class were always just <literal>&quot;Web browser&quot;</literal>,
+      how would you classify 5 hours spent using the web browser? If the
+      5 hours are classified as any or all of those purposes, then the
+      results will be misleading garbage - you probably did not spend 5
+      hours just listening to music, but a mixture of those purposes,
+      which changes from day to day.)
+    </para>
+    <para>
+      You should check for such problematic programs upon starting using
+      arbtt. It would be unfortunate if you were to log for a few
+      months, go back for a detailed report for some reason, and
+      discover that the necessary data was never available for
+      arbtt to log!
+    </para>
+    <para>
+      These programs can sometimes be customized internally, a bug
+      report filed with the maintainers, or their titles can be
+      externally set by
+      <ulink url="https://en.wikipedia.org/wiki/Wmctrl"><literal>wmctrl</literal></ulink>
+      or
+      <ulink url="http://jonisalonen.com/2014/setting-x11-window-properties-with-xprop/"><literal>xprop</literal></ulink>.
+    </para>
+    <sect3>
+      <title><literal>xprop</literal></title>
+      <para>
+        You can check the X properties of a running window by running
+        the command
+        <ulink url="http://www.xfree86.org/current/xprop.1.html"><literal>xprop</literal></ulink>
+        and clicking on the window; <literal>xprop</literal> will print
+        out all the relevant X information. For example, the output for
+        Emacs might look like this
+      </para>
+      <programlisting>
+$ xprop | tail -5
+WM_CLASS(STRING) = &quot;emacs&quot;, &quot;Emacs&quot;
+WM_ICON_NAME(STRING) = &quot;emacs@elan&quot;
+_NET_WM_ICON_NAME(UTF8_STRING) = &quot;emacs@elan&quot;
+WM_NAME(STRING) = &quot;emacs@elan&quot;
+_NET_WM_NAME(UTF8_STRING) = &quot;emacs@elan&quot;
+</programlisting>
+      <para>
+        This is not very helpful: it does not tell us the filename being
+        edited, the mode being used, or anything. You could classify
+        time spent in Emacs as &quot;programming&quot; or
+        &quot;writing&quot;, but this would be imperfect, especially if
+        you do both activities regularly. However, Emacs can be
+        customized by editing <literal>~/.emacs</literal>, and after
+        some searching with queries like &quot;setting Emacs window
+        title&quot;, the
+        <ulink url="http://www.emacswiki.org/emacs-en/FrameTitle">Emacs
+        wiki</ulink> and
+        <ulink url="https://www.gnu.org/software/emacs/manual/html_node/efaq/Displaying-the-current-file-name-in-the-titlebar.html">manual</ulink>
+        advise us to put something like this Elisp in our
+        <literal>.emacs</literal> file:
+      </para>
+      <programlisting>
+(setq frame-title-format &quot;%f&quot;)
+</programlisting>
+      <para>
+        Now the output looks different:
+      </para>
+      <programlisting>
+$ xprop | tail -5
+WM_CLASS(STRING) = &quot;emacs&quot;, &quot;Emacs&quot;
+WM_ICON_NAME(STRING) = &quot;/home/gwern/arbtt.page&quot;
+_NET_WM_ICON_NAME(UTF8_STRING) = &quot;/home/gwern/arbtt.page&quot;
+WM_NAME(STRING) = &quot;/home/gwern/arbtt.page&quot;
+_NET_WM_NAME(UTF8_STRING) = &quot;/home/gwern/arbtt.page&quot;
+</programlisting>
+      <para>
+        With this, we can usefully classify all such time samples as
+        being “writing”:
+      </para>
+<programlisting>
+current window $title == &quot;/home/gwern/arbtt.page&quot; ==> tag Writing,
+</programlisting>
+      <para>
+        Another common gap is terminals/shells: they often do not
+        include information in the title like the current working
+        directory or last shell command. For example, urxvt/Bash:
+      </para>
+      <programlisting>
+WM_COMMAND(STRING) = { &quot;urxvt&quot; }
+_NET_WM_ICON_NAME(UTF8_STRING) = &quot;urxvt&quot;
+WM_ICON_NAME(STRING) = &quot;urxvt&quot;
+_NET_WM_NAME(UTF8_STRING) = &quot;urxvt&quot;
+WM_NAME(STRING) = &quot;urxvt&quot;
+</programlisting>
+      <para>
+        Programmers may spend many hours in the shell doing a variety of
+        things (like Emacs), so this is a problem. Fortunately, this is
+        also solvable by customizing one's <literal>.bashrc</literal> to
+        set the prompt to emit an escape code interpreted by the
+        terminal (baroque, but it works). The following will include the
+        working directory, a timestamp, and the last command:
+      </para>
+      <programlisting>
+trap 'echo -ne &quot;\033]2;$(pwd); $(history 1 | sed &quot;s/^[ ]*[0-9]*[ ]*//g&quot;)\007&quot;' DEBUG
+</programlisting>
+      <para>
+        Now the urxvt samples are useful:
+      </para>
+      <programlisting>
+_NET_WM_NAME(UTF8_STRING) = &quot;/home/gwern/wiki; 2014-09-03 13:39:32 arbtt-stats --help&quot;
+</programlisting>
+      <para>
+    Some distributions (e.g. Debian) already provide the relevant
+    configuration for this to happen. If it does not work for you, you can try to add
+    <programlisting>. /etc/profile.d/vte.sh</programlisting>
+    to your <filename>~/.bashrc</filename>.
+      </para>
+      <para>
+        A rule could classify based on the directory you are working in,
+        the command one ran, or both. Other shells like zsh can be fixed
+        this way too but the exact command may differ; you will need to
+        research and experiment.
+      </para>
+      <para>
+        Some programs can be tricky to set. The
+        <ulink url="http://feh.finalrewind.org/">X image viewer
+        feh</ulink> has a <literal>--title</literal> option but it
+        cannot be set in the configuration file,
+        <literal>.config/feh/themes</literal>, because it needs to be
+        specified dynamically; so you need to set up a shell alias or
+        script to wrap the command like
+        <literal>feh --title &quot;$(pwd) / %f / %n&quot;</literal>.
+      </para>
+    </sect3>
+    <sect3>
+      <title>Raw samples</title>
+      <para>
+        <literal>xprop</literal> can be tedious to use on every running
+        window and you may forget to check seldomly used programs. A better
+        approach is to use <literal>arbtt-stats</literal>’s
+        <literal>--dump-samples</literal> option: this option will print
+        out the collected data for specified time periods, allowing you
+        to examine the X properties en masse. This option can be used
+        with the <literal>--exclude=</literal>
+        option to print the samples for <emphasis>samples not matched
+        by existing rules</emphasis> as well, which is indispensable for
+        improving coverage and suggesting ideas for new rules. A good
+        way to figure out what customizations to make is to run arbtt as
+        a daemon for a day or so, and then begin examining the raw
+        samples for problems.
+      </para>
+      <example>
+      <title>An initial configuration session</title>
+      <para>
+        An example: suppose I create a simple category file named
+        <literal>foo</literal> with just the line
+      </para>
+      <programlisting>
+$idle &gt; 30 ==&gt; tag inactive
+</programlisting>
+      <para>
+        I can then dump all my arbtt samples for the past day with a
+        command like this:
+      </para>
+      <programlisting>
+arbtt-stats --categorizefile=foo --m=0 --filter='$sampleage &lt;24:00' --dump-samples
+</programlisting>
+      <para>
+        Because there are so many open windows, this produces a large
+        amount (26586 lines) of hard-to-read output:
+      </para>
+      <programlisting>
+...
+( ) Navigator:      /r/Touhou's Favorite Arranges! Part 71: Retribution for the Eternal Night ~ Imperishable Night : touhou - Iceweasel
+( ) Navigator:      Configuring the arbtt categorizer (arbtt-stats) - Iceweasel
+( ) evince:         ATTACHMENT02
+( ) evince:         2009-geisler.pdf — Heart rate variability predicts self-control in goal pursuit
+( ) urxvt:          /home/gwern; arbtt-stats --categorizefile=foo --m=0 --filter='$sampleage &lt;24:00' --dump-samples
+( ) mnemosyne:      Mnemosyne
+( ) urxvt:          /home/gwern; 2014-09-03 13:11:45 xprop
+( ) urxvt:          /home/gwern; 2014-09-03 13:42:17 history 1 | cut --delimiter=' ' --fields=5-
+( ) urxvt:          /home/gwern; 2014-09-03 13:12:21 git log -p .emacs
+(*) emacs:          emacs@elan
+( ) urxvt:          /home/gwern; 2014-09-01 14:50:30 while true; do cd ~/ &amp;&amp; getmail_fetch --ssl pop.gmail.com gwern0 'ugaozoumbhwcijxb' ./mail/; done
+( ) urxvt:          /home/gwern/blackmarket-mirrors/silkroad2-forums; 2014-08-31 23:20:10 mv /home/gwern/cookies.txt ./; http_proxy=&quot;localhost:8118&quot; wget...
+( ) urxvt:          /home/gwern/blackmarket-mirrors/agora; 2014-08-31 23:15:50 mv /home/gwern/cookies.txt ./; http_proxy=&quot;localhost:8118&quot; wget --mirror ...
+( ) urxvt:          /home/gwern/blackmarket-mirrors/evolution-forums; 2014-08-31 23:04:10 mv ~/cookies.txt ./; http_proxy=&quot;localhost:8118&quot; wget --mirror ...
+( ) puddletag:      puddletag: /home/gwern/music
+</programlisting>
+      <para>
+        Active windows are denoted by an asterisk, so I can focus &amp;
+        simplify by adding a pipe like <literal>| fgrep '(*)'</literal>,
+        producing more manageable output like
+      </para>
+      <programlisting>
+(*) urxvt:          irssi
+(*) urxvt:          irssi
+(*) urxvt:          irssi
+(*) Navigator:      Pyramid of Technology - NextNature.net - Iceweasel
+(*) Navigator:      Search results - gwern0@gmail.com - Gmail - Iceweasel
+(*) Navigator:      [New comment] The Wrong Path - gwern0@gmail.com - Gmail - Iceweasel
+(*) Navigator:      Iceweasel
+(*) Navigator:      Litecoin Exchange Rate - $4.83 USD - litecoinexchangerate.org - Iceweasel
+(*) Navigator:      PredictionBook: LiteCoin will trade at &gt;=10 USD per ltc in 2 years, - Iceweasel
+(*) urxvt:          irssi
+(*) Navigator:      Bug#691547 closed by Mikhail Gusarov &lt;dottedmag@dottedmag.net&gt; (Re: s3cmd: Man page: --default-mime-type documentation incomplete...)
+(*) Navigator:      Bug#691547 closed by Mikhail Gusarov &lt;dottedmag@dottedmag.net&gt; (Re: s3cmd: Man page: --default-mime-type documentation incomplete...)
+(*) Navigator:      Bug#691547 closed by Mikhail Gusarov &lt;dottedmag@dottedmag.net&gt; (Re: s3cmd: Man page: --default-mime-type documentation incomplete...)
+(*) urxvt:          /home/gwern; 2014-09-02 14:25:17 man s3cmd
+(*) evince:         bayesiancausality.pdf
+(*) evince:         bayesiancausality.pdf
+(*) puddletag:      puddletag: /home/gwern/music
+(*) puddletag:      puddletag: /home/gwern/music
+(*) evince:         bayesiancausality.pdf
+(*) Navigator:      ▶ Umineko no Naku Koro ni Music Box 4 - オルガン小曲 第2億番 ハ短調 - YouTube - Iceweasel
+...
+</programlisting>
+      <para>
+        This is better. We can see a few things: the windows all now
+        produce enough information to be usefully classified (Gmail can
+        be classified under email, irssi can be classified as IRC, the
+        urxvt usage can clearly be classified as programming, the PDF
+        being read is statistics, etc) in part because of customizations
+        to bash/urxvt. The duplication still impedes focus, and we don't
+        know what's most common. We can use another pipeline to sort,
+        count duplicates, and sort by number of duplicates
+        (<literal>| sort | uniq --count | sort --general-numeric-sort</literal>),
+        yielding:
+      </para>
+      <programlisting>
+ ...
+ 14     (*) Navigator:      A Bluer Shade of White Chapter 4, a frozen fanfic | FanFiction - Iceweasel
+ 14     (*) Navigator:      Iceweasel
+ 15     (*) evince:         2009-geisler.pdf — Heart rate variability predicts self-control in goal pursuit
+ 15     (*) Navigator:      Tool use by animals - Wikipedia, the free encyclopedia - Iceweasel
+ 16     (*) Navigator:      Hacker News | Add Comment - Iceweasel
+ 17     (*) evince:         bayesiancausality.pdf
+ 17     (*) Navigator:      Comments - Less Wrong Discussion - Iceweasel
+ 17     (*) Navigator:      Keith Gessen · Why not kill them all?: In Donetsk · LRB 11 September 2014 - Iceweasel
+ 17     (*) Navigator:      Notes on the Celebrity Data Theft | Hacker News - Iceweasel
+ 18     (*) Navigator:      A Bluer Shade of White Chapter 1, a frozen fanfic | FanFiction - Iceweasel
+ 19     (*) gl:             mplayer2
+ 19     (*) Navigator:      Neural networks and deep learning - Iceweasel
+ 20     (*) Navigator:      Harry Potter and the Philosopher's Zombie, a harry potter fanfic | FanFiction - Iceweasel
+ 20     (*) Navigator:      [OBNYC] Time tracking app - gwern0@gmail.com - Gmail - Iceweasel
+ 25     (*) evince:         ps2007.pdf — untitled
+ 35     (*) emacs:          /home/gwern/arbtt.page
+ 43     (*) Navigator:      CCC comments on The Octopus, the Dolphin and Us: a Great Filter tale - Less Wrong - Iceweasel
+ 62     (*) evince:         The physics of information processing superobjects - Anders Sandberg - 1999.pdf — Brains2
+ 69     (*) liferea:        Liferea
+ 82     (*) evince:         BMS_raftery.pdf — untitled
+ 84     (*) emacs:          emacs@elan
+ 87     (*) Navigator:      overview for gwern - Iceweasel
+109     (*) puddletag:      puddletag: /home/gwern/music
+150     (*) urxvt:          irssi
+</programlisting>
+      <para>
+        Put this way, we can see what rules we should write to
+        categorize: we could categorize the activities here into a few
+        categories of &quot;recreational&quot;, &quot;statistics&quot;,
+        &quot;music&quot;, &quot;email&quot;, &quot;IRC&quot;,
+        &quot;research&quot;, and &quot;writing&quot;; and add to the
+        <literal>categorize.cfg</literal> some rules like thus:
+      </para>
+      <programlisting>
+$idle &gt; 30 ==&gt; tag inactive,
+
+current window $title =~ [/.*Hacker News.*/, /.*Less Wrong.*/, /.*overview for gwern.*/, /.*[fF]an[fF]ic.*/, /.* LRB .*/]
+  || current window $program == &quot;liferea&quot; ==&gt; tag Recreation,
+current window $title =~ [/.*puddletag.*/, /.*mplayer2.*/] ==&gt; tag Music,
+current window $title =~ [/.*[bB]ayesian.*/, /.*[nN]eural [nN]etworks.*/, /.*ps2007.pdf.*/, /.*[Rr]aftery.*/] ==&gt; tag Statistics,
+current window $title =~ [/.*Wikipedia.*/, /.*Heart rate variability.*/, /.*Anders Sandberg.*/] ==&gt; tag Research,
+current window $title =~ [/.*Gmail.*/] ==&gt; tag Email,
+current window $title =~ [/.*arbtt.*/] ==&gt; tag Writing,
+current window $title == &quot;irssi&quot; ==&gt; tag IRC,
+</programlisting>
+      <para>
+        If we reran the command, we'd see the same output, so we need to
+        leverage our new rules and <emphasis>exclude</emphasis> any
+        samples matching our current tags, so now we run a command like:
+      </para>
+      <programlisting>
+arbtt-stats --categorizefile=foo --filter='$sampleage &lt;24:00' --dump-samples --exclude=Recreation --exclude=Music --exclude=Statistics
+             --exclude=Research --exclude=Email --exclude=Writing --exclude=IRC |
+             fgrep '(*)' | sort | uniq --count | sort --general-numeric-sort
+</programlisting>
+      <para>
+        Now the previous samples disappear, leaving us with a fresh
+        batch of unclassified samples to work with:
+      </para>
+      <programlisting>
+  9     (*) Navigator:      New Web Order &gt; Nik Cubrilovic - - Notes on the Celebrity Data Theft - Iceweasel
+  9     ( ) urxvt:          /home/gwern; arbtt-stats --categorizefile=foo --filter='$sampleage &lt;24:00' --dump-samples | fgrep '(*)' | less
+ 10     (*) evince:         ATTACHMENT02
+ 10     (*) Navigator:      These Giant Copper Orbs Show Just How Much Metal Comes From a Mine | Design | WIRED - Iceweasel
+ 12     (*) evince:         [Jon_Elster]_Alchemies_of_the_Mind_Rationality_an(BookFi.org).pdf — Alchemies of the mind
+ 12     (*) Navigator:      Morality Quiz/Test your Morals, Values &amp; Ethics - YourMorals.Org - Iceweasel
+ 33     ( ) urxvt:          /home/gwern; arbtt-stats --categorizefile=foo --filter='$sampleage &lt;24:00' --dump-samples | fgrep '(*)'...
+</programlisting>
+      <para>
+        We can add rules categorizing these as 'Recreational',
+        'Writing', 'Research', 'Recreational', 'Research', 'Writing',
+        and 'Writing' respectively; and we might decide at this point
+        that 'Writing' is starting to become overloaded, so we'll split
+        it into two tags, 'Writing' and 'Programming'. And then after
+        tossing another <literal>--exclude=Programming</literal> into
+        our rules, we can repeat the process.
+      </para>
+      <para>
+        As we refine our rules, we will quickly spot instances where the
+        title/class/program are insufficient to allow accurate
+        classification, and we will figure out the best collection of
+        tags for our particular purposes. A few iterations is enough for
+        most purposes.
+      </para>
+      </example>
+    </sect3>
+  </sect2>
+  <sect2>
+    <title>Categorizing advice</title>
+    <para>
+      When building up rules, a few rules of thumb should be kept in
+      mind:
+    </para>
+    <sect3>
+        <title>
+          Categorize by purpose, not by program
+        </title>
+        <para>
+          This leads to misleading time reports. Avoid, for example,
+          lumping all web browser time into a single category named
+          'Internet'; this is more misleading than helpful. Good
+          categories describe an activity or goal, such as 'Work' or
+          'Recreation', not a tool, like 'Emacs' or 'Vim'.
+        </para>
+    </sect3>
+    <sect3>
+        <title>
+          When in doubt, write narrow rules and generalize later
+        </title>
+        <para>
+          Regexps are tricky and it can be easy to write rules far
+          broader than one intended. The <literal>--exclude</literal>
+          filters mean that one will never see samples which are matched
+          accidentally. If one is in doubt, it can be helpful to take a
+          specific sample one wants to match and several similar strings
+          and look at how well one's regexp rule works in Emacs's
+          <ulink url="http://www.emacswiki.org/emacs/ReBuilder">regexp-builder</ulink>
+          or online regexp-testers like
+          <ulink url="http://regexpal.com/">regexpal</ulink>.
+        </para>
+    </sect3>
+    <sect3>
+        <title>
+          Don't try to classify everything
+        </title>
+        <para>
+          You will never classify 100% of samples because sometimes
+          programs do not include useful X properties and cannot be
+          fixed, you have samples from before you fixed them, or they
+          are too transient (like popups and dialogues) to be worth
+          fixing. It is not necessary to classify 100% of your time,
+          since as long as the most common programs and, say,
+          <ulink url="https://en.wikipedia.org/wiki/Pareto_principle">80%</ulink>
+          of your time is classified, then you have most of the value.
+          It is easy to waste more time tweaking arbtt than one gains
+          from increased accuracy or more finely-grained tags.
+        </para>
+    </sect3>
+    <sect3>
+    <title>
+      Avoid large and microscopic tags
+    </title>
+    <para>
+      If a tag takes up more than a third or so of your time, it is
+      probably too large, masks variation, and can be broken down into more
+      meaningful tags. Conversely, a tag too narrow to show up regularly in
+      reports (because it is below the default 1% filter) may not be
+      helpful because it is usually tiny, and can be combined with the most
+      similar tag to yield more compact and easily interpreted reports.
+    </para>
+    </sect3>
+  </sect2>
+  <sect2>
+    <title>Long-term storage</title>
+    <para>
+      Each halving of the sampling rate doubles the number of samples
+      taken and hence the storage requirement; sampling rates below 20s
+      are probably wasteful. But even the default 60s can accumulate
+      into a nontrivial amount of data over a year. A
+      constantly-changing binary file can interact poorly with backup
+      systems, may make arbtt analyses slower, and if one's system
+      occasionally crashes or experiences other problems, cause some
+      corruption of the log and be a nuisance in having to run
+      <literal>arbtt-recover</literal>.
+    </para>
+    <para>
+      Thus it may be a good idea to archive one's
+      <literal>capture.log</literal> on an annual basis. If one needs to
+      query the historical data, the particular log file can be
+      specified as an option like
+      <literal>--logfile=/home/gwern/doc/arbtt/2013-2014.log</literal>
+    </para>
+  </sect2>
+  <sect2 id="external-processing">
+    <title>External processing of arbtt statistics</title>
+    <para>
+      arbtt supports CSV export of time by category in various levels of
+      granularity in a 'long' format (multiple rows for each day, with
+      <emphasis>n</emphasis> row specifying a category's value for that
+      day). These CSV exports can be imported into statistical programs
+      like R or Excel and manipulated as desired.
+    </para>
+    <para>
+      R users may prefer to have their time data in a 'wide' format
+      (each row is 1 day, with <emphasis>n</emphasis> columns for each
+      possible category); this can be done with the
+      <literal>reshape</literal> default library. After reading in the
+      CSV, the time-intervals can be converted to counts and the data to
+      a wide data-frame with R code like the following:
+    </para>
+    <programlisting>
+arbtt &lt;- read.csv(&quot;arbtt.csv&quot;)
+interval &lt;- function(x) { if (!is.na(x)) { if (grepl(&quot; s&quot;,x)) as.integer(sub(&quot; s&quot;,&quot;&quot;,x))
+                                          else { y &lt;- unlist(strsplit(x, &quot;:&quot;));
+                                                 as.integer(y[[1]])*3600 + as.integer(y[[2]])*60 + as.integer(y[[3]]); }
+                                                 }
+                         else NA
+                         }
+arbtt$Time &lt;- sapply(as.character(arbtt$Time), interval)
+library(reshape)
+arbtt &lt;- reshape(arbtt, v.names=&quot;Time&quot;, timevar=&quot;Tag&quot;, idvar=&quot;Day&quot;, direction=&quot;wide&quot;)
+</programlisting>
+  </sect2>
+</sect1>
+<sect1 id="contributed">
+  <title>Contributed tools</title>
+  <para>
+    Due to the export facilities of arbtt (as explained in <xref
+    linkend="external-processing"/>), tools analyzing arbtt’s data can be
+    developed independently. This section lists those that we are aware of. If
+    you create a tool of your own, or find one somewhere, pleas tell us on the
+    mailing list (see <xref linkend="copyright"/>).
+  </para>
+  <sect2>
+    <title>arbtt-graph</title>
+    <para>
+      Jayesh Kumar Gupta created a nice d3-based visualization of your arbtt
+      data, including daily pie charts and barcode graphs.
+    </para>
+    <para>
+      You can find his tool on <ulink url="https://github.com/rejuvyesh/arbtt-graph">https://github.com/rejuvyesh/arbtt-graph</ulink>.
+    </para>
+  </sect2>
+</sect1>
 <sect1 id="references">
   <title>Program references</title>
   <para>arbtt consists of a few command line tools, the most important one is
@@ -747,9 +1262,9 @@
             <term><option>--for-each</option> <replaceable>PERIOD</replaceable></term>
             <listitem><para>This is not a report of its own, but causes the selected
             report to be executed for each of the given PERIOD (which can be
-            day, month or year) where there exist selected samples. All the reports
-            will then be printed one after another or, in the case of CSV output,
-            with an additional column.</para>
+            minute, hour,day, month or year) where there exist selected
+            samples. All the reports will then be printed one after another or,
+            in the case of CSV output, with an additional column.</para>
 
             <para>Note that if this option is enabled, samples that are filtered out
             are completely ignored (to avoid empty reports for periods with
@@ -913,7 +1428,7 @@
         <term><option>-V</option></term>
         <term><option>--version</option></term>
         <listitem><simpara>shows the version number, and exists.</simpara></listitem>
-	</varlistentry>
+    </varlistentry>
         <varlistentry>
         <term><option>-f</option> <replaceable>FILE</replaceable></term>
         <term><option>--logfile</option> <replaceable>FILE</replaceable></term>
@@ -923,7 +1438,7 @@
         <term><option>-t</option> <replaceable>FORMAT</replaceable></term>
         <term><option>--format</option> <replaceable>FORMAT</replaceable></term>
         <listitem><simpara>dumping format to use, where <replaceable>FORMAT</replaceable> is one of <literal>human</literal> (the default), <literal>show</literal> or <literal>JSON</literal>. Case in-sensitive.</simpara></listitem>
-	</varlistentry>
+    </varlistentry>
         <varlistentry>
         <term><option>-l</option> <replaceable>NUMBER</replaceable></term>
         <term><option>--last</option> <replaceable>NUMBER</replaceable></term>
@@ -1045,7 +1560,7 @@
         <term><option>-V</option></term>
         <term><option>--version</option></term>
         <listitem><simpara>shows the version number, and exists.</simpara></listitem>
-	</varlistentry>
+    </varlistentry>
         <varlistentry>
         <term><option>-f</option> <replaceable>FILE</replaceable></term>
         <term><option>--logfile</option> <replaceable>FILE</replaceable></term>
@@ -1111,7 +1626,7 @@
         <term><option>-V</option></term>
         <term><option>--version</option></term>
         <listitem><simpara>shows the version number, and exists.</simpara></listitem>
-	</varlistentry>
+    </varlistentry>
         <varlistentry>
         <term><option>-i</option></term>
         <term><option>--infile</option></term>
@@ -1194,14 +1709,32 @@
   The version history with changes relevant for the user is documented here.
   </para>
 
+  <sect2 id="release-notes-0.9">
+    <title>Version 0.9</title>
+    <itemizedlist>
+      <listitem>
+    <para>
+          The <option>--for-each</option> option of
+          <command>arbtt-stats</command> now supports grouping results by
+          minute or hour.
+    </para>
+      </listitem>
+      <listitem>
+    <para>
+      Gwern Branwen contributed the <xref linkend="effective-use"/>.
+    </para>
+      </listitem>
+    </itemizedlist>
+  </sect2>
+
   <sect2 id="release-notes-0.8.1">
     <title>Version 0.8.1</title>
     <itemizedlist>
       <listitem>
-	<para>
-	  The syntax now allows for time differences larger than 99:99.
+    <para>
+      The syntax now allows for time differences larger than 99:99.
           (<ulink url="https://bitbucket.org/nomeata/arbtt/issue/14/improve-time-categorization-directives">issue #14</ulink>)
-	</para>
+    </para>
       </listitem>
     </itemizedlist>
   </sect2>
@@ -1210,12 +1743,12 @@
     <title>Version 0.8</title>
     <itemizedlist>
       <listitem>
-	<para>
-	  <command>arbtt-dump</command> can now show the data in other formats
-	  as well, as suggested by Waldir Pimenta (option
-	  <option>--format</option>). This includes a human-readale output and
-	  JSON.
-	</para>
+    <para>
+      <command>arbtt-dump</command> can now show the data in other formats
+      as well, as suggested by Waldir Pimenta (option
+      <option>--format</option>). This includes a human-readale output and
+      JSON.
+    </para>
       </listitem>
       <listitem>
         <para>New option <option>--last</option> of <command>arbtt-dump</command>.</para>
@@ -1272,9 +1805,9 @@
     <title>Version 0.6.4.1</title>
     <itemizedlist>
       <listitem>
-      	<para>
-      	Added missing module to the packages.
-      	</para>
+        <para>
+        Added missing module to the packages.
+        </para>
       </listitem>
     </itemizedlist>
   </sect2>
@@ -1357,7 +1890,7 @@
     <title>Version 0.5 (The ZuriHac-Release)</title>
     <itemizedlist>
       <listitem>
-	<para>New command <command>arbtt-import</command>, which imports the output from <command>arbtt-dump</command>.
+    <para>New command <command>arbtt-import</command>, which imports the output from <command>arbtt-dump</command>.
         </para>
       </listitem>
       <listitem>
@@ -1376,14 +1909,14 @@
       </listitem>
       <listitem>
         <para>
-	  Unicode is handled correctly in regular expressions.
-	</para>
+      Unicode is handled correctly in regular expressions.
+    </para>
       </listitem>
       <listitem>
         <para>
-	  Improved date-handling functions for <filename>categorize.cfg</filename>.
+      Improved date-handling functions for <filename>categorize.cfg</filename>.
           (<xref linkend="sergey"/>)
-	</para>
+    </para>
       </listitem>
     </itemizedlist>
   </sect2>
@@ -1393,7 +1926,7 @@
     <itemizedlist>
       <listitem>
         <para>Bugfix: Added missing modules to the cabal file.
-	</para>
+    </para>
       </listitem>
     </itemizedlist>
   </sect2>
@@ -1403,7 +1936,7 @@
     <itemizedlist>
       <listitem>
         <para>Implement a custom compression method greatly reduce the file size of the log file. Run <command>arbtt-capture</command> to compress the previous samples as well.
-	</para>
+    </para>
       </listitem>
     </itemizedlist>
   </sect2>
@@ -1438,11 +1971,11 @@
         <command>arbtt-dump</command>.</para>
       </listitem>
       <listitem>
-	<para>New command <command>arbtt-recover</command> to rescue data from
-	a proken data log file.</para>
+    <para>New command <command>arbtt-recover</command> to rescue data from
+    a proken data log file.</para>
       </listitem>
       <listitem>
-	<para>Actually include this documentation in the released tarball.</para>
+    <para>Actually include this documentation in the released tarball.</para>
       </listitem>
     </itemizedlist>
   </sect2>
diff --git a/src/Data/MyText.hs b/src/Data/MyText.hs
--- a/src/Data/MyText.hs
+++ b/src/Data/MyText.hs
@@ -1,6 +1,7 @@
 module Data.MyText where
 
 import qualified Data.ByteString.UTF8 as BSU
+import qualified Data.ByteString.Lazy.UTF8 as BLU
 import qualified Data.ByteString.Lazy as LBS
 import qualified Data.ByteString as BS
 import Data.Binary.Get
@@ -27,17 +28,22 @@
 -- Binary instance compatible with Binary String
 instance Binary Text where
     put = put . unpack
+    get = pack <$> get
     -- The following code exploits that the Binary Char instance uses UTF8 as well
     -- The downside is that it quietly suceeds for broken input
+    -- Unfortunately, with binary-0.7, it is no longer possible to implement
+    -- this nice and lazily, so go via String :-(
+    {-
     get = do
         n <- get :: Get Int
-        r <- remaining
-        bs <- lookAhead (getByteString (min (fromIntegral r) (4*n))) -- safe approximation
-        let utf8bs = BSU.take n bs
-        unless (BSU.length utf8bs == n) $
+        bs <- lookAhead $ getRemainingLazyByteString
+        let utf8bs = BLU.take (fromIntegral n) bs
+        unless (BLU.length utf8bs == n) $
             fail $ "Coult not parse the expected " ++ show n ++ " utf8 characters."
-        skip (BS.length utf8bs)
-        return $ Text utf8bs
+        let sbs = LBS.toStrict utf8bs
+        skip $ BS.length sbs
+        return $ Text sbs
+    -}
 
 {- Possible speedup with a version of binary that provides access to the
    internals, as the Char instance is actually UTF8, but the length bit is
diff --git a/src/Stats.hs b/src/Stats.hs
--- a/src/Stats.hs
+++ b/src/Stats.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE RecordWildCards, NamedFieldPuns, TypeOperators, TupleSections #-}
+{-# LANGUAGE RecordWildCards, NamedFieldPuns, TypeOperators, TupleSections, GADTSyntax, ExistentialQuantification #-}
 module Stats (
     Report(..),
     ReportOptions(..),
@@ -57,7 +57,7 @@
 data ActivityFilter = ExcludeActivity ActivityMatcher | OnlyActivity ActivityMatcher
         deriving (Show, Eq)
 
-data Repeater = ByDay | ByMonth | ByYear
+data Repeater = ByMinute | ByHour | ByDay | ByMonth | ByYear
         deriving (Show, Eq)
 
 -- Supported report output formats: text, comma-separated values and
@@ -174,19 +174,40 @@
                 let go' m act = M.insertWith' (+) act (fromInteger (tlRate tl)/1000) m
                 in foldl' go' m (snd (tlData tl))) id
 
-processRepeater :: Repeater -> LeftFold (Bool :!: TimeLogEntry (Ctx, ActivityData)) ReportResults -> LeftFold (Bool :!: TimeLogEntry (Ctx, ActivityData)) ReportResults
-processRepeater ByDay rep =
-    filterElems (\(b :!: _) -> b) $
-    pure (RepeatedReportResults "Day" . map (\(d,rr) -> (showGregorian d, rr)) . M.toList) <*>
-    multiplex (utctDay . tlTime . Strict.snd) rep
-processRepeater ByMonth rep =
-    filterElems (\(b :!: _) -> b) $
-    pure (RepeatedReportResults "Month" . map (\((y,m),rr) -> (show y ++ "-" ++ show m, rr)) . M.toList) <*>
-    multiplex ((\(y,m,_) -> (y, m)). toGregorian . utctDay . tlTime . Strict.snd) rep
-processRepeater ByYear rep =
-    filterElems (\(b :!: _) -> b) $
-    pure (RepeatedReportResults "Year" . map (\(y,rr) -> (show y, rr)) . M.toList) <*>
-    multiplex ((\(y,_,_) -> y). toGregorian . utctDay . tlTime . Strict.snd) rep
+processRepeater :: TimeZone -> Repeater -> LeftFold (Bool :!: TimeLogEntry (Ctx, ActivityData)) ReportResults -> LeftFold (Bool :!: TimeLogEntry (Ctx, ActivityData)) ReportResults
+processRepeater tz r rep = case repeaterImpl r of
+    RepeaterImpl catR showR ->
+        filterElems (\(b :!: _) -> b) $
+        pure (RepeatedReportResults (repeaterTitle r) . map (first showR) . M.toList) <*>
+        multiplex (catR . utcToLocalTime tz . tlTime . Strict.snd) rep
+
+data RepeaterImpl where
+  RepeaterImpl :: Ord r => (LocalTime -> r) -> (r -> String) -> RepeaterImpl
+
+repeaterTitle :: Repeater -> String
+repeaterTitle ByMinute = "Minute"
+repeaterTitle ByHour   = "Hour"
+repeaterTitle ByDay    = "Day"
+repeaterTitle ByMonth  = "Month"
+repeaterTitle ByYear   = "Year"
+
+repeaterImpl :: Repeater -> RepeaterImpl
+repeaterImpl ByMinute = RepeaterImpl
+    -- a somewhat lazy implementations, using strings...
+    (formatTime defaultTimeLocale "%F %H:%M")
+    id
+repeaterImpl ByHour = RepeaterImpl
+    (formatTime defaultTimeLocale "%F %H:00")
+    id
+repeaterImpl ByDay = RepeaterImpl
+    localDay
+    showGregorian
+repeaterImpl ByMonth = RepeaterImpl
+    ((\(y,m,_) -> (y, m)) . toGregorian . localDay)
+    (\(y,m) -> show y ++ "-" ++ show m)
+repeaterImpl ByYear = RepeaterImpl
+    ((\(y,_,_) -> y) . toGregorian . localDay)
+    show
 
 processReport :: ReportOptions -> Report -> LeftFold (Bool :!: TimeLogEntry (Ctx, ActivityData)) ReportResults
 processReport opts GeneralInfos =
diff --git a/src/TimeLog.hs b/src/TimeLog.hs
--- a/src/TimeLog.hs
+++ b/src/TimeLog.hs
@@ -105,13 +105,13 @@
         return $ parseTimeLog content
 
 parseTimeLog :: (NFData a, ListOfStringable a) => BS.ByteString -> TimeLog a
-parseTimeLog input = 
+parseTimeLog input =
     if startString == magic
        then go Nothing rest off
        else error $
             "Timelog starts with unknown marker " ++
             show (map (chr.fromIntegral) (BS.unpack startString))
-  where                
+  where
     (startString, rest, off) = runGetState (getLazyByteString (BS.length magic)) input 0
     go prev input off =
         let (v, rest, off') = runGetState (ls_get strs) input off
diff --git a/src/stats-main.hs b/src/stats-main.hs
--- a/src/stats-main.hs
+++ b/src/stats-main.hs
@@ -18,6 +18,7 @@
 import System.ProgressBar
 import TermSize
 import qualified Data.MyText as T
+import Data.Time.LocalTime
 
 import TimeLog
 import Categorize
@@ -48,7 +49,7 @@
     , optLogFile = dir </> "capture.log"
     , optCategorizeFile = dir </> "categorize.cfg"
     }
-    
+
 versionStr, header :: String
 versionStr = "arbtt-stats " ++ showVersion version
 header = "Usage: arbtt-stats [OPTIONS...]"
@@ -91,7 +92,7 @@
               "only consider samples matching the condition"
      , Option "m"       ["min-percentage"]
               (ReqArg (\arg opt -> let ro = (optReportOptions opt) { roMinPercentage = read arg}
-                                   in  return opt { optReportOptions = ro }) "COND")
+                                   in  return opt { optReportOptions = ro }) "PERC")
               "do not show tags with a percentage lower than PERC% (default: 1)"
      , Option ""        ["output-exclude"]
               (ReqArg (\arg opt -> let filters = ExcludeActivity (parseActivityMatcher arg) : roActivityFilter (optReportOptions opt)
@@ -141,10 +142,12 @@
 readRepeater :: String -> Repeater
 readRepeater arg =
     case map toLower arg of
-        "day"   -> ByDay
-        "month" -> ByMonth
-        "year"  -> ByYear
-        _       -> error ("Unsupported parameter to --for-each: '" ++ arg ++ "'")
+        "minute" -> ByMinute
+        "hour"   -> ByHour
+        "day"    -> ByDay
+        "month"  -> ByMonth
+        "year"   -> ByYear
+        _        -> error ("Unsupported parameter to --for-each: '" ++ arg ++ "'")
 
 readReportFormat :: String -> ReportFormat
 readReportFormat arg =
@@ -163,6 +166,7 @@
           (_,_,errs) -> do
                 hPutStr stderr (concat errs ++ usageInfo header options)
                 exitFailure
+  tz <- getCurrentTimeZone
 
   dir <- getAppUserDataDirectory "arbtt"
   flags <- foldl (>>=) (return (defaultOptions dir)) actions
@@ -175,12 +179,12 @@
   categorizer <- readCategorizer (optCategorizeFile flags)
 
   timelog <- BS.readFile (optLogFile flags)
-  size <- fileSize <$> getFileStatus (optLogFile flags)
   isTerm <- hIsTerminalDevice stderr
 
   trackedTimelog <- case isTerm of
     True -> do
       hSetBuffering stderr NoBuffering
+      size <- fileSize <$> getFileStatus (optLogFile flags)
       trackProgressWithChunkSize (fromIntegral size `div` 100) (\_ b -> do
         (_height, width) <- getTermSize
         hPutChar stderr '\r'
@@ -195,28 +199,26 @@
 
   let captures = parseTimeLog trackedTimelog
   let allTags = categorizer captures
+
   when (null allTags) $ do
      putStrLn "Nothing recorded yet"
      exitFailure
-      
+
   let filters = (if optAlsoInactive flags then id else (defaultFilter:)) $ optFilters flags
 
   let rep = case optReports flags of
                 [] -> TotalTime
                 [x] -> x
                 _ -> error "Please specify exactly one report to generate"
-  let repeater = foldr (.) id $ map processRepeater (optRepeater flags)
+  let repeater = foldr (.) id $ map (processRepeater tz) (optRepeater flags)
 
-  -- These are defined here, but of course only evaluated when any report
-  -- refers to them. Some are needed by more than one report, which is then
-  -- advantageous.
   let opts = optReportOptions flags
   let fold = filterPredicate filters `adjoin` repeater (processReport opts rep)
   let result = runLeftFold fold allTags
 
   -- Force the results a bit, to ensure the progress bar to be shown before the title
   result `seq` return ()
-  
+
   renderReport opts result
 
 {-
