Replace JGit's GitDateFormatter with our own class

We want to be able to configure aspects of the formatter based on
gitiles.config. Create a DateFormatterBuilder class that will hold
this configuration, and refactor the servlets to use it.

Change-Id: If1e9fb32334ef39062fbb1598e8d58752afec599
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/CommitJsonData.java b/gitiles-servlet/src/main/java/com/google/gitiles/CommitJsonData.java
index e887441..01f5ed0 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/CommitJsonData.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/CommitJsonData.java
@@ -19,12 +19,12 @@
 import com.google.common.collect.Sets;
 import com.google.gitiles.CommitData.DiffList;
 import com.google.gitiles.CommitData.Field;
+import com.google.gitiles.DateFormatterBuilder.DateFormatter;
 
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.util.GitDateFormatter;
 
 import java.io.IOException;
 import java.util.List;
@@ -72,11 +72,11 @@
     return this;
   }
 
-  Commit toJsonData(HttpServletRequest req, RevCommit c, GitDateFormatter df) throws IOException {
+  Commit toJsonData(HttpServletRequest req, RevCommit c, DateFormatter df) throws IOException {
     return toJsonData(req, c, DEFAULT_FIELDS, df);
   }
 
-  Commit toJsonData(HttpServletRequest req, RevCommit c, Set<Field> fs, GitDateFormatter df)
+  Commit toJsonData(HttpServletRequest req, RevCommit c, Set<Field> fs, DateFormatter df)
       throws IOException {
     CommitData cd = new CommitData.Builder()
         .setRevWalk(walk)
@@ -107,11 +107,11 @@
     return result;
   }
 
-  private static Ident toJsonData(PersonIdent ident, GitDateFormatter df) {
+  private static Ident toJsonData(PersonIdent ident, DateFormatter df) {
     Ident result = new Ident();
     result.name = ident.getName();
     result.email = ident.getEmailAddress();
-    result.time = df.formatDate(ident);
+    result.time = df.format(ident);
     return result;
   }
 
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/CommitSoyData.java b/gitiles-servlet/src/main/java/com/google/gitiles/CommitSoyData.java
index 8127d6d..3bb4f41 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/CommitSoyData.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/CommitSoyData.java
@@ -26,6 +26,7 @@
 import com.google.common.collect.Sets;
 import com.google.gitiles.CommitData.DiffList;
 import com.google.gitiles.CommitData.Field;
+import com.google.gitiles.DateFormatterBuilder.DateFormatter;
 import com.google.template.soy.data.restricted.NullData;
 
 import org.eclipse.jgit.diff.DiffEntry;
@@ -35,7 +36,6 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.util.GitDateFormatter;
 import org.eclipse.jgit.util.RelativeDateFormatter;
 
 import java.io.IOException;
@@ -75,7 +75,7 @@
   }
 
   Map<String, Object> toSoyData(HttpServletRequest req, RevCommit c, Set<Field> fs,
-      GitDateFormatter df) throws IOException {
+      DateFormatter df) throws IOException {
     GitilesView view = ViewFilter.getView(req);
     CommitData cd = new CommitData.Builder()
         .setRevWalk(walk)
@@ -141,16 +141,16 @@
   }
 
   Map<String, Object> toSoyData(HttpServletRequest req, RevCommit commit,
-      GitDateFormatter df) throws IOException {
+      DateFormatter df) throws IOException {
     return toSoyData(req, commit, DEFAULT_FIELDS, df);
   }
 
   // TODO(dborowitz): Extract this.
-  public static Map<String, String> toSoyData(PersonIdent ident, GitDateFormatter df) {
+  public static Map<String, String> toSoyData(PersonIdent ident, DateFormatter df) {
     return ImmutableMap.of(
         "name", ident.getName(),
         "email", ident.getEmailAddress(),
-        "time", df.formatDate(ident),
+        "time", df.format(ident),
         // TODO(dborowitz): Switch from relative to absolute at some threshold.
         "relativeTime", RelativeDateFormatter.format(ident.getWhen()));
   }
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/DateFormatterBuilder.java b/gitiles-servlet/src/main/java/com/google/gitiles/DateFormatterBuilder.java
new file mode 100644
index 0000000..00d7372
--- /dev/null
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/DateFormatterBuilder.java
@@ -0,0 +1,87 @@
+// Copyright (C) 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gitiles;
+
+import com.google.common.collect.Lists;
+
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.util.SystemReader;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.List;
+import java.util.TimeZone;
+
+/** Date formatter similar in spirit to JGit's {@code GitDateFormatter}. */
+public class DateFormatterBuilder {
+  public static enum Format {
+    // Format strings should match org.eclipse.jgit.util.GitDateFormatter except
+    // for the timezone suffix.
+    DEFAULT("EEE MMM dd HH:mm:ss yyyy"),
+    ISO("yyyy-MM-dd HH:mm:ss");
+
+    private final String fmt;
+
+    private Format(String fmt) {
+      this.fmt = fmt;
+    }
+  }
+
+  public class DateFormatter {
+    private final Format format;
+
+    private DateFormatter(Format format) {
+      this.format = format;
+    }
+
+    public String format(PersonIdent ident) {
+      DateFormat df = getDateFormat(format);
+      TimeZone tz = ident.getTimeZone();
+      if (tz == null) {
+        tz = SystemReader.getInstance().getTimeZone();
+      }
+      df.setTimeZone(tz);
+      return df.format(ident.getWhen());
+    }
+  }
+
+  private final ThreadLocal<List<DateFormat>> dfs;
+
+  DateFormatterBuilder() {
+    this.dfs = new ThreadLocal<List<DateFormat>>();
+  }
+
+  public DateFormatter create(Format format) {
+    return new DateFormatter(format);
+  }
+
+  private DateFormat getDateFormat(Format format) {
+    List<DateFormat> result = dfs.get();
+    if (result == null) {
+      int n = Format.values().length;
+      result = Lists.newArrayListWithCapacity(n);
+      for (int i = 0; i < n; i++) {
+        result.add(null);
+      }
+      dfs.set(result);
+    }
+    DateFormat df = result.get(format.ordinal());
+    if (df == null) {
+      df = new SimpleDateFormat(format.fmt + " Z");
+      result.set(format.ordinal(), df);
+    }
+    return result.get(format.ordinal());
+  }
+}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/DiffServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/DiffServlet.java
index 7af19e2..3be0af1 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/DiffServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/DiffServlet.java
@@ -19,6 +19,8 @@
 
 import com.google.common.base.Charsets;
 import com.google.gitiles.CommitData.Field;
+import com.google.gitiles.DateFormatterBuilder.DateFormatter;
+import com.google.gitiles.DateFormatterBuilder.Format;
 
 import org.eclipse.jgit.diff.DiffFormatter;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -34,8 +36,6 @@
 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.filter.PathFilter;
-import org.eclipse.jgit.util.GitDateFormatter;
-import org.eclipse.jgit.util.GitDateFormatter.Format;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -51,11 +51,13 @@
   private static final long serialVersionUID = 1L;
   private static final String PLACEHOLDER = "id=\"DIFF_OUTPUT_BLOCK\"";
 
+  private final DateFormatterBuilder dfb;
   private final Linkifier linkifier;
 
-  public DiffServlet(GitilesAccess.Factory accessFactory, Renderer renderer,
-      Linkifier linkifier) {
+  public DiffServlet(DateFormatterBuilder dfb, GitilesAccess.Factory accessFactory,
+      Renderer renderer, Linkifier linkifier) {
     super(renderer, accessFactory);
+    this.dfb = checkNotNull(dfb, "dfb");
     this.linkifier = checkNotNull(linkifier, "linkifier");
   }
 
@@ -95,7 +97,7 @@
         if (isFile) {
           fs = Field.setOf(fs, Field.PARENT_BLAME_URL);
         }
-        GitDateFormatter df = new GitDateFormatter(Format.DEFAULT);
+        DateFormatter df = dfb.create(Format.DEFAULT);
         data.put("commit", new CommitSoyData()
             .setLinkifier(linkifier)
             .setArchiveFormat(getArchiveFormat(getAccess(req)))
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
index 5c765d8..34503c3 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
@@ -170,6 +170,7 @@
   private TimeCache timeCache;
   private BlameCache blameCache;
   private GitwebRedirectFilter gitwebRedirect;
+  private DateFormatterBuilder dateFormatterBuilder;
   private boolean initialized;
 
   GitilesFilter() {
@@ -238,23 +239,23 @@
       case HOST_INDEX:
         return new HostIndexServlet(accessFactory, renderer, urls);
       case REPOSITORY_INDEX:
-        return new RepositoryIndexServlet(accessFactory, renderer, timeCache);
+        return new RepositoryIndexServlet(accessFactory, renderer, dateFormatterBuilder, timeCache);
       case REFS:
         return new RefServlet(accessFactory, renderer, timeCache);
       case REVISION:
-        return new RevisionServlet(accessFactory, renderer, linkifier());
+        return new RevisionServlet(accessFactory, renderer, dateFormatterBuilder, linkifier());
       case PATH:
         return new PathServlet(accessFactory, renderer, urls);
       case DIFF:
-        return new DiffServlet(accessFactory, renderer, linkifier());
+        return new DiffServlet(dateFormatterBuilder, accessFactory, renderer, linkifier());
       case LOG:
-        return new LogServlet(accessFactory, renderer, linkifier());
+        return new LogServlet(accessFactory, renderer, dateFormatterBuilder, linkifier());
       case DESCRIBE:
         return new DescribeServlet(accessFactory);
       case ARCHIVE:
         return new ArchiveServlet(accessFactory);
       case BLAME:
-        return new BlameServlet(accessFactory, renderer, blameCache);
+        return new BlameServlet(accessFactory, renderer, dateFormatterBuilder, blameCache);
       default:
         throw new IllegalArgumentException("Invalid view type: " + view);
     }
@@ -310,6 +311,7 @@
     setDefaultTimeCache();
     setDefaultBlameCache();
     setDefaultGitwebRedirect();
+    setDefaultDateFormatterBuilder();
   }
 
   private void setDefaultConfig(FilterConfig filterConfig) throws ServletException {
@@ -419,6 +421,12 @@
     }
   }
 
+  private void setDefaultDateFormatterBuilder() {
+    if (dateFormatterBuilder == null) {
+      dateFormatterBuilder = new DateFormatterBuilder();
+    }
+  }
+
   private static String getBaseGitUrl(Config config) throws ServletException {
     String baseGitUrl = config.getString("gitiles", null, "baseGitUrl");
     if (baseGitUrl == null) {
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java
index e547563..c2fafbb 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java
@@ -25,6 +25,8 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.primitives.Longs;
+import com.google.gitiles.DateFormatterBuilder.DateFormatter;
+import com.google.gitiles.DateFormatterBuilder.Format;
 import com.google.gson.reflect.TypeToken;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -44,8 +46,6 @@
 import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
 import org.eclipse.jgit.treewalk.filter.PathFilter;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
-import org.eclipse.jgit.util.GitDateFormatter;
-import org.eclipse.jgit.util.GitDateFormatter.Format;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -67,10 +67,13 @@
   private static final int DEFAULT_LIMIT = 100;
   private static final int MAX_LIMIT = 10000;
 
+  private final DateFormatterBuilder dfb;
   private final Linkifier linkifier;
 
-  public LogServlet(GitilesAccess.Factory accessFactory, Renderer renderer, Linkifier linkifier) {
+  public LogServlet(GitilesAccess.Factory accessFactory, Renderer renderer,
+      DateFormatterBuilder dfb, Linkifier linkifier) {
     super(renderer, accessFactory);
+    this.dfb = checkNotNull(dfb, "dfb");
     this.linkifier = checkNotNull(linkifier, "linkifier");
   }
 
@@ -85,7 +88,7 @@
     }
 
     try {
-      GitDateFormatter df = new GitDateFormatter(Format.DEFAULT);
+      DateFormatter df = dfb.create(Format.DEFAULT);
       Map<String, Object> data = new LogSoyData(req, view).toSoyData(paginator, null, df);
 
       if (!view.getRevision().nameIsId()) {
@@ -131,7 +134,7 @@
     }
 
     try {
-      GitDateFormatter df = new GitDateFormatter(Format.DEFAULT);
+      DateFormatter df = dfb.create(Format.DEFAULT);
       Map<String, Object> result = Maps.newLinkedHashMap();
       List<CommitJsonData.Commit> entries = Lists.newArrayListWithCapacity(paginator.getLimit());
       for (RevCommit c : paginator) {
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java b/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java
index 1c134cf..71cec89 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java
@@ -19,11 +19,11 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.gitiles.CommitData.Field;
+import com.google.gitiles.DateFormatterBuilder.DateFormatter;
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.util.GitDateFormatter;
 
 import java.io.IOException;
 import java.util.List;
@@ -45,12 +45,12 @@
   }
 
   public Map<String, Object> toSoyData(RevWalk walk, int limit, @Nullable String revision,
-      @Nullable ObjectId start, GitDateFormatter df) throws IOException {
+      @Nullable ObjectId start, DateFormatter df) throws IOException {
     return toSoyData(new Paginator(walk, limit, start), revision, df);
   }
 
   public Map<String, Object> toSoyData(Paginator paginator, @Nullable String revision,
-      GitDateFormatter df) throws IOException {
+      DateFormatter df) throws IOException {
     Map<String, Object> data = Maps.newHashMapWithExpectedSize(3);
 
     List<Map<String, Object>> entries = Lists.newArrayListWithCapacity(paginator.getLimit());
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/RepositoryIndexServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/RepositoryIndexServlet.java
index ec375ba..0df8aec 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/RepositoryIndexServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/RepositoryIndexServlet.java
@@ -20,6 +20,8 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
+import com.google.gitiles.DateFormatterBuilder.DateFormatter;
+import com.google.gitiles.DateFormatterBuilder.Format;
 
 import org.eclipse.jgit.http.server.ServletUtils;
 import org.eclipse.jgit.lib.Constants;
@@ -28,8 +30,6 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.util.GitDateFormatter;
-import org.eclipse.jgit.util.GitDateFormatter.Format;
 
 import java.io.IOException;
 import java.util.List;
@@ -45,11 +45,13 @@
   static final int REF_LIMIT = 10;
   private static final int LOG_LIMIT = 20;
 
+  private final DateFormatterBuilder dfb;
   private final TimeCache timeCache;
 
   public RepositoryIndexServlet(GitilesAccess.Factory accessFactory, Renderer renderer,
-      TimeCache timeCache) {
+      DateFormatterBuilder dfb, TimeCache timeCache) {
     super(renderer, accessFactory);
+    this.dfb = checkNotNull(dfb, "dfb");
     this.timeCache = checkNotNull(timeCache, "timeCache");
   }
 
@@ -74,7 +76,7 @@
         if (head.getType() == Constants.OBJ_COMMIT) {
           walk.reset();
           walk.markStart((RevCommit) head);
-          GitDateFormatter df = new GitDateFormatter(Format.DEFAULT);
+          DateFormatter df = dfb.create(Format.DEFAULT);
           data = new LogSoyData(req, view).toSoyData(walk, LOG_LIMIT, "HEAD", null, df);
         } else {
           // TODO(dborowitz): Handle non-commit or missing HEAD?
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/RevisionServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/RevisionServlet.java
index 113a6f6..565cf76 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/RevisionServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/RevisionServlet.java
@@ -26,6 +26,8 @@
 import com.google.common.collect.Lists;
 import com.google.gitiles.CommitData.Field;
 import com.google.gitiles.CommitJsonData.Commit;
+import com.google.gitiles.DateFormatterBuilder.DateFormatter;
+import com.google.gitiles.DateFormatterBuilder.Format;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -37,8 +39,6 @@
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.util.GitDateFormatter;
-import org.eclipse.jgit.util.GitDateFormatter.Format;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -59,11 +59,13 @@
   private static final long serialVersionUID = 1L;
   private static final Logger log = LoggerFactory.getLogger(RevisionServlet.class);
 
+  private final DateFormatterBuilder dfb;
   private final Linkifier linkifier;
 
   public RevisionServlet(GitilesAccess.Factory accessFactory, Renderer renderer,
-      Linkifier linkifier) {
+      DateFormatterBuilder dfb, Linkifier linkifier) {
     super(renderer, accessFactory);
+    this.dfb = checkNotNull(dfb, "dfb");
     this.linkifier = checkNotNull(linkifier, "linkifier");
   }
 
@@ -74,7 +76,7 @@
 
     RevWalk walk = new RevWalk(repo);
     try {
-      GitDateFormatter df = new GitDateFormatter(Format.DEFAULT);
+      DateFormatter df = dfb.create(Format.DEFAULT);
       List<RevObject> objects = listObjects(walk, view.getRevision());
       List<Map<String, ?>> soyObjects = Lists.newArrayListWithCapacity(objects.size());
       boolean hasBlob = false;
@@ -140,7 +142,7 @@
 
     RevWalk walk = new RevWalk(repo);
     try {
-      GitDateFormatter df = new GitDateFormatter(Format.DEFAULT);
+      DateFormatter df = dfb.create(Format.DEFAULT);
       RevObject obj = walk.parseAny(view.getRevision().getId());
       switch (obj.getType()) {
         case OBJ_COMMIT:
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/TagSoyData.java b/gitiles-servlet/src/main/java/com/google/gitiles/TagSoyData.java
index 9e8d871..e715cdd 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/TagSoyData.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/TagSoyData.java
@@ -15,10 +15,10 @@
 package com.google.gitiles;
 
 import com.google.common.collect.Maps;
+import com.google.gitiles.DateFormatterBuilder.DateFormatter;
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevTag;
-import org.eclipse.jgit.util.GitDateFormatter;
 
 import java.util.Map;
 
@@ -34,7 +34,7 @@
     this.req = req;
   }
 
-  public Map<String, Object> toSoyData(RevTag tag, GitDateFormatter df) {
+  public Map<String, Object> toSoyData(RevTag tag, DateFormatter df) {
     Map<String, Object> data = Maps.newHashMapWithExpectedSize(4);
     data.put("sha", ObjectId.toString(tag));
     if (tag.getTaggerIdent() != null) {
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java
index 34c9016..1530916 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java
@@ -15,7 +15,6 @@
 package com.google.gitiles.blame;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-
 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 
 import com.google.common.base.Strings;
@@ -25,6 +24,9 @@
 import com.google.gitiles.BaseServlet;
 import com.google.gitiles.BlobSoyData;
 import com.google.gitiles.CommitSoyData;
+import com.google.gitiles.DateFormatterBuilder;
+import com.google.gitiles.DateFormatterBuilder.DateFormatter;
+import com.google.gitiles.DateFormatterBuilder.Format;
 import com.google.gitiles.GitilesAccess;
 import com.google.gitiles.GitilesView;
 import com.google.gitiles.Renderer;
@@ -41,8 +43,6 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.util.GitDateFormatter;
-import org.eclipse.jgit.util.GitDateFormatter.Format;
 
 import java.io.IOException;
 import java.util.List;
@@ -55,10 +55,13 @@
 public class BlameServlet extends BaseServlet {
   private static final long serialVersionUID = 1L;
 
+  private final DateFormatterBuilder dfb;
   private final BlameCache cache;
 
-  public BlameServlet(GitilesAccess.Factory accessFactory, Renderer renderer, BlameCache cache) {
+  public BlameServlet(GitilesAccess.Factory accessFactory, Renderer renderer,
+      DateFormatterBuilder dfb, BlameCache cache) {
     super(renderer, accessFactory);
+    this.dfb = checkNotNull(dfb, "dfb");
     this.cache = checkNotNull(cache, "cache");
   }
 
@@ -85,7 +88,7 @@
           res.setStatus(SC_NOT_FOUND);
           return;
         }
-        GitDateFormatter df = new GitDateFormatter(Format.ISO);
+        DateFormatter df = dfb.create(Format.ISO);
         renderHtml(req, res, "gitiles.blameDetail", ImmutableMap.of(
             "title", title,
             "breadcrumbs", view.getBreadcrumbs(),
@@ -129,7 +132,7 @@
   }
 
   private static SoyListData toSoyData(GitilesView view, ObjectReader reader,
-      List<Region> regions, GitDateFormatter df) throws IOException {
+      List<Region> regions, DateFormatter df) throws IOException {
     Map<ObjectId, String> abbrevShas = Maps.newHashMap();
     SoyListData result = new SoyListData();
 
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/DateFormatterBuilderTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/DateFormatterBuilderTest.java
new file mode 100644
index 0000000..c730fa9
--- /dev/null
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/DateFormatterBuilderTest.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gitiles;
+
+import static org.junit.Assert.assertEquals;
+
+import static com.google.gitiles.DateFormatterBuilder.Format.DEFAULT;
+import static com.google.gitiles.DateFormatterBuilder.Format.ISO;
+
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.util.GitDateParser;
+import org.junit.Test;
+
+import java.text.ParseException;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class DateFormatterBuilderTest {
+  @Test
+  public void defaultIncludingTimeZone() throws Exception {
+    DateFormatterBuilder dfb = new DateFormatterBuilder();
+    PersonIdent ident = newIdent("Mon Jan 2 15:04:05 2006", "-0700");
+    assertEquals("Mon Jan 02 15:04:05 2006 -0700", dfb.create(DEFAULT).format(ident));
+  }
+
+  @Test
+  public void isoIncludingTimeZone() throws Exception {
+    DateFormatterBuilder dfb = new DateFormatterBuilder();
+    PersonIdent ident = newIdent("Mon Jan 2 15:04:05 2006", "-0700");
+    assertEquals("2006-01-02 15:04:05 -0700", dfb.create(ISO).format(ident));
+  }
+
+  private PersonIdent newIdent(String whenStr, String tzStr) throws ParseException {
+    whenStr += " " + tzStr;
+    Date when = GitDateParser.parse(whenStr, null);
+    TimeZone tz = TimeZone.getTimeZone("GMT" + tzStr);
+    PersonIdent ident = new PersonIdent("A User", "[email protected]", when, tz);
+    // PersonIdent.toString() uses its own format with "d" instead of "dd",
+    // hence the mismatches in 2 vs. 02 above. Nonetheless I think this sanity
+    // check is useful enough to keep around.
+    assertEquals("PersonIdent[A User, [email protected], " + whenStr + "]", ident.toString());
+    return ident;
+  }
+}
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/RepositoryIndexServletTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/RepositoryIndexServletTest.java
index f239b82..e5a821c 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/RepositoryIndexServletTest.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/RepositoryIndexServletTest.java
@@ -43,6 +43,7 @@
     servlet = new RepositoryIndexServlet(
         new TestGitilesAccess(repo.getRepository()),
         new DefaultRenderer(),
+        new DateFormatterBuilder(),
         new TimeCache());
   }