subversion-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Stefan Sperling <s...@elego.de>
Subject [PATCH] -r { DATE } with words
Date Mon, 16 May 2011 22:45:50 GMT
Back before 1.0 we had a date parser written in Yacc that could
parse all sorts of fancy strings such as "yesterday", "last month",
or "two fortnights ago". This was dropped in r848401/r848402 because
of maintenance concerns. The parser was missing some desired features
and also did a lot more than needed. E.g. it could even parse future
dates such as "tomorrow" or "next week" which makes no sense at all
since future commits don't exist yet :)
See http://svn.haxx.se/dev/archive-2003-12/0737.shtml (linked from
issue #408) for details.

It has been pointed out to me that Subversion does not support such date
specifications at all. I think they're rather useful because they allow
users to easily get at commits that happened e.g. during the last N days
or weeks relative to the local time of their machine without having to
calculate appropriate date strings. The patch below adds support for simple
word strings for specifying dates (see log message for details).

There are a couple of restrictions:

  To keep things simple years always have 365 days and months
  always have 30 days. If more precision is desired normal dates
  can be used. Stephen Butler convinced me to take this route.

  I do not intend to grow the set of strings any further unless new
  strings open up very exciting possibilities.

  Localising the strings to other languages would also complicate
  things too much since the order of words might change (also pointed
  out by Steve). All command names and long options are always in
  English anyway.

  If the system clock on the client side is wrong revisions might not
  be resolved correctly.

Combined with the new --diff option of svn log this makes it very
easy to review commits which happened during e.g. the last 3 days:

  svn log --diff -r {"3 days ago"}:{"now"} ^/trunk | less

I am posting this prior to commit because I have received mixed reactions
to this idea. Like myself, Greg was surprised that we don't already support
this. C-Mike said that this might open the door for potential maintenance
problems to reappear in particular if we keep adding more strings.
I don't think we will have problems if we don't go too far with adding
complexity to this feature. But let's continue discussion based on this patch.

Any comments or objections?

[[[
Add support for the following revision { DATE } specifications:

  "now" resolves to the most recent revision as of the current time.

  "yesterday" resolves to the most recent revision prior to 00:00h of today.

  "N years|months|weeks|days|hours|minutes ago" resolve to the most recent
  revision prior to the specified time. For years, months, weeks, and days,
  round up to 00:00h of the day following the specified time.
  A year always has 365 days. A month always has 30 days.
  N may be a word from "zero" up to "twelve", or a non-negative digit.
  To help scripts, N=0 is allowed and produces the same result as "now",
  and if N=1 the final 's' of the unit name is allowed, but not required.

* subversion/libsvn_subr/date.c
  (unit_words_table, number_words_table, words_match): New.
  (svn_parse_date): Parse new date specifications.
]]]

Index: subversion/libsvn_subr/date.c
===================================================================
--- subversion/libsvn_subr/date.c	(revision 1103298)
+++ subversion/libsvn_subr/date.c	(working copy)
@@ -22,6 +22,7 @@
 
 #include "svn_time.h"
 #include "svn_error.h"
+#include "svn_string.h"
 
 #include "svn_private_config.h"
 
@@ -187,6 +188,146 @@ template_match(apr_time_exp_t *expt, svn_boolean_t
   return TRUE;
 }
 
+static struct unit_words_table {
+  const char *word;
+  apr_time_t value;
+} unit_words_table[] = {
+  /* Word matching does not concern itself with exact days of the month
+   * or leap years so these amounts are always fixed. */
+  { "years",    apr_time_from_sec(60 * 60 * 24 * 365) },
+  { "months",   apr_time_from_sec(60 * 60 * 24 * 30) },
+  { "weeks",    apr_time_from_sec(60 * 60 * 24 * 7) },
+  { "days",     apr_time_from_sec(60 * 60 * 24) },
+  { "hours",    apr_time_from_sec(60 * 60) },
+  { "minutes",  apr_time_from_sec(60) },
+  { "mins",     apr_time_from_sec(60) },
+  { NULL ,      0 }
+};
+
+static struct number_words_table {
+  const char *word;
+  int number;
+} number_words_table[] = {
+  { "zero", 0 }, { "one", 1 }, { "two", 2 }, { "three", 3 }, { "four", 4 },
+  { "five", 5 }, { "six", 6 }, { "seven", 7 }, { "eight", 8 }, { "nine", 9 },
+  { "ten", 10 }, { "eleven", 11 }, { "twelve", 12 }, { NULL, 0 }
+};
+
+/* Attempt to match the date-string in TEXT according to the following rules:
+ *
+ * "now" resolves to the most recent revision as of the current time NOW.
+ * "yesterday" resolves to the most recent revision prior to 00:00h of today.
+ * "N years|months|weeks|days|hours|minutes ago" resolve to the most recent
+ * revision prior to the specified time. For years, months, weeks, and days,
+ * round up to 00:00h of the day following the specified time.
+ * N may either be a word from NUMBER_WORDS_TABLE defined above, or a
+ * non-negative digit.
+ *
+ * Return TRUE on successful match, FALSE otherwise. On successful match,
+ * fill in *EXP with the matched value and set *LOCALTZ to TRUE (this
+ * function always uses local time). Use POOL for temporary allocations. */
+static svn_boolean_t
+words_match(apr_time_exp_t *expt, svn_boolean_t *localtz,
+            apr_time_t now, const char *text, apr_pool_t *pool)
+{
+  apr_time_t t = -1;
+  const char *word;
+  svn_boolean_t round_to_next_day = TRUE;
+  apr_array_header_t *words;
+
+  words = svn_cstring_split(text, " ", TRUE /* chop_whitespace */, pool);
+  
+  if (words->nelts == 0)
+    return FALSE;
+
+  word = APR_ARRAY_IDX(words, 0, const char *);
+
+  if (words->nelts == 1)
+    {
+      if (!strcmp(word, "now"))
+        {
+          t = now;
+          round_to_next_day = FALSE;
+        }
+      else if (!strcmp(word, "yesterday"))
+        t = now - apr_time_from_sec(60 * 60 * 24);
+    }
+  else if (words->nelts == 3)
+    {
+      int i;
+      int n = -1;
+      const char *number_str;
+      const char *unit_str;
+
+      /* Try to parse a number word. */
+      for (i = 0, number_str = number_words_table[i].word;
+           number_str = number_words_table[i].word, number_str != NULL; i++)
+        {
+          if (!strcmp(word, number_str))
+            {
+              n = number_words_table[i].number;
+              break;
+            }
+        }
+
+        if (n < 0)
+          {
+            svn_error_t *err; 
+
+            /* Try to parse a digit. */
+            err = svn_cstring_atoi(&n, word);
+            if (err)
+              {
+                svn_error_clear(err);
+                return FALSE;
+              }
+            if (n < 0)
+              return FALSE;
+          }
+
+      /* Try to parse a unit. */
+      word = APR_ARRAY_IDX(words, 1, const char *);
+      for (i = 0, unit_str = unit_words_table[i].word;
+           unit_str = unit_words_table[i].word, unit_str != NULL; i++)
+        {
+          /* Tolerate missing trailing 's' from unit for n=1. */
+          if (!strcmp(word, unit_str) ||
+              (n == 1 && !strncmp(word, unit_str, strlen(unit_str) - 1)))
+            {
+              t = now - (n * unit_words_table[i].value);
+              round_to_next_day = !(!strcmp(unit_str, "hours") ||
+                                    !strcmp(unit_str, "minutes") ||
+                                    !strcmp(unit_str, "mins"));
+              break;
+            }
+        }
+      if (t < 0)
+        return FALSE;
+
+      /* Require trailing "ago". */
+      word = APR_ARRAY_IDX(words, 2, const char *);
+      if (strcmp(word, "ago"))
+        return FALSE;
+    }
+  else
+    return FALSE;
+
+  if (t >= 0)
+    {
+      if (apr_time_exp_lt(expt, t) != APR_SUCCESS)
+        return FALSE;
+      if (round_to_next_day)
+        {
+          expt->tm_usec = expt->tm_sec = expt->tm_min = expt->tm_hour = 0;
+          expt->tm_mday++;
+        }
+      *localtz = TRUE;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
 static int
 valid_days_by_month[] = {
   31, 29, 31, 30,
@@ -244,7 +385,7 @@ svn_parse_date(svn_boolean_t *matched, apr_time_t
       expt.tm_mon = expnow.tm_mon;
       expt.tm_mday = expnow.tm_mday;
     }
-  else
+  else if (!words_match(&expt, &localtz, now, text, pool))
     return SVN_NO_ERROR;
 
   /* Range validation, allowing for leap seconds */

Mime
View raw message