James Moger
2012-06-15 9adf6283b75a187b96796b892fd128e300f429a4
Revised TimeUtils for localization

TimeUtils needs to output localized strings like "5 days ago" and "6 months". In order to do this it needs a translation resource. Additionally, that resource can not be static because the single Gitblit server can handle multiple connections in different locales/languages.

TimeUtils has changed from a collection of static methods to some static methods and some instance methods. A TimeUtils is instantiated with an optional resource bundle which contains the preferred translation. If the resourec bundle is null or the requested translation key does not exist, an English default will be used.

This change required adjusting the signatures of several key methods and that cascaded out to adjusting those methods calls in many, many classes.
28 files modified
299 ■■■■■ changed files
src/com/gitblit/client/DateCellRenderer.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/StatusPanel.java 5 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/Translation.java 10 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/IssueModel.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/utils/TimeUtils.java 85 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/WicketUtils.java 22 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/BasePage.java 21 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitPage.java 6 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/FederationRegistrationPage.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/LuceneSearchPage.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/MetricsPage.java 3 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/ReviewProposalPage.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/SummaryPage.java 5 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/TagPage.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/TicketPage.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/TicketsPage.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/ActivityPanel.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/BasePanel.java 17 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/BranchesPanel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/CommitHeaderPanel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/FederationProposalsPanel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/FederationRegistrationsPanel.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/HistoryPanel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/LogPanel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/RepositoriesPanel.java 5 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/SearchPanel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/TagsPanel.java 2 ●●● patch | view | raw | blame | history
tests/com/gitblit/tests/TimeUtilsTest.java 74 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/DateCellRenderer.java
@@ -37,7 +37,7 @@
    private static final long serialVersionUID = 1L;
    private final String pattern;
    public DateCellRenderer(String pattern, Color foreground) {
        this.pattern = (pattern == null ? "yyyy-MM-dd HH:mm" : pattern);
        setForeground(foreground);
@@ -55,7 +55,7 @@
                title = "--";
                dateString = "never";
            } else {
                title = TimeUtils.timeAgo(date);
                title = Translation.getTimeUtils().timeAgo(date);
                dateString = new SimpleDateFormat(pattern).format((Date) value);
            }
src/com/gitblit/client/StatusPanel.java
@@ -35,7 +35,6 @@
import com.gitblit.Constants.RpcRequest;
import com.gitblit.models.ServerStatus;
import com.gitblit.utils.ByteFormat;
import com.gitblit.utils.TimeUtils;
/**
 * This panel displays the server status.
@@ -155,8 +154,8 @@
        ServerStatus status = gitblit.getStatus();
        header.setText(Translation.get("gb.status"));
        version.setText(Constants.NAME + (status.isGO ? " GO v" : " WAR v") + status.version);
        releaseDate.setText(status.releaseDate);
        bootDate.setText(status.bootDate.toString() + " (" + TimeUtils.timeAgo(status.bootDate)
        releaseDate.setText(status.releaseDate);
        bootDate.setText(status.bootDate.toString() + " (" + Translation.getTimeUtils().timeAgo(status.bootDate)
                + ")");
        url.setText(gitblit.url);
        servletContainer.setText(status.servletContainer);
src/com/gitblit/client/Translation.java
@@ -18,6 +18,8 @@
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import com.gitblit.utils.TimeUtils;
/**
 * Loads the Gitblit language resource file.
 * 
@@ -27,6 +29,8 @@
public class Translation {
    private final static ResourceBundle translation;
    private final static TimeUtils timeUtils;
    static {
        ResourceBundle bundle;
@@ -38,6 +42,8 @@
            bundle = ResourceBundle.getBundle("GitBlitWebApp");
        }
        translation = bundle;
        timeUtils = new TimeUtils(translation);
    }
    public static String get(String key) {
@@ -46,4 +52,8 @@
        }
        return key;
    }
    public static TimeUtils getTimeUtils() {
        return timeUtils;
    }
}
src/com/gitblit/models/IssueModel.java
@@ -297,8 +297,8 @@
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(TimeUtils.timeAgo(created));
            StringBuilder sb = new StringBuilder();
            sb.append(new TimeUtils().timeAgo(created));
            switch (code) {
            case '+':
                sb.append(" created by ");
src/com/gitblit/utils/TimeUtils.java
@@ -15,9 +15,11 @@
 */
package com.gitblit.utils;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.ResourceBundle;
/**
 * Utility class of time functions.
@@ -35,6 +37,16 @@
    public static final long ONEDAY = ONEHOUR * 24L;
    public static final long ONEYEAR = ONEDAY * 365L;
    private final ResourceBundle translation;
    public TimeUtils() {
        this(null);
    }
    public TimeUtils(ResourceBundle translation) {
        this.translation = translation;
    }
    /**
     * Returns true if date is today.
@@ -67,21 +79,21 @@
     * @param days
     * @return duration as string in days, months, and/or years
     */
    public static String duration(int days) {
    public String duration(int days) {
        if (days <= 60) {
            return days + (days > 1 ? " days" : " day");
            return (days > 1 ? translate(days, "gb.duration.days", "{0} days") : translate("gb.duration.oneDay", "1 day"));
        } else if (days < 365) {
            int rem = days % 30;
            return ((days / 30) + (rem >= 15 ? 1 : 0)) + " months";
            return translate(((days / 30) + (rem >= 15 ? 1 : 0)), "gb.duration.months", "{0} months");
        } else {
            int years = days / 365;
            int rem = days % 365;
            String yearsString = years + (years > 1 ? " years" : " year");
            String yearsString = (years > 1 ? translate(years, "gb.duration.years", "{0} years") : translate("gb.duration.oneYear", "1 year"));
            if (rem < 30) {
                if (rem == 0) {
                    return yearsString;
                } else {
                    return yearsString + (rem >= 15 ? ", 1 month" : "");
                    return yearsString + (rem >= 15 ? (", " + translate("gb.duration.oneMonth", "1 month")): "");
                }
            } else {
                int months = rem / 30;
@@ -89,8 +101,8 @@
                if (remDays >= 15) {
                    months++;
                }
                String monthsString = yearsString + ", " + months
                        + (months > 1 ? " months" : " month");
                String monthsString = yearsString + ", "
                        + (months > 1 ? translate(months, "gb.duration.months", "{0} months") : translate("gb.duration.oneMonth", "1 month"));
                return monthsString;
            }
        }
@@ -155,6 +167,14 @@
        return days;
    }
    public String today() {
        return translate("gb.time.today", "today");
    }
    public String yesterday() {
        return translate("gb.time.yesterday", "yesterday");
    }
    /**
     * Returns the string representation of the duration between now and the
     * date.
@@ -162,7 +182,7 @@
     * @param date
     * @return duration as a string
     */
    public static String timeAgo(Date date) {
    public String timeAgo(Date date) {
        return timeAgo(date, false);
    }
@@ -172,7 +192,7 @@
     * @param date
     * @return the css class
     */
    public static String timeAgoCss(Date date) {
    public String timeAgoCss(Date date) {
        return timeAgo(date, true);
    }
@@ -184,7 +204,7 @@
     * @param css
     * @return the string representation of the duration OR the css class
     */
    private static String timeAgo(Date date, boolean css) {
    private String timeAgo(Date date, boolean css) {
        if (isToday(date) || isYesterday(date)) {
            int mins = minutesAgo(date, true);
            if (mins >= 120) {
@@ -193,15 +213,18 @@
                }
                int hours = hoursAgo(date, true);
                if (hours > 23) {
                    return "yesterday";
                    return yesterday();
                } else {
                    return hours + " hours ago";
                    return translate(hours, "gb.time.hoursAgo", "{0} hours ago");
                }
            }
            if (css) {
                return "age0";
            }
            return mins + " min" + (mins > 1 ? "s" : "") + " ago";
            if (mins > 2) {
                return translate(mins, "gb.time.minsAgo", "{0} mins ago");
            }
            return translate("gb.time.justNow", "just now");
        } else {
            int days = daysAgo(date);
            if (css) {
@@ -215,13 +238,13 @@
            }
            if (days < 365) {
                if (days <= 30) {
                    return days + " days ago";
                    return translate(days, "gb.time.daysAgo", "{0} days ago");
                } else if (days <= 90) {
                    int weeks = days / 7;
                    if (weeks == 12) {
                        return "3 months ago";
                        return translate(3, "gb.time.monthsAgo", "{0} months ago");
                    } else {
                        return weeks + " weeks ago";
                        return translate(weeks, "gb.time.weeksAgo", "{0} weeks ago");
                    }
                }
                int months = days / 30;
@@ -229,21 +252,43 @@
                if (weeks >= 2) {
                    months++;
                }
                return months + " months ago";
                return translate(months, "gb.time.monthsAgo", "{0} months ago");
            } else if (days == 365) {
                return "1 year ago";
                return translate("gb.time.oneYearAgo", "1 year ago");
            } else {
                int yr = days / 365;
                days = days % 365;
                int months = (yr * 12) + (days / 30);
                if (months > 23) {
                    return yr + " years ago";
                    return translate(yr, "gb.time.yearsAgo", "{0} years ago");
                } else {
                    return months + " months ago";
                    return translate(months, "gb.time.monthsAgo", "{0} months ago");
                }
            }
        }
    }
    private String translate(String key, String defaultValue) {
        String value = defaultValue;
        if (translation != null && translation.containsKey(key)) {
            String aValue = translation.getString(key);
            if (!StringUtils.isEmpty(aValue)) {
                value = aValue;
            }
        }
        return value;
    }
    private String translate(int val, String key, String defaultPattern) {
        String pattern = defaultPattern;
        if (translation != null && translation.containsKey(key)) {
            String aValue = translation.getString(key);
            if (!StringUtils.isEmpty(aValue)) {
                pattern = aValue;
            }
        }
        return MessageFormat.format(pattern, val);
    }
    /**
     * Convert a frequency string into minutes.
src/com/gitblit/wicket/WicketUtils.java
@@ -418,7 +418,7 @@
        return params.getString("n", "");
    }
    public static Label createDateLabel(String wicketId, Date date, TimeZone timeZone) {
    public static Label createDateLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
        String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy");
        DateFormat df = new SimpleDateFormat(format);
        if (timeZone == null) {
@@ -434,7 +434,7 @@
        String title = null;
        if (date.getTime() <= System.currentTimeMillis()) {
            // past
            title = TimeUtils.timeAgo(date);
            title = timeUtils.timeAgo(date);
        }
        if ((System.currentTimeMillis() - date.getTime()) < 10 * 24 * 60 * 60 * 1000L) {
            String tmp = dateString;
@@ -442,14 +442,14 @@
            title = tmp;
        }
        Label label = new Label(wicketId, dateString);
        WicketUtils.setCssClass(label, TimeUtils.timeAgoCss(date));
        WicketUtils.setCssClass(label, timeUtils.timeAgoCss(date));
        if (!StringUtils.isEmpty(title)) {
            WicketUtils.setHtmlTooltip(label, title);
        }
        return label;
    }
    public static Label createTimeLabel(String wicketId, Date date, TimeZone timeZone) {
    public static Label createTimeLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
        String format = GitBlit.getString(Keys.web.timeFormat, "HH:mm");
        DateFormat df = new SimpleDateFormat(format);
        if (timeZone == null) {
@@ -462,7 +462,7 @@
        } else {
            timeString = df.format(date);
        }
        String title = TimeUtils.timeAgo(date);
        String title = timeUtils.timeAgo(date);
        Label label = new Label(wicketId, timeString);
        if (!StringUtils.isEmpty(title)) {
            WicketUtils.setHtmlTooltip(label, title);
@@ -470,7 +470,7 @@
        return label;
    }
    public static Label createDatestampLabel(String wicketId, Date date, TimeZone timeZone) {
    public static Label createDatestampLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
        String format = GitBlit.getString(Keys.web.datestampLongFormat, "EEEE, MMMM d, yyyy");
        DateFormat df = new SimpleDateFormat(format);
        if (timeZone == null) {
@@ -485,12 +485,12 @@
        }
        String title = null;
        if (TimeUtils.isToday(date)) {
            title = "today";
            title = timeUtils.today();
        } else if (TimeUtils.isYesterday(date)) {
                title = "yesterday";
                title = timeUtils.yesterday();
        } else if (date.getTime() <= System.currentTimeMillis()) {
            // past
            title = TimeUtils.timeAgo(date);
            title = timeUtils.timeAgo(date);
        }
        if ((System.currentTimeMillis() - date.getTime()) < 10 * 24 * 60 * 60 * 1000L) {
            String tmp = dateString;
@@ -504,7 +504,7 @@
        return label;
    }
    public static Label createTimestampLabel(String wicketId, Date date, TimeZone timeZone) {
    public static Label createTimestampLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
        String format = GitBlit.getString(Keys.web.datetimestampLongFormat,
                "EEEE, MMMM d, yyyy HH:mm Z");
        DateFormat df = new SimpleDateFormat(format);
@@ -521,7 +521,7 @@
        String title = null;
        if (date.getTime() <= System.currentTimeMillis()) {
            // past
            title = TimeUtils.timeAgo(date);
            title = timeUtils.timeAgo(date);
        }
        Label label = new Label(wicketId, dateString);
        if (!StringUtils.isEmpty(title)) {
src/com/gitblit/wicket/pages/BasePage.java
@@ -17,6 +17,7 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TimeZone;
import javax.servlet.http.Cookie;
@@ -47,6 +48,7 @@
import com.gitblit.Keys;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.TimeUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.LinkPanel;
@@ -54,6 +56,8 @@
public abstract class BasePage extends WebPage {
    private final Logger logger;
    private transient TimeUtils timeUtils;
    public BasePage() {
        super();
@@ -75,6 +79,23 @@
        }
    }
    
    protected String getLanguageCode() {
        return GitBlitWebSession.get().getLocale().getLanguage();
    }
    protected TimeUtils getTimeUtils() {
        if (timeUtils == null) {
            ResourceBundle bundle;
            try {
                bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp", GitBlitWebSession.get().getLocale());
            } catch (Throwable t) {
                bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp");
            }
            timeUtils = new TimeUtils(bundle);
        }
        return timeUtils;
    }
    @Override
    protected void onBeforeRender() {
        if (GitBlit.isDebugMode()) {
src/com/gitblit/wicket/pages/CommitPage.java
@@ -81,12 +81,12 @@
        // author
        add(createPersonPanel("commitAuthor", c.getAuthorIdent(), Constants.SearchType.AUTHOR));
        add(WicketUtils.createTimestampLabel("commitAuthorDate", c.getAuthorIdent().getWhen(),
                getTimeZone()));
                getTimeZone(), getTimeUtils()));
        
        // committer
        add(createPersonPanel("commitCommitter", c.getCommitterIdent(), Constants.SearchType.COMMITTER));
        add(WicketUtils.createTimestampLabel("commitCommitterDate",
                c.getCommitterIdent().getWhen(), getTimeZone()));
                c.getCommitterIdent().getWhen(), getTimeZone(), getTimeUtils()));
        add(new Label("commitId", c.getName()));
@@ -129,7 +129,7 @@
                        Constants.SearchType.AUTHOR));
                item.add(new GravatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
                item.add(WicketUtils.createTimestampLabel("authorDate", entry.notesRef
                        .getAuthorIdent().getWhen(), getTimeZone()));
                        .getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
                item.add(new Label("noteContent", GitBlit.self().processCommitMessage(
                        repositoryName, entry.content)).setEscapeModelStrings(false));
            }
src/com/gitblit/wicket/pages/FederationRegistrationPage.java
@@ -52,8 +52,8 @@
        add(new Label("frequency", registration.frequency));
        add(new Label("folder", registration.folder));
        add(new Label("token", showAdmin ? registration.token : "--"));
        add(WicketUtils.createTimestampLabel("lastPull", registration.lastPull, getTimeZone()));
        add(WicketUtils.createTimestampLabel("nextPull", registration.nextPull, getTimeZone()));
        add(WicketUtils.createTimestampLabel("lastPull", registration.lastPull, getTimeZone(), getTimeUtils()));
        add(WicketUtils.createTimestampLabel("nextPull", registration.nextPull, getTimeZone(), getTimeUtils()));
        StringBuilder inclusions = new StringBuilder();
        for (String inc : registration.inclusions) {
src/com/gitblit/wicket/pages/LuceneSearchPage.java
@@ -234,7 +234,7 @@
                    item.add(new LinkPanel("branch", "branch", StringUtils.getRelativePath(Constants.R_HEADS, sr.branch), LogPage.class, WicketUtils.newObjectParameter(sr.repository, sr.branch)));
                }
                item.add(new Label("author", sr.author));
                item.add(WicketUtils.createDatestampLabel("date", sr.date, getTimeZone()));
                item.add(WicketUtils.createDatestampLabel("date", sr.date, getTimeZone(), getTimeUtils()));
            }
        };
        add(resultsView.setVisible(results.size() > 0));
src/com/gitblit/wicket/pages/MetricsPage.java
@@ -41,7 +41,6 @@
import com.gitblit.models.Metric;
import com.gitblit.utils.MetricUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.wicket.WicketUtils;
public class MetricsPage extends RepositoryPage {
@@ -62,7 +61,7 @@
        } else {
            add(new Label("branchStats",
                    MessageFormat.format(getString("gb.branchStats"), metricsTotal.count,
                            metricsTotal.tag, TimeUtils.duration(metricsTotal.duration))));
                            metricsTotal.tag, getTimeUtils().duration(metricsTotal.duration))));
        }
        insertLinePlot("commitsChart", metrics);
        insertBarPlot("dayOfWeekChart", getDayOfWeekMetrics(r, objectId));
src/com/gitblit/wicket/pages/ReviewProposalPage.java
@@ -54,7 +54,7 @@
        add(new Label("url", proposal.url));
        add(new Label("message", proposal.message));
        add(WicketUtils.createTimestampLabel("received", proposal.received, getTimeZone()));
        add(WicketUtils.createTimestampLabel("received", proposal.received, getTimeZone(), getTimeUtils()));
        add(new Label("token", proposal.token));
        add(new Label("tokenType", proposal.tokenType.name()));
        
src/com/gitblit/wicket/pages/SummaryPage.java
@@ -49,7 +49,6 @@
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.BranchesPanel;
import com.gitblit.wicket.panels.LogPanel;
@@ -84,13 +83,13 @@
        add(new Label("repositoryOwner", getRepositoryModel().owner));
        add(WicketUtils.createTimestampLabel("repositoryLastChange",
                JGitUtils.getLastChange(r), getTimeZone()));
                JGitUtils.getLastChange(r), getTimeZone(), getTimeUtils()));
        if (metricsTotal == null) {
            add(new Label("branchStats", ""));
        } else {
            add(new Label("branchStats",
                    MessageFormat.format(getString("gb.branchStats"), metricsTotal.count,
                            metricsTotal.tag, TimeUtils.duration(metricsTotal.duration))));
                            metricsTotal.tag, getTimeUtils().duration(metricsTotal.duration))));
        }
        add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,
                WicketUtils.newRepositoryParameter(repositoryName)));
src/com/gitblit/wicket/pages/TagPage.java
@@ -87,7 +87,7 @@
        if (tagRef.getAuthorIdent() != null) {
            when = tagRef.getAuthorIdent().getWhen();
        }
        add(WicketUtils.createTimestampLabel("tagDate", when, getTimeZone()));
        add(WicketUtils.createTimestampLabel("tagDate", when, getTimeZone(), getTimeUtils()));
        addFullText("fullMessage", tagRef.getFullMessage(), true);
    }
src/com/gitblit/wicket/pages/TicketPage.java
@@ -42,7 +42,7 @@
        add(new Label("ticketTitle", t.title));
        add(new Label("ticketId", t.id));
        add(new Label("ticketHandler", t.handler.toLowerCase()));
        add(WicketUtils.createTimestampLabel("ticketOpenDate", t.date, getTimeZone()));
        add(WicketUtils.createTimestampLabel("ticketOpenDate", t.date, getTimeZone(), getTimeUtils()));
        Label stateLabel = new Label("ticketState", t.state);
        WicketUtils.setTicketCssClass(stateLabel, t.state);
        add(stateLabel);
@@ -56,7 +56,7 @@
            public void populateItem(final Item<Comment> item) {
                final Comment entry = item.getModelObject();
                item.add(WicketUtils.createDateLabel("commentDate", entry.date, GitBlitWebSession
                        .get().getTimezone()));
                        .get().getTimezone(), getTimeUtils()));
                item.add(new Label("commentAuthor", entry.author.toLowerCase()));
                item.add(new Label("commentText", prepareComment(entry.text))
                        .setEscapeModelStrings(false));
src/com/gitblit/wicket/pages/TicketsPage.java
@@ -52,7 +52,7 @@
                WicketUtils.setTicketCssClass(stateLabel, entry.state);
                item.add(stateLabel);
                item.add(WicketUtils.createDateLabel("ticketDate", entry.date, GitBlitWebSession
                        .get().getTimezone()));
                        .get().getTimezone(), getTimeUtils()));
                item.add(new Label("ticketHandler", StringUtils.trimString(
                        entry.handler.toLowerCase(), 30)));
                item.add(new LinkPanel("ticketTitle", "list subject", StringUtils.trimString(
src/com/gitblit/wicket/panels/ActivityPanel.java
@@ -56,7 +56,7 @@
            public void populateItem(final Item<Activity> activityItem) {
                final Activity entry = activityItem.getModelObject();
                activityItem.add(WicketUtils.createDatestampLabel("title", entry.startDate, getTimeZone()));
                activityItem.add(WicketUtils.createDatestampLabel("title", entry.startDate, getTimeZone(), getTimeUtils()));
                // display the commits in chronological order
                DataView<RepositoryCommit> commits = new DataView<RepositoryCommit>("commit",
@@ -68,7 +68,7 @@
                        // commit time of day
                        commitItem.add(WicketUtils.createTimeLabel("time", commit.getCommitterIdent()
                                .getWhen(), getTimeZone()));
                                .getWhen(), getTimeZone(), getTimeUtils()));
                        // avatar
                        commitItem.add(new GravatarImage("avatar", commit.getAuthorIdent(), 36));
src/com/gitblit/wicket/panels/BasePanel.java
@@ -15,6 +15,7 @@
 */
package com.gitblit.wicket.panels;
import java.util.ResourceBundle;
import java.util.TimeZone;
import org.apache.wicket.AttributeModifier;
@@ -25,12 +26,15 @@
import com.gitblit.Constants;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.utils.TimeUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
public abstract class BasePanel extends Panel {
    private static final long serialVersionUID = 1L;
    private transient TimeUtils timeUtils;
    public BasePanel(String wicketId) {
        super(wicketId);
@@ -40,6 +44,19 @@
        return GitBlit.getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get()
                .getTimezone() : GitBlit.getTimezone();
    }
    protected TimeUtils getTimeUtils() {
        if (timeUtils == null) {
            ResourceBundle bundle;
            try {
                bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp", GitBlitWebSession.get().getLocale());
            } catch (Throwable t) {
                bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp");
            }
            timeUtils = new TimeUtils(bundle);
        }
        return timeUtils;
    }
    protected void setPersonSearchTooltip(Component component, String value, Constants.SearchType searchType) {
        if (searchType.equals(Constants.SearchType.AUTHOR)) {
src/com/gitblit/wicket/panels/BranchesPanel.java
@@ -83,7 +83,7 @@
            public void populateItem(final Item<RefModel> item) {
                final RefModel entry = item.getModelObject();
                item.add(WicketUtils.createDateLabel("branchDate", entry.getDate(), getTimeZone()));
                item.add(WicketUtils.createDateLabel("branchDate", entry.getDate(), getTimeZone(), getTimeUtils()));
                item.add(new LinkPanel("branchName", "list name", StringUtils.trimString(
                        entry.displayName, 28), LogPage.class, WicketUtils.newObjectParameter(
src/com/gitblit/wicket/panels/CommitHeaderPanel.java
@@ -42,7 +42,7 @@
                WicketUtils.newObjectParameter(repositoryName, c.getName())));
        add(new Label("commitid", c.getName()));
        add(new Label("author", c.getAuthorIdent().getName()));
        add(WicketUtils.createDateLabel("date", c.getAuthorIdent().getWhen(), getTimeZone()));
        add(WicketUtils.createDateLabel("date", c.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
        add(new GravatarImage("authorAvatar", c.getAuthorIdent()));
    }
}
src/com/gitblit/wicket/panels/FederationProposalsPanel.java
@@ -56,7 +56,7 @@
                final FederationProposal entry = item.getModelObject();
                item.add(new LinkPanel("url", "list", entry.url, ReviewProposalPage.class,
                        WicketUtils.newTokenParameter(entry.token)));
                item.add(WicketUtils.createDateLabel("received", entry.received, getTimeZone()));
                item.add(WicketUtils.createDateLabel("received", entry.received, getTimeZone(), getTimeUtils()));
                item.add(new Label("tokenType", entry.tokenType.name()));
                item.add(new LinkPanel("token", "list", entry.token, ReviewProposalPage.class,
                        WicketUtils.newTokenParameter(entry.token)));
src/com/gitblit/wicket/panels/FederationRegistrationsPanel.java
@@ -66,9 +66,9 @@
                item.add(WicketUtils.getRegistrationImage("typeIcon", entry, this));
                item.add(WicketUtils.createDateLabel("lastPull", entry.lastPull, getTimeZone()));
                item.add(WicketUtils.createDateLabel("lastPull", entry.lastPull, getTimeZone(), getTimeUtils()));
                item.add(WicketUtils
                        .createTimestampLabel("nextPull", entry.nextPull, getTimeZone()));
                        .createTimestampLabel("nextPull", entry.nextPull, getTimeZone(), getTimeUtils()));
                item.add(new Label("frequency", entry.frequency));
                WicketUtils.setAlternatingBackground(item, counter);
                counter++;
src/com/gitblit/wicket/panels/HistoryPanel.java
@@ -103,7 +103,7 @@
                final RevCommit entry = item.getModelObject();
                final Date date = JGitUtils.getCommitDate(entry);
                item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone()));
                item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
                // author search link
                String author = entry.getAuthorIdent().getName();
src/com/gitblit/wicket/panels/LogPanel.java
@@ -91,7 +91,7 @@
                final RevCommit entry = item.getModelObject();
                final Date date = JGitUtils.getCommitDate(entry);
                item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone()));
                item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
                // author search link
                String author = entry.getAuthorIdent().getName();
src/com/gitblit/wicket/panels/RepositoriesPanel.java
@@ -49,7 +49,6 @@
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.BasePage;
@@ -242,11 +241,11 @@
                if (entry.lastChange.getTime() == 0) {
                    lastChange = "--";
                } else {
                    lastChange = TimeUtils.timeAgo(entry.lastChange);
                    lastChange = getTimeUtils().timeAgo(entry.lastChange);
                }
                Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
                row.add(lastChangeLabel);
                WicketUtils.setCssClass(lastChangeLabel, TimeUtils.timeAgoCss(entry.lastChange));
                WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));
                boolean showOwner = user != null && user.username.equalsIgnoreCase(entry.owner);
                if (showAdmin) {
src/com/gitblit/wicket/panels/SearchPanel.java
@@ -87,7 +87,7 @@
                final RevCommit entry = item.getModelObject();
                final Date date = JGitUtils.getCommitDate(entry);
                item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone()));
                item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
                // author search link
                String author = entry.getAuthorIdent().getName();
src/com/gitblit/wicket/panels/TagsPanel.java
@@ -69,7 +69,7 @@
            public void populateItem(final Item<RefModel> item) {
                RefModel entry = item.getModelObject();
                item.add(WicketUtils.createDateLabel("tagDate", entry.getDate(), getTimeZone()));
                item.add(WicketUtils.createDateLabel("tagDate", entry.getDate(), getTimeZone(), getTimeUtils()));
                Class<? extends RepositoryPage> linkClass;
                switch (entry.getReferencedObjectType()) {
tests/com/gitblit/tests/TimeUtilsTest.java
@@ -53,50 +53,52 @@
    @Test
    public void testDurations() throws Exception {
        assertEquals("1 day", TimeUtils.duration(1));
        assertEquals("5 days", TimeUtils.duration(5));
        assertEquals("3 months", TimeUtils.duration(75));
        assertEquals("12 months", TimeUtils.duration(364));
        assertEquals("1 year", TimeUtils.duration(365 + 0));
        assertEquals("1 year", TimeUtils.duration(365 + 10));
        assertEquals("1 year, 1 month", TimeUtils.duration(365 + 15));
        assertEquals("1 year, 1 month", TimeUtils.duration(365 + 30));
        assertEquals("1 year, 1 month", TimeUtils.duration(365 + 44));
        assertEquals("1 year, 2 months", TimeUtils.duration(365 + 45));
        assertEquals("1 year, 2 months", TimeUtils.duration(365 + 60));
        TimeUtils timeUtils = new TimeUtils();
        assertEquals("1 day", timeUtils.duration(1));
        assertEquals("5 days", timeUtils.duration(5));
        assertEquals("3 months", timeUtils.duration(75));
        assertEquals("12 months", timeUtils.duration(364));
        assertEquals("1 year", timeUtils.duration(365 + 0));
        assertEquals("1 year", timeUtils.duration(365 + 10));
        assertEquals("1 year, 1 month", timeUtils.duration(365 + 15));
        assertEquals("1 year, 1 month", timeUtils.duration(365 + 30));
        assertEquals("1 year, 1 month", timeUtils.duration(365 + 44));
        assertEquals("1 year, 2 months", timeUtils.duration(365 + 45));
        assertEquals("1 year, 2 months", timeUtils.duration(365 + 60));
        assertEquals("2 years", TimeUtils.duration(2 * 365 + 0));
        assertEquals("2 years", TimeUtils.duration(2 * 365 + 10));
        assertEquals("2 years, 1 month", TimeUtils.duration(2 * 365 + 15));
        assertEquals("2 years, 1 month", TimeUtils.duration(2 * 365 + 30));
        assertEquals("2 years, 1 month", TimeUtils.duration(2 * 365 + 44));
        assertEquals("2 years, 2 months", TimeUtils.duration(2 * 365 + 45));
        assertEquals("2 years, 2 months", TimeUtils.duration(2 * 365 + 60));
        assertEquals("2 years", timeUtils.duration(2 * 365 + 0));
        assertEquals("2 years", timeUtils.duration(2 * 365 + 10));
        assertEquals("2 years, 1 month", timeUtils.duration(2 * 365 + 15));
        assertEquals("2 years, 1 month", timeUtils.duration(2 * 365 + 30));
        assertEquals("2 years, 1 month", timeUtils.duration(2 * 365 + 44));
        assertEquals("2 years, 2 months", timeUtils.duration(2 * 365 + 45));
        assertEquals("2 years, 2 months", timeUtils.duration(2 * 365 + 60));
    }
    @Test
    public void testTimeAgo() throws Exception {
        // standard time ago tests
        assertEquals("1 min ago", TimeUtils.timeAgo(offset(1 * TimeUtils.MIN)));
        assertEquals("60 mins ago", TimeUtils.timeAgo(offset(60 * TimeUtils.MIN)));
        assertEquals("2 hours ago", TimeUtils.timeAgo(offset(120 * TimeUtils.MIN)));
        assertEquals("15 hours ago", TimeUtils.timeAgo(offset(15 * TimeUtils.ONEHOUR)));
        assertEquals("yesterday", TimeUtils.timeAgo(offset(24 * TimeUtils.ONEHOUR)));
        assertEquals("2 days ago", TimeUtils.timeAgo(offset(2 * TimeUtils.ONEDAY)));
        assertEquals("5 weeks ago", TimeUtils.timeAgo(offset(35 * TimeUtils.ONEDAY)));
        assertEquals("3 months ago", TimeUtils.timeAgo(offset(84 * TimeUtils.ONEDAY)));
        assertEquals("3 months ago", TimeUtils.timeAgo(offset(95 * TimeUtils.ONEDAY)));
        assertEquals("4 months ago", TimeUtils.timeAgo(offset(104 * TimeUtils.ONEDAY)));
        assertEquals("1 year ago", TimeUtils.timeAgo(offset(365 * TimeUtils.ONEDAY)));
        assertEquals("13 months ago", TimeUtils.timeAgo(offset(395 * TimeUtils.ONEDAY)));
        assertEquals("2 years ago", TimeUtils.timeAgo(offset((2 * 365 + 30) * TimeUtils.ONEDAY)));
        TimeUtils timeUtils = new TimeUtils();
        assertEquals("just now", timeUtils.timeAgo(offset(1 * TimeUtils.MIN)));
        assertEquals("60 mins ago", timeUtils.timeAgo(offset(60 * TimeUtils.MIN)));
        assertEquals("2 hours ago", timeUtils.timeAgo(offset(120 * TimeUtils.MIN)));
        assertEquals("15 hours ago", timeUtils.timeAgo(offset(15 * TimeUtils.ONEHOUR)));
        assertEquals("yesterday", timeUtils.timeAgo(offset(24 * TimeUtils.ONEHOUR)));
        assertEquals("2 days ago", timeUtils.timeAgo(offset(2 * TimeUtils.ONEDAY)));
        assertEquals("5 weeks ago", timeUtils.timeAgo(offset(35 * TimeUtils.ONEDAY)));
        assertEquals("3 months ago", timeUtils.timeAgo(offset(84 * TimeUtils.ONEDAY)));
        assertEquals("3 months ago", timeUtils.timeAgo(offset(95 * TimeUtils.ONEDAY)));
        assertEquals("4 months ago", timeUtils.timeAgo(offset(104 * TimeUtils.ONEDAY)));
        assertEquals("1 year ago", timeUtils.timeAgo(offset(365 * TimeUtils.ONEDAY)));
        assertEquals("13 months ago", timeUtils.timeAgo(offset(395 * TimeUtils.ONEDAY)));
        assertEquals("2 years ago", timeUtils.timeAgo(offset((2 * 365 + 30) * TimeUtils.ONEDAY)));
        // css class tests
        assertEquals("age0", TimeUtils.timeAgoCss(offset(1 * TimeUtils.MIN)));
        assertEquals("age0", TimeUtils.timeAgoCss(offset(60 * TimeUtils.MIN)));
        assertEquals("age1", TimeUtils.timeAgoCss(offset(120 * TimeUtils.MIN)));
        assertEquals("age1", TimeUtils.timeAgoCss(offset(24 * TimeUtils.ONEHOUR)));
        assertEquals("age2", TimeUtils.timeAgoCss(offset(2 * TimeUtils.ONEDAY)));
        assertEquals("age0", timeUtils.timeAgoCss(offset(1 * TimeUtils.MIN)));
        assertEquals("age0", timeUtils.timeAgoCss(offset(60 * TimeUtils.MIN)));
        assertEquals("age1", timeUtils.timeAgoCss(offset(120 * TimeUtils.MIN)));
        assertEquals("age1", timeUtils.timeAgoCss(offset(24 * TimeUtils.ONEHOUR)));
        assertEquals("age2", timeUtils.timeAgoCss(offset(2 * TimeUtils.ONEDAY)));
    }
    @Test