On Tue, May 17, 2011 at 12:45:50AM +0200, Stefan Sperling wrote:
> Any comments or objections?

Neels didn't like the arbitrary "round to 00:00 of next day" rules
and everyone in the hackathon room seems to agree. So "one day ago"
is now the same as "24 hours ago".

I also dropped the "yesterday" keyword because it overlaps with "one day ago".

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

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

  "N years|months|weeks|days|hours|minutes ago" resolve to the most recent
  revision prior to 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 1104073)
+++ 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,126 @@ 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.
+ * "N years|months|weeks|days|hours|minutes ago" resolve to the most recent
+ * revision prior to 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;
+  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) && (!strcmp(word, "now")))
+    t = now;
+  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);
+              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;
+      *localtz = TRUE;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
 static int
 valid_days_by_month[] = {
   31, 29, 31, 30,
@@ -244,7 +365,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 */

Reply via email to