--- /dev/null
+/*
+ * Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.eclipse.jgit.util.RelativeDateFormatter.YEAR_IN_MILLIS;
+import static org.eclipse.jgit.util.RelativeDateFormatter.SECOND_IN_MILLIS;
+import static org.eclipse.jgit.util.RelativeDateFormatter.MINUTE_IN_MILLIS;
+import static org.eclipse.jgit.util.RelativeDateFormatter.HOUR_IN_MILLIS;
+import static org.eclipse.jgit.util.RelativeDateFormatter.DAY_IN_MILLIS;
+
+import java.util.Date;
+
+import org.eclipse.jgit.util.RelativeDateFormatter;
+import org.junit.Test;
+
+public class RelativeDateFormatterTest {
+
+ private void assertFormat(long ageFromNow, long timeUnit,
+ String expectedFormat) {
+ Date d = new Date(System.currentTimeMillis() - ageFromNow * timeUnit);
+ String s = RelativeDateFormatter.format(d);
+ assertEquals(expectedFormat, s);
+ }
+
+ @Test
+ public void testFuture() {
+ assertFormat(-100, YEAR_IN_MILLIS, "in the future");
+ assertFormat(-1, SECOND_IN_MILLIS, "in the future");
+ }
+
+ @Test
+ public void testFormatSeconds() {
+ assertFormat(1, SECOND_IN_MILLIS, "1 seconds ago");
+ assertFormat(89, SECOND_IN_MILLIS, "89 seconds ago");
+ }
+
+ @Test
+ public void testFormatMinutes() {
+ assertFormat(90, SECOND_IN_MILLIS, "2 minutes ago");
+ assertFormat(3, MINUTE_IN_MILLIS, "3 minutes ago");
+ assertFormat(60, MINUTE_IN_MILLIS, "60 minutes ago");
+ assertFormat(89, MINUTE_IN_MILLIS, "89 minutes ago");
+ }
+
+ @Test
+ public void testFormatHours() {
+ assertFormat(90, MINUTE_IN_MILLIS, "2 hours ago");
+ assertFormat(149, MINUTE_IN_MILLIS, "2 hours ago");
+ assertFormat(35, HOUR_IN_MILLIS, "35 hours ago");
+ }
+
+ @Test
+ public void testFormatDays() {
+ assertFormat(36, HOUR_IN_MILLIS, "2 days ago");
+ assertFormat(13, DAY_IN_MILLIS, "13 days ago");
+ }
+
+ @Test
+ public void testFormatWeeks() {
+ assertFormat(14, DAY_IN_MILLIS, "2 weeks ago");
+ assertFormat(69, DAY_IN_MILLIS, "10 weeks ago");
+ }
+
+ @Test
+ public void testFormatMonths() {
+ assertFormat(70, DAY_IN_MILLIS, "2 months ago");
+ assertFormat(75, DAY_IN_MILLIS, "3 months ago");
+ assertFormat(364, DAY_IN_MILLIS, "12 months ago");
+ }
+
+ @Test
+ public void testFormatYearsMonths() {
+ assertFormat(366, DAY_IN_MILLIS, "1 year, 0 month ago");
+ assertFormat(380, DAY_IN_MILLIS, "1 year, 1 month ago");
+ assertFormat(410, DAY_IN_MILLIS, "1 year, 2 months ago");
+ assertFormat(2, YEAR_IN_MILLIS, "2 years, 0 month ago");
+ assertFormat(1824, DAY_IN_MILLIS, "4 years, 12 months ago");
+ }
+
+ @Test
+ public void testFormatYears() {
+ assertFormat(5, YEAR_IN_MILLIS, "5 years ago");
+ assertFormat(60, YEAR_IN_MILLIS, "60 years ago");
+ }
+}
credentialPassword=Password
credentialUsername=Username
daemonAlreadyRunning=Daemon already running
+daysAgo={0} days ago
deleteBranchUnexpectedResult=Delete branch returned unexpected result {0}
deleteFileFailed=Could not delete file {0}
deletingNotSupported=Deleting {0} not supported.
flagNotFromThis={0} not from this.
flagsAlreadyCreated={0} flags already created.
funnyRefname=funny refname
+hoursAgo={0} hours ago
hugeIndexesAreNotSupportedByJgitYet=Huge indexes are not supported by jgit, yet
hunkBelongsToAnotherFile=Hunk belongs to another file
hunkDisconnectedFromFile=Hunk disconnected from file
integerValueOutOfRange=Integer value {0}.{1} out of range
internalRevisionError=internal revision error
interruptedWriting=Interrupted writing {0}
+inTheFuture=in the future
invalidAdvertisementOf=invalid advertisement of {0}
invalidAncestryLength=Invalid ancestry length
invalidBooleanValue=Invalid boolean value: {0}.{1}={2}
mergeStrategyAlreadyExistsAsDefault=Merge strategy "{0}" already exists as a default strategy
mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads to be merged into HEAD
mergeUsingStrategyResultedInDescription=Merge of revisions {0} with base {1} using strategy {2} resulted in: {3}. {4}
+minutesAgo={0} minutes ago
missingAccesskey=Missing accesskey.
missingConfigurationForKey=No value for key {0} found in configuration
missingDeltaBase=delta base
mixedStagesNotAllowed=Mixed stages not allowed
mkDirFailed=Creating directory {0} failed
mkDirsFailed=Creating directories for {0} failed
+month=month
+months=months
+monthsAgo={0} months ago
multipleMergeBasesFor=Multiple merge bases for:\n {0}\n {1} found:\n {2}\n {3}
need2Arguments=Need 2 arguments
needPackOut=need packOut
rewinding=Rewinding to commit {0}
searchForReuse=Finding sources
searchForSizes=Getting sizes
+secondsAgo={0} seconds ago
sequenceTooLargeForDiffAlgorithm=Sequence too large for difference algorithm.
serviceNotEnabledNoName=Service not enabled
serviceNotPermitted={0} not permitted
userConfigFileInvalid=User config file {0} invalid {1}
walkFailure=Walk failure.
wantNotValid=want {0} not valid
+weeksAgo={0} weeks ago
windowSizeMustBeLesserThanLimit=Window size must be < limit
windowSizeMustBePowerOf2=Window size must be power of 2
writeTimedOut=Write timed out
writingObjects=Writing objects
wrongDecompressedLength=wrong decompressed length
wrongRepositoryState=Wrong Repository State: {0}
+year=year
+years=years
+yearsAgo={0} years ago
+yearsMonthsAgo={0} {1}, {2} {3} ago
/***/ public String credentialPassword;
/***/ public String credentialUsername;
/***/ public String daemonAlreadyRunning;
+ /***/ public String daysAgo;
/***/ public String deleteBranchUnexpectedResult;
/***/ public String deleteFileFailed;
/***/ public String deletingNotSupported;
/***/ public String flagNotFromThis;
/***/ public String flagsAlreadyCreated;
/***/ public String funnyRefname;
+ /***/ public String hoursAgo;
/***/ public String hugeIndexesAreNotSupportedByJgitYet;
/***/ public String hunkBelongsToAnotherFile;
/***/ public String hunkDisconnectedFromFile;
/***/ public String integerValueOutOfRange;
/***/ public String internalRevisionError;
/***/ public String interruptedWriting;
+ /***/ public String inTheFuture;
/***/ public String invalidAdvertisementOf;
/***/ public String invalidAncestryLength;
/***/ public String invalidBooleanValue;
/***/ public String mergeStrategyAlreadyExistsAsDefault;
/***/ public String mergeStrategyDoesNotSupportHeads;
/***/ public String mergeUsingStrategyResultedInDescription;
+ /***/ public String minutesAgo;
/***/ public String missingAccesskey;
/***/ public String missingConfigurationForKey;
/***/ public String missingDeltaBase;
/***/ public String mixedStagesNotAllowed;
/***/ public String mkDirFailed;
/***/ public String mkDirsFailed;
+ /***/ public String month;
+ /***/ public String months;
+ /***/ public String monthsAgo;
/***/ public String multipleMergeBasesFor;
/***/ public String need2Arguments;
/***/ public String needPackOut;
/***/ public String rewinding;
/***/ public String searchForReuse;
/***/ public String searchForSizes;
+ /***/ public String secondsAgo;
/***/ public String sequenceTooLargeForDiffAlgorithm;
/***/ public String serviceNotEnabledNoName;
/***/ public String serviceNotPermitted;
/***/ public String userConfigFileInvalid;
/***/ public String walkFailure;
/***/ public String wantNotValid;
+ /***/ public String weeksAgo;
/***/ public String windowSizeMustBeLesserThanLimit;
/***/ public String windowSizeMustBePowerOf2;
/***/ public String writeTimedOut;
/***/ public String writingObjects;
/***/ public String wrongDecompressedLength;
/***/ public String wrongRepositoryState;
+ /***/ public String year;
+ /***/ public String years;
+ /***/ public String yearsAgo;
+ /***/ public String yearsMonthsAgo;
}
--- /dev/null
+/*
+ * Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.util;
+
+import java.text.MessageFormat;
+import java.util.Date;
+
+import org.eclipse.jgit.JGitText;
+
+/**
+ * Formatter to format timestamps relative to the current time using time units
+ * in the format defined by {@code git log --relative-date}.
+ */
+public class RelativeDateFormatter {
+ final static long SECOND_IN_MILLIS = 1000;
+
+ final static long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
+
+ final static long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
+
+ final static long DAY_IN_MILLIS = 24 * HOUR_IN_MILLIS;
+
+ final static long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS;
+
+ final static long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS;
+
+ final static long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS;
+
+ /**
+ * @param when
+ * {@link Date} to format
+ * @return age of given {@link Date} compared to now formatted in the same
+ * relative format as returned by {@code git log --relative-date}
+ */
+ @SuppressWarnings("boxing")
+ public static String format(Date when) {
+
+ long ageMillis = (System.currentTimeMillis() - when.getTime());
+
+ // shouldn't happen in a perfect world
+ if (ageMillis < 0)
+ return JGitText.get().inTheFuture;
+
+ // seconds
+ if (ageMillis < upperLimit(MINUTE_IN_MILLIS))
+ return MessageFormat.format(JGitText.get().secondsAgo,
+ round(ageMillis, SECOND_IN_MILLIS));
+
+ // minutes
+ if (ageMillis < upperLimit(HOUR_IN_MILLIS))
+ return MessageFormat.format(JGitText.get().minutesAgo,
+ round(ageMillis, MINUTE_IN_MILLIS));
+
+ // hours
+ if (ageMillis < upperLimit(DAY_IN_MILLIS))
+ return MessageFormat.format(JGitText.get().hoursAgo,
+ round(ageMillis, HOUR_IN_MILLIS));
+
+ // up to 14 days use days
+ if (ageMillis < 14 * DAY_IN_MILLIS)
+ return MessageFormat.format(JGitText.get().daysAgo,
+ round(ageMillis, DAY_IN_MILLIS));
+
+ // up to 10 weeks use weeks
+ if (ageMillis < 10 * WEEK_IN_MILLIS)
+ return MessageFormat.format(JGitText.get().weeksAgo,
+ round(ageMillis, WEEK_IN_MILLIS));
+
+ // months
+ if (ageMillis < YEAR_IN_MILLIS)
+ return MessageFormat.format(JGitText.get().monthsAgo,
+ round(ageMillis, MONTH_IN_MILLIS));
+
+ // up to 5 years use "year, months" rounded to months
+ if (ageMillis < 5 * YEAR_IN_MILLIS) {
+ long years = ageMillis / YEAR_IN_MILLIS;
+ String yearLabel = (years > 1) ? JGitText.get().years : //
+ JGitText.get().year;
+ long months = round(ageMillis % YEAR_IN_MILLIS, MONTH_IN_MILLIS);
+ String monthLabel = (months > 1) ? JGitText.get().months : //
+ JGitText.get().month;
+ return MessageFormat.format(JGitText.get().yearsMonthsAgo,
+ new Object[] { years, yearLabel, months, monthLabel });
+ }
+
+ // years
+ return MessageFormat.format(JGitText.get().yearsAgo,
+ round(ageMillis, YEAR_IN_MILLIS));
+ }
+
+ private static long upperLimit(long unit) {
+ long limit = unit + unit / 2;
+ return limit;
+ }
+
+ private static long round(long n, long unit) {
+ long rounded = (n + unit / 2) / unit;
+ return rounded;
+ }
+}
\ No newline at end of file