diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,10 @@
 # Changelog for Shakebook
 
+## (v0.5.1.0)
+
+* Add `tagIndex` and `monthIndex` instead of filter and partition functions.
+* Drop `hashable-time` dependency.
+
 ## (v0.5.0.0)
 
 * Upgrade to [shake-plus](https://hackage.haskell.org/package/shake-plus) v0.1.6.0.
diff --git a/shakebook.cabal b/shakebook.cabal
--- a/shakebook.cabal
+++ b/shakebook.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 166b471f6106f0f5cbd0ce94e44b3042fb7f4d1b5da4deed5e14c7aea3195b8c
+-- hash: f5bb0ca4ec7dfb075bca03025667a052ba4df3ca169c5d2746369855586f78fd
 
 name:           shakebook
-version:        0.5.0.0
+version:        0.5.1.0
 synopsis:       Shake-based technical documentation generator; HTML & PDF
 description:    Shakebook is a documentation generator aimed at covering all the bases for mathematical, technical and scientific diagrams and typesetting. Shakebook provides combinators for taking markdown files and combining them into documents, but allowing the user to control how. Shakebook provides general combinators for templating single pages, cofree comonads for representing tables of contents, and zipper comonads for representing pagers.
 category:       Web
@@ -45,7 +45,6 @@
     , doctemplates
     , feed
     , free
-    , ixset-typed
     , lens
     , lens-aeson
     , mustache
@@ -79,8 +78,6 @@
     , doctemplates
     , feed
     , free
-    , hashable-time
-    , ixset-typed
     , lens
     , lens-aeson
     , mustache
diff --git a/src/Shakebook/Conventions.hs b/src/Shakebook/Conventions.hs
--- a/src/Shakebook/Conventions.hs
+++ b/src/Shakebook/Conventions.hs
@@ -44,9 +44,8 @@
 , genTocNavbarData
 
 , dateSortPosts
-, monthFilterPosts
-, sameMonth
-, tagFilterPosts
+, monthIndex
+, tagIndex
 ) where
 
 import           Control.Comonad.Cofree
@@ -58,6 +57,7 @@
 import           Data.Aeson.With
 import           Data.Text.Time
 import           RIO                          hiding (view)
+import qualified RIO.HashMap                  as HM
 import           RIO.List
 import           RIO.List.Partial
 import qualified RIO.Text                     as T
@@ -181,29 +181,18 @@
 genLinkData :: Text -> Text -> Value
 genLinkData x u = object ["id" A..= String x, "url" A..= String u]
 
--- | Filter a lists of posts by tag.
-tagFilterPosts :: Text -> [Value] -> [Value]
-tagFilterPosts tag = filter (elem tag . viewTags)
+-- | Create a `HashMap` of posts indexed by tag.
+tagIndex :: [Value] -> HashMap Text [Value]
+tagIndex xs = HM.fromList [(t, filter (elem t . viewTags) xs) | t <- viewAllPostTags xs]
 
+-- | Create a `HashMap` of posts indexed by year-month..
+monthIndex :: [Value] -> HashMap Text [Value]
+monthIndex xs = HM.fromList [(t, filter ((== t) . T.pack . formatTime defaultTimeLocale "%Y-%m" . viewPostTime) xs) | t <- nub (T.pack . formatTime defaultTimeLocale "%Y-%m" <$> viewAllPostTimes xs)]
+
 -- | Sort a lists of posts by date.
 dateSortPosts :: [Value] -> [Value]
 dateSortPosts = sortOn (Down . viewPostTime)
 
--- | Check whether two posts were posted in the same month.
-sameMonth :: UTCTime -> UTCTime -> Bool
-sameMonth a b = y1 == y2 && m1 == m2 where
-  (y1, m1, _) = f a
-  (y2, m2, _) = f b
-  f = toGregorian . utctDay
-
-monthFilterPosts :: UTCTime -> [Value] -> [Value]
-monthFilterPosts time = filter (sameMonth time . viewPostTime)
-
-
--- | Partition a list of posts by the month they were posted.
-partitionToMonths :: [Value] -> [[Value]]
-partitionToMonths = groupBy (on sameMonth viewPostTime) . dateSortPosts
-
 -- | Create a blog navbar object for a posts section, with layers "toc1", "toc2", and "toc3".
 genBlogNavbarData :: Text -- ^ "Top level title, e.g "Blog"
                -> Text -- ^ Root page, e.g "/posts"
@@ -214,12 +203,12 @@
 genBlogNavbarData a b f g xs = object [ "toc1" A..= object [
                                         "title" A..= String a
                                       , "url"   A..= String b
-                                      , "toc2"  A..= Array (V.fromList $ map toc2 (partitionToMonths xs)) ]
+                                      , "toc2"  A..= Array (V.fromList $ map toc2 (HM.elems $ monthIndex xs)) ]
                                      ] where
        toc2 [] = object []
        toc2 t@(x : _) = object [ "title" A..= String (f (viewPostTime x))
-                               , "url"   A..= String (g (viewPostTime x))
-                               , "toc3"  A..= Array (V.fromList t) ]
+                              , "url"   A..= String (g (viewPostTime x))
+                              , "toc3"  A..= Array (V.fromList t) ]
 
 -- | Create a toc navbar object for a docs section, with layers "toc1", "toc2" and "toc3".
 genTocNavbarData :: Cofree [] Value -> Value
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -2,7 +2,6 @@
 
 import           Control.Lens hiding ((:<))
 import           Data.Aeson.With
-import           Data.Hashable.Time()
 import           Data.List.Split
 import           Data.Text.Time
 import           Path.Extensions
@@ -86,22 +85,16 @@
 
   blogTagIndexPageData <- newCache $ \fp -> do
     xs <- sortedPosts fp
-    zs <- forM (viewAllPostTags (snd <$> xs)) $ \t -> do
-      let ys = filter (elem t . viewTags . snd) xs
-      k <- genIndexPageData (snd <$> ys) ("Posts tagged " <> t) (("/posts/tags/" <> t <> "/pages/") <>) postsPerPage
-      return (t, k)
-    return $ HM.fromList zs
+    flip HM.traverseWithKey (tagIndex (snd <$> xs)) $ \t ys ->
+      genIndexPageData ys ("Posts tagged " <> t) (("/posts/tags/" <> t <> "/pages/") <>) postsPerPage
 
   blogMonthIndexPageData <- newCache $ \fp -> do
     xs <- sortedPosts fp
-    zs <- forM (nub $ defaultMonthUrlFormat <$> viewAllPostTimes (snd <$> xs)) $ \t -> do
-      let t' = parseISODateTime . T.pack $ t
-      let ys = filter (sameMonth t' . viewPostTime . snd) xs
-      k <- genIndexPageData (snd <$> ys)
+    flip HM.traverseWithKey (monthIndex (snd <$> xs)) $ \t ys ->
+      let t' = parseISODateTime t
+      in genIndexPageData ys
                    (("Posts from " <>) . T.pack . defaultPrettyMonthFormat $ t')
                    (("/posts/months/"  <> T.pack (defaultMonthUrlFormat t') <> "/pages/") <>) postsPerPage
-      return (t', k)
-    return $ HM.fromList zs
 
   let myPosts = ["posts/*.md"] `within` sourceFolder
 
@@ -172,7 +165,7 @@
     copyFileChangedWithin (o' i) out
 
   o' "posts/months/*/pages/*/index.html" %^> \out -> do
-     let t = parseISODateTime . T.pack . (!! 2) . splitOn "/" . toFilePath . extract $ out
+     let t = T.pack . (!! 2) . splitOn "/" . toFilePath . extract $ out
      let n = (+ (-1)) . read  . (!! 4) . splitOn "/" . toFilePath . extract $ out
      xs <- blogMonthIndexPageData myPosts
      case HM.lookup t xs of
@@ -205,7 +198,7 @@
   phony "by-month-index" $ do
      ps <- blogMonthIndexPageData myPosts
      void $ flip HM.traverseWithKey ps $ \t z -> do
-       u  <- parseRelDir $ defaultMonthUrlFormat t
+       u  <- parseRelDir $ T.unpack t
        fs <- defaultPagePaths [1..size z]
        let monthFolder = outputFolder </> $(mkRelDir "posts/months") </> u
        needIn monthFolder ($(mkRelFile "index.html") : fs)
