diff --git a/ext/date.c b/ext/date.c
--- a/ext/date.c
+++ b/ext/date.c
@@ -28,6 +28,8 @@
 	DATE_UNKNOWN
 };
 
+unsigned long approxidate_careful(const char *date, int *error_ret);
+
 /*
  * This is like mktime, but without normalization of tm_wday and tm_yday.
  */
@@ -108,118 +110,6 @@
 	return offset * eastwest;
 }
 
-const char *show_date_relative(unsigned long time, int tz,
-			       const struct timeval *now,
-			       char *timebuf,
-			       size_t timebuf_size)
-{
-	unsigned long diff;
-	if (now->tv_sec < time)
-		return "in the future";
-	diff = now->tv_sec - time;
-	if (diff < 90) {
-		snprintf(timebuf, timebuf_size, "%lu seconds ago", diff);
-		return timebuf;
-	}
-	/* Turn it into minutes */
-	diff = (diff + 30) / 60;
-	if (diff < 90) {
-		snprintf(timebuf, timebuf_size, "%lu minutes ago", diff);
-		return timebuf;
-	}
-	/* Turn it into hours */
-	diff = (diff + 30) / 60;
-	if (diff < 36) {
-		snprintf(timebuf, timebuf_size, "%lu hours ago", diff);
-		return timebuf;
-	}
-	/* We deal with number of days from here on */
-	diff = (diff + 12) / 24;
-	if (diff < 14) {
-		snprintf(timebuf, timebuf_size, "%lu days ago", diff);
-		return timebuf;
-	}
-	/* Say weeks for the past 10 weeks or so */
-	if (diff < 70) {
-		snprintf(timebuf, timebuf_size, "%lu weeks ago", (diff + 3) / 7);
-		return timebuf;
-	}
-	/* Say months for the past 12 months or so */
-	if (diff < 365) {
-		snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30);
-		return timebuf;
-	}
-	/* Give years and months for 5 years or so */
-	if (diff < 1825) {
-		unsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2);
-		unsigned long years = totalmonths / 12;
-		unsigned long months = totalmonths % 12;
-		int n;
-		n = snprintf(timebuf, timebuf_size, "%lu year%s",
-				years, (years > 1 ? "s" : ""));
-		if (months)
-			snprintf(timebuf + n, timebuf_size - n,
-					", %lu month%s ago",
-					months, (months > 1 ? "s" : ""));
-		else
-			snprintf(timebuf + n, timebuf_size - n, " ago");
-		return timebuf;
-	}
-	/* Otherwise, just years. Centuries is probably overkill. */
-	snprintf(timebuf, timebuf_size, "%lu years ago", (diff + 183) / 365);
-	return timebuf;
-}
-
-const char *show_date(unsigned long time, int tz, enum date_mode mode)
-{
-	struct tm *tm;
-	static char timebuf[200];
-
-	if (mode == DATE_RAW) {
-		snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz);
-		return timebuf;
-	}
-
-	if (mode == DATE_RELATIVE) {
-		struct timeval now;
-		gettimeofday(&now, NULL);
-		return show_date_relative(time, tz, &now,
-					  timebuf, sizeof(timebuf));
-	}
-
-	if (mode == DATE_LOCAL)
-		tz = local_tzoffset(time);
-
-	tm = time_to_tm(time, tz);
-	if (!tm)
-		return NULL;
-	if (mode == DATE_SHORT)
-		sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
-				tm->tm_mon + 1, tm->tm_mday);
-	else if (mode == DATE_ISO8601)
-		sprintf(timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
-				tm->tm_year + 1900,
-				tm->tm_mon + 1,
-				tm->tm_mday,
-				tm->tm_hour, tm->tm_min, tm->tm_sec,
-				tz);
-	else if (mode == DATE_RFC2822)
-		sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
-			weekday_names[tm->tm_wday], tm->tm_mday,
-			month_names[tm->tm_mon], tm->tm_year + 1900,
-			tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
-	else
-		sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
-				weekday_names[tm->tm_wday],
-				month_names[tm->tm_mon],
-				tm->tm_mday,
-				tm->tm_hour, tm->tm_min, tm->tm_sec,
-				tm->tm_year + 1900,
-				(mode == DATE_LOCAL) ? 0 : ' ',
-				tz);
-	return timebuf;
-}
-
 /*
  * Check these. And note how it doesn't do the summer-time conversion.
  *
@@ -388,7 +278,7 @@
 		 * sense to specify timestamp way into the future.  Make
 		 * sure it is not later than ten days from now...
 		 */
-		if (now + 10*24*3600 < specified)
+		if ((specified != -1) && (now + 10*24*3600 < specified))
 			return 0;
 		tm->tm_mon = r->tm_mon;
 		tm->tm_mday = r->tm_mday;
@@ -629,7 +519,7 @@
 	unsigned long stamp;
 	int ofs;
 
-	if (*date < '0' || '9' <= *date)
+	if (*date < '0' || '9' < *date)
 		return -1;
 	stamp = strtoul(date, &end, 10);
 	if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
@@ -699,8 +589,14 @@
 
 	/* mktime uses local timezone */
 	*timestamp = tm_to_time_t(&tm);
-	if (*offset == -1)
-		*offset = ((time_t)*timestamp - mktime(&tm)) / 60;
+	if (*offset == -1) {
+		time_t temp_time = mktime(&tm);
+		if ((time_t)*timestamp > temp_time) {
+			*offset = ((time_t)*timestamp - temp_time) / 60;
+		} else {
+			*offset = -(int)((temp_time - (time_t)*timestamp) / 60);
+		}
+	}
 
 	if (*timestamp == -1)
 		return -1;
@@ -708,6 +604,28 @@
 	if (!tm_gmt)
 		*timestamp -= *offset * 60;
 	return 0; /* success */
+}
+
+int parse_expiry_date(const char *date, unsigned long *timestamp)
+{
+	int errors = 0;
+
+	if (!strcmp(date, "never") || !strcmp(date, "false"))
+		*timestamp = 0;
+	else if (!strcmp(date, "all") || !strcmp(date, "now"))
+		/*
+		 * We take over "now" here, which usually translates
+		 * to the current timestamp.  This is because the user
+		 * really means to expire everything she has done in
+		 * the past, and by definition reflogs are the record
+		 * of the past, and there is nothing from the future
+		 * to be kept.
+		 */
+		*timestamp = ULONG_MAX;
+	else
+		*timestamp = approxidate_careful(date, &errors);
+
+	return errors;
 }
 
 int parse_date(const char *date, char *result, int maxlen)
diff --git a/git-date.cabal b/git-date.cabal
--- a/git-date.cabal
+++ b/git-date.cabal
@@ -1,5 +1,5 @@
 name:            git-date
-version:         0.2
+version:         0.2.1
 cabal-version:   >= 1.8
 license:         GPL-2
 license-file:    COPYING
@@ -34,6 +34,26 @@
                 bytestring,
                 utf8-string,
                 time
+
+test-suite tests
+        type:       exitcode-stdio-1.0
+        main-is:    tests/suite.hs
+
+        c-sources: ext/date.c
+        include-dirs: ext
+
+        other-modules:
+                Data.Time.Git
+
+        build-depends:
+                base == 4.*,
+                bytestring,
+                utf8-string,
+                time,
+                old-locale,
+                QuickCheck >= 2.4.1.1,
+                test-framework,
+                test-framework-quickcheck2
 
 source-repository head
         type:     git
diff --git a/tests/suite.hs b/tests/suite.hs
new file mode 100644
--- /dev/null
+++ b/tests/suite.hs
@@ -0,0 +1,53 @@
+import Test.Framework (defaultMain, Test)
+import Test.Framework.Providers.QuickCheck2
+import Test.QuickCheck
+
+import Data.Int
+import Data.Time.Git
+import System.Locale
+import Data.Time
+import Data.Time.Clock.POSIX
+import System.IO.Unsafe
+
+-- utcToLocalZonedTime is safeish because timezone won't change during testing
+toLocal :: UTCTime -> ZonedTime
+toLocal = unsafePerformIO . utcToLocalZonedTime
+
+newtype PositiveTime = PositiveTime ZonedTime deriving (Show)
+
+instance Arbitrary PositiveTime where
+	arbitrary =
+		fmap (PositiveTime . toLocal . posixSecondsToUTCTime . fromIntegral) $
+		choose (0, maxBound::Int32) -- Should be able to use CULong, but that failed
+
+newtype DateFormat = DateFormat String deriving (Show, Eq)
+
+instance Arbitrary DateFormat where
+	arbitrary = elements $ map DateFormat $ [
+			"%c",
+			"%Y-%m-%d %H:%M:%S%Q%z",
+			"%B %d, %Y %l:%M:%S %P",
+			"%B %d, %Y %l:%M:%S %p %Z",
+			"%l:%M:%S %p %Y %B %d",
+			rfc822DateFormat,
+			iso8601DateFormat (Just "%H:%M:%S%Q"),
+			iso8601DateFormat (Just "%H:%M:%S"),
+			iso8601DateFormat (Just "%H:%M:%S%Q%z"),
+			iso8601DateFormat (Just "%H:%M:%S%Q%Z")
+		]
+
+prop_format :: DateFormat -> PositiveTime -> Bool
+prop_format (DateFormat fmt) (PositiveTime time) =
+	approxidate formatted == Just posix
+	where
+	posix = floor $ utcTimeToPOSIXSeconds $ zonedTimeToUTC time
+	formatted = formatTime defaultTimeLocale fmt time
+
+tests :: [Test]
+tests =
+	[
+		testProperty "Arbitrary date formats work" prop_format
+	]
+
+main :: IO ()
+main = defaultMain tests
