diff --git a/app/assets/javascripts/discourse/models/category.js b/app/assets/javascripts/discourse/models/category.js
index 7ce772e61..fb944c4f3 100644
--- a/app/assets/javascripts/discourse/models/category.js
+++ b/app/assets/javascripts/discourse/models/category.js
@@ -134,7 +134,34 @@ Discourse.Category = Discourse.Model.extend({
 
   newTopics: function(){
     return this.get('topicTrackingState').countNew(this.get('name'));
-  }.property('topicTrackingState.messageCount')
+  }.property('topicTrackingState.messageCount'),
+
+  totalTopicsTitle: function() {
+    return I18n.t('categories.total_topics', {count: this.get('topic_count')});
+  }.property('post_count'),
+
+  totalPostsTitle: function() {
+    return I18n.t('categories.total_posts', {count: this.get('post_count')});
+  }.property('post_count'),
+
+  topicCountStatsStrings: function() {
+    return this.countStatsStrings('topics');
+  }.property('posts_year', 'posts_month', 'posts_week', 'posts_day'),
+
+  postCountStatsStrings: function() {
+    return this.countStatsStrings('posts');
+  }.property('posts_year', 'posts_month', 'posts_week', 'posts_day'),
+
+  countStatsStrings: function(prefix) {
+    var sep = ' / ';
+    if (this.get(prefix + '_day') > 1) {
+      return [this.get(prefix + '_day') + sep + I18n.t('day'), this.get(prefix + '_week') + sep + I18n.t('week')];
+    } else if (this.get(prefix + '_week') > 1) {
+      return [this.get(prefix + '_week') + sep + I18n.t('week'), this.get(prefix + '_month') + sep + I18n.t('month')];
+    } else {
+      return [this.get(prefix + '_month') + sep + I18n.t('month'), this.get(prefix + '_year') + sep + I18n.t('year')];
+    }
+  }
 
 });
 
diff --git a/app/assets/javascripts/discourse/templates/list/wide_categories.js.handlebars b/app/assets/javascripts/discourse/templates/list/wide_categories.js.handlebars
index 3135804c1..be2be9e84 100644
--- a/app/assets/javascripts/discourse/templates/list/wide_categories.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/list/wide_categories.js.handlebars
@@ -5,8 +5,8 @@
     <tr>
       <th class='category'>{{i18n categories.category}}</th>
       <th class='latest'>{{i18n categories.latest}}</th>
-      <th class='num topics'>{{i18n categories.topics}}</th>
-      <th class='num posts'>{{i18n categories.posts}}
+      <th class='stats topics'>{{i18n categories.topics}}</th>
+      <th class='stats posts'>{{i18n categories.posts}}
         {{#if canEdit}}
           <button title='{{i18n categories.toggle_ordering}}' class='btn toggle-admin no-text' {{action toggleOrdering}}><i class='fa fa-wrench'></i></button>
         {{/if}}
@@ -79,8 +79,16 @@
             </div>
           {{/each}}
         </td>
-        <td class='num'>{{number topic_count}}</td>
-        <td class='num'>{{number post_count}}</td>
+        <td class='stats' {{bindAttr title="totalTopicsTitle"}}>
+          {{#each stat in topicCountStatsStrings}}
+            {{stat}}<br/>
+          {{/each}}
+        </td>
+        <td class='stats' {{bindAttr title="totalPostsTitle"}}>
+          {{#each stat in postCountStatsStrings}}
+            {{stat}}<br/>
+          {{/each}}
+        </td>
       </tr>
       {{/each}}
     </tbody>
diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss
index c6cd133a8..d77d2acdd 100644
--- a/app/assets/stylesheets/desktop/topic-list.scss
+++ b/app/assets/stylesheets/desktop/topic-list.scss
@@ -250,14 +250,13 @@
   th.num {
     width: 45px;
   }
+  th.stats {
+    width: 90px;
+  }
   .last-user-info {
     font-size: 12px;
   }
 
-  .has-description td.category {
-    padding-top: 15px;
-  }
-
   .has-description {
     td.category {
       padding-top: 15px;
@@ -266,7 +265,7 @@
 
   .category{
     position: relative;
-    width: 55%;
+    width: 45%;
 
     .subcategories {
       margin-top: 10px;
diff --git a/app/models/category.rb b/app/models/category.rb
index 512070ab2..bbd9ee655 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -105,7 +105,7 @@ class Category < ActiveRecord::Base
     end
   end
 
-  # Internal: Update category stats: # of topics in past year, month, week for
+  # Internal: Update category stats: # of topics and posts in past year, month, week for
   # all categories.
   def self.update_stats
     topics = Topic
@@ -135,11 +135,62 @@ class Category < ActiveRecord::Base
 
 SQL
 
+    posts = Post.select("count(*) post_count")
+                .joins(:topic)
+                .where('topics.category_id = categories.id')
+                .where('topics.visible = true')
+                .where("topics.id NOT IN (select cc.topic_id from categories cc WHERE topic_id IS NOT NULL)")
+                .where('posts.deleted_at IS NULL')
+                .where('posts.user_deleted = false')
+
+    posts_year = posts.created_since(1.year.ago).to_sql
+    posts_month = posts.created_since(1.month.ago).to_sql
+    posts_week = posts.created_since(1.week.ago).to_sql
 
     # TODO don't update unchanged data
     Category.update_all("topics_year = (#{topics_year}),
                          topics_month = (#{topics_month}),
-                         topics_week = (#{topics_week})")
+                         topics_week = (#{topics_week}),
+                         posts_year = (#{posts_year}),
+                         posts_month = (#{posts_month}),
+                         posts_week = (#{posts_week})")
+  end
+
+  def visible_posts
+    query = Post.joins(:topic)
+                .where(['topics.category_id = ?', self.id])
+                .where('topics.visible = true')
+                .where('posts.deleted_at IS NULL')
+                .where('posts.user_deleted = false')
+    self.topic_id ? query.where(['topics.id <> ?', self.topic_id]) : query
+  end
+
+  def topics_day
+    if val = $redis.get(topics_day_key)
+      val.to_i
+    else
+      val = self.topics.where(['topics.id <> ?', self.topic_id]).created_since(1.day.ago).visible.count
+      $redis.setex topics_day_key, 30.minutes.to_i, val
+      val
+    end
+  end
+
+  def topics_day_key
+    "topics_day:cat-#{self.id}"
+  end
+
+  def posts_day
+    if val = $redis.get(posts_day_key)
+      val.to_i
+    else
+      val = self.visible_posts.created_since(1.day.ago).count
+      $redis.setex posts_day_key, 30.minutes.to_i, val
+      val
+    end
+  end
+
+  def posts_day_key
+    "posts_day:cat-#{self.id}"
   end
 
   # Internal: Generate the text of post prompting to enter category
diff --git a/app/models/post.rb b/app/models/post.rb
index 6b0692e47..8c9e3d11f 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -47,6 +47,7 @@ class Post < ActiveRecord::Base
   scope :by_newest, -> { order('created_at desc, id desc') }
   scope :by_post_number, -> { order('post_number ASC') }
   scope :with_user, -> { includes(:user) }
+  scope :created_since, lambda { |time_ago| where('posts.created_at > ?', time_ago) }
   scope :public_posts, -> { joins(:topic).where('topics.archetype <> ?', Archetype.private_message) }
   scope :private_posts, -> { joins(:topic).where('topics.archetype = ?', Archetype.private_message) }
   scope :with_topic_subtype, ->(subtype) { joins(:topic).where('topics.subtype = ?', subtype) }
diff --git a/app/serializers/category_detailed_serializer.rb b/app/serializers/category_detailed_serializer.rb
index 0065e2049..8551255ea 100644
--- a/app/serializers/category_detailed_serializer.rb
+++ b/app/serializers/category_detailed_serializer.rb
@@ -1,9 +1,15 @@
 class CategoryDetailedSerializer < BasicCategorySerializer
 
-  attributes :post_count,
+  attributes :topic_count,
+             :post_count,
+             :topics_day,
              :topics_week,
              :topics_month,
              :topics_year,
+             :posts_day,
+             :posts_week,
+             :posts_month,
+             :posts_year,
              :description_excerpt,
              :is_uncategorized,
              :subcategory_ids
@@ -11,6 +17,7 @@ class CategoryDetailedSerializer < BasicCategorySerializer
   has_many :featured_users, serializer: BasicUserSerializer
   has_many :displayable_topics, serializer: ListableTopicSerializer, embed: :objects, key: :topics
 
+
   def topics_week
     object.topics_week || 0
   end
@@ -23,6 +30,18 @@ class CategoryDetailedSerializer < BasicCategorySerializer
     object.topics_year || 0
   end
 
+  def posts_week
+    object.posts_week || 0
+  end
+
+  def posts_month
+    object.posts_month || 0
+  end
+
+  def posts_year
+    object.posts_year || 0
+  end
+
   def is_uncategorized
     object.id == SiteSetting.uncategorized_category_id
   end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index c8bc26514..af37bf5ad 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -193,6 +193,8 @@ en:
       latest_by: "latest by"
       toggle_ordering: "toggle ordering control"
       subcategories: "Subcategories:"
+      total_topics: "Total topics: %{count}"
+      total_posts: "Total posts: %{count}"
 
     user:
       said: "{{username}} said:"
@@ -375,6 +377,7 @@ en:
     month_desc: 'topics posted in the last 30 days'
     week: 'week'
     week_desc: 'topics posted in the last 7 days'
+    day: 'day'
 
     first_post: First post
     mute: Mute
diff --git a/db/migrate/20131212225511_add_post_count_stats_columns_to_categories.rb b/db/migrate/20131212225511_add_post_count_stats_columns_to_categories.rb
new file mode 100644
index 000000000..4c42074a2
--- /dev/null
+++ b/db/migrate/20131212225511_add_post_count_stats_columns_to_categories.rb
@@ -0,0 +1,9 @@
+class AddPostCountStatsColumnsToCategories < ActiveRecord::Migration
+  def change
+    change_table :categories do |t|
+      t.integer :posts_year
+      t.integer :posts_month
+      t.integer :posts_week
+    end
+  end
+end
diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb
index 24d932259..4ed827ce1 100644
--- a/spec/models/category_spec.rb
+++ b/spec/models/category_spec.rb
@@ -294,6 +294,9 @@ describe Category do
         @category.topics_year.should == 1
         @category.topic_count.should == 1
         @category.post_count.should == 1
+        @category.posts_year.should == 1
+        @category.posts_month.should == 1
+        @category.posts_week.should == 1
       end
 
     end
@@ -312,8 +315,29 @@ describe Category do
         @category.topics_month.should == 0
         @category.topics_year.should == 0
         @category.post_count.should == 0
+        @category.posts_year.should == 0
+        @category.posts_month.should == 0
+        @category.posts_week.should == 0
+      end
+    end
+
+    context 'with revised post' do
+      before do
+        post = create_post(user: @category.user, category: @category.name)
+
+        SiteSetting.stubs(:ninja_edit_window).returns(1.minute.to_i)
+        post.revise(post.user, 'updated body', revised_at: post.updated_at + 2.minutes)
+
+        Category.update_stats
+        @category.reload
       end
 
+      it "doesn't count each version of a post" do
+        @category.post_count.should == 1
+        @category.posts_year.should == 1
+        @category.posts_month.should == 1
+        @category.posts_week.should == 1
+      end
     end
   end
 
diff --git a/test/javascripts/models/category_test.js b/test/javascripts/models/category_test.js
index 269d9039b..6c9064d3f 100644
--- a/test/javascripts/models/category_test.js
+++ b/test/javascripts/models/category_test.js
@@ -37,4 +37,31 @@ test('findBySlug', function() {
   equal(Discourse.Category.findBySlug('luke', 'darth'), luke, 'we can find a child with parent');
   blank(Discourse.Category.findBySlug('luke'), 'luke is blank without the parent');
   blank(Discourse.Category.findBySlug('luke', 'leia'), 'luke is blank with an incorrect parent');
-});
\ No newline at end of file
+});
+
+test('postCountStatsStrings', function() {
+  var category1 = Discourse.Category.create({id: 1, slug: 'unloved', posts_year: 2, posts_month: 0, posts_week: 0, posts_day: 0}),
+      category2 = Discourse.Category.create({id: 2, slug: 'hasbeen', posts_year: 50, posts_month: 4, posts_week: 0, posts_day: 0}),
+      category3 = Discourse.Category.create({id: 3, slug: 'solastweek', posts_year: 250, posts_month: 200, posts_week: 50, posts_day: 0}),
+      category4 = Discourse.Category.create({id: 4, slug: 'hotstuff', posts_year: 500, posts_month: 280, posts_week: 100, posts_day: 22});
+
+  var result = category1.get('postCountStatsStrings');
+  equal(result.length, 2, "should show month and year");
+  equal(result[0], '0 / month', "should show month and year");
+  equal(result[1], '2 / year', "should show month and year");
+
+  result = category2.get('postCountStatsStrings');
+  equal(result.length, 2, "should show month and year");
+  equal(result[0], '4 / month', "should show month and year");
+  equal(result[1], '50 / year', "should show month and year");
+
+  result = category3.get('postCountStatsStrings');
+  equal(result.length, 2, "should show week and month");
+  equal(result[0], '50 / week', "should show week and month");
+  equal(result[1], '200 / month', "should show week and month");
+
+  result = category4.get('postCountStatsStrings');
+  equal(result.length, 2, "should show day and week");
+  equal(result[0], '22 / day', "should show day and week");
+  equal(result[1], '100 / week', "should show day and week");
+});