[notmuch] [PATCH 1/4] Make the date parser nicer.

Sebastian Spaeth Sebastian at SSpaeth.de
Tue Jan 26 03:43:38 PST 2010


Currently we have to enter mail dates as timestamps. This approach does 2 things:
1) it requires the prefix 'date:'
2) it allows dates to be specified in a flexible way. So a notmuch show date:2005..2006-05-12 will find all mails from 2005-01-01 until 2006-05-12.
3) allow 'now' as keyword for NOW. This let's us search for messages 2001..now (note that this won't find timestamps in the future). Also adapt the documentation to state the new date search syntax.

Possible time formats: YYYY-MM-DD, YYYY-MM (from/through that month) , YYYY (from/through that year), MM-DD (month-day in current year), DD (day in current month).

Signed-off-by: Sebastian Spaeth <Sebastian at SSpaeth.de>
---
 TODO            |    9 ------
 lib/database.cc |   87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 notmuch.1       |   30 ++++++++++++------
 notmuch.c       |   24 +++++++-------
 4 files changed, 118 insertions(+), 32 deletions(-)

diff --git a/TODO b/TODO
index bdfe64c..86cbf74 100644
--- a/TODO
+++ b/TODO
@@ -114,15 +114,6 @@ notmuch library
 ---------------
 Index content from citations, please.
 
-Provide a sane syntax for date ranges. First, we don't want to require
-both endpoints to be specified. For example it would be nice to be
-able to say things like "since:2009-01-1" or "until:2009-01-1" and
-have the other endpoint be implicit. Second we'd like to support
-relative specifications of time such as "since:'2 months ago'". To do
-any of this we're probably going to need to break down an write our
-own parser for the query string rather than using Xapian's QueryParser
-class.
-
 Make failure to read a file (such as a permissions problem) a warning
 rather than an error (should be similar to the existing warning for a
 non-mail file).
diff --git a/lib/database.cc b/lib/database.cc
index cce7847..b386e1a 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -494,6 +494,91 @@ _notmuch_database_ensure_writable (notmuch_database_t *notmuch)
     return NOTMUCH_STATUS_SUCCESS;
 }
 
+struct MaildateValueRangeProcessor : public Xapian::ValueRangeProcessor {
+    MaildateValueRangeProcessor() {}
+
+  time_t
+  parsedate(std::string &str, bool early) {
+    /* Parse the date to a 'time_t', return NULL on error            */
+    /* possible time formats: YYYY-MM-DD, YYYY-MM, YYYY,             */
+    /* MM-DD (current month), DD (day in current month).             */
+    /* Uses start of time unit when 'early', end otherwise, e.g.     */
+    /* 2001:=2001-01-01:00:00:00 when 'early' or 2001-12-31:23:59:59 */
+    bool now = false;
+    struct tm *timeinfo;
+    time_t timet;
+    int year = 0, month = 0, day = 0;
+
+    now = (str == "now");
+
+    if (str.size() == 2) {
+      /* We got just current day in month, parse & remove it */
+      day = atoi(str.c_str());
+      str.erase(0,2);
+    }
+    
+    if (str.size() == 4 or str.size() > 5) {
+      /* expect a year, parse & remove it */
+      year = atoi(str.c_str());
+      str.erase(0,5);
+    }
+
+    /* parse & remove month if there is sth left in the string */
+    month = atoi(str.c_str());
+    str.erase(0,3);
+
+    /* Parse day if we have one left */
+    if (str.size())
+      day = atoi(str.c_str());	
+
+    if (!now && year == 0 && month == 0 && day == 0)
+      // no expected time format
+      return -1 ;
+
+    timet = time(NULL);                /* init timeinfo with current time */
+    timeinfo = gmtime(&timet);
+
+    if (!now) {
+      /* add timeunit if !early (1 second too much, which we deduct later   */
+      if (!early) {
+	if (year && !month)        ++year;  /* only year given              */
+	if (year && month && !day) ++month; /* year & month given           */
+      }
+      if (year)  timeinfo -> tm_year = year - 1900;
+      if (month) timeinfo -> tm_mon = month - 1;
+      if (day)   timeinfo -> tm_mday = (early ? day : ++day);
+      else       timeinfo -> tm_mday = 1;
+
+      timeinfo -> tm_hour = 0;
+      timeinfo -> tm_min  = 0;
+      timeinfo -> tm_sec  = (early ? 0 : -1); /* -1 sec if !early */
+    }
+    timet = mktime(timeinfo);
+
+    return timet;
+  }
+
+    Xapian::valueno operator()(std::string &begin, std::string &end) {
+      time_t begintime, endtime;
+
+      if (begin.substr(0, 5) != "date:")
+	 return Xapian::BAD_VALUENO;
+      begin.erase(0, 5);
+
+      begintime = parsedate(begin, true);
+      endtime   = parsedate(end, false);
+
+      if ((begintime == -1) || (endtime == -1))
+	// parsedate failed, no valid time format
+	return Xapian::BAD_VALUENO;
+
+      begin.assign(Xapian::sortable_serialise(begintime));
+      end.assign(Xapian::sortable_serialise(endtime));
+
+      return NOTMUCH_VALUE_TIMESTAMP;
+    }
+};
+
 notmuch_database_t *
 notmuch_database_open (const char *path,
 		       notmuch_database_mode_t mode)
@@ -570,7 +655,7 @@ notmuch_database_open (const char *path,
 	notmuch->query_parser = new Xapian::QueryParser;
 	notmuch->term_gen = new Xapian::TermGenerator;
 	notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
-	notmuch->value_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
+	notmuch->value_range_processor = new MaildateValueRangeProcessor();
 
 	notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
 	notmuch->query_parser->set_database (*notmuch->xapian_db);
diff --git a/notmuch.1 b/notmuch.1
index 282ad98..38379b1 100644
--- a/notmuch.1
+++ b/notmuch.1
@@ -413,17 +413,27 @@ expression).
 Finally, results can be restricted to only messages within a
 particular time range, (based on the Date: header) with a syntax of:
 
-	<intial-timestamp>..<final-timestamp>
+	date:<startdate>..<enddate>
+
+A 
+.B date 
+can be specified in the following formats:
+.BR 
+.B YYYY
+(e.g 2001 meaning since 2001, or through 2001, depending on whether it is the start or end). 2) 
+.B YYYY-MM
+meaning from/to month MM in year YYYY, 3) 
+.B YYYY-MM-DD
+(from/to that exact day) 4) 
+.B MM-DD 
+(from/to month/day in the current year) 5) 
+.B DD
+(from/to day DD in current month). 6) the keyword
+.B now
+can be used to denote NOW (so a "05-01..now" finds all mails from May until now).
+.BR
+Formats can be mixed, so "date:2001..22" means from 2001-01-01 until the 22nd this months.
 
-Each timestamp is a number representing the number of seconds since
-1970-01-01 00:00:00 UTC. This is not the most convenient means of
-expressing date ranges, but until notmuch is fixed to accept a more
-convenient form, one can use the date program to construct
-timestamps. For example, with the bash shell the folowing syntax would
-specify a date range to return messages from 2009-10-01 until the
-current time:
-
-	$(date +%s -d 2009-10-01)..$(date +%s)
 .SH SEE ALSO
 The emacs-based interface to notmuch (available as
 .B notmuch.el
diff --git a/notmuch.c b/notmuch.c
index 87479f8..808a370 100644
--- a/notmuch.c
+++ b/notmuch.c
@@ -92,19 +92,19 @@ static const char search_terms_help[] =
     "\t\tmarks around any parenthesized expression).\n"
     "\n"
     "\t\tFinally, results can be restricted to only messages within a\n"
-    "\t\tparticular time range, (based on the Date: header) with:\n"
+    "\t\tparticular time range, (based on the Date: header) with a\n"
+    "\t\tsyntax of: date:<startdate>..<enddate>\n"
+    "\t\tIt can be specified in the following formats:\n"
+    "\t\tYYYY (e.g 2001 meaning since 2001, or through 2001, depending\n"
+    "\t\ton whether it is the start or end). 2) YYYY-MM meaning from/to\n"
+    "\t\tmonth MM in year YYYY, 3) YYYY-MM-DD (that exact day) 4)\n"
+    "\t\tMM-DD (month/day in the current year) 5) DD (day DD in \n"
+    "\t\tcurrent month). 6) the keyword 'now' can be used to denote\n"
+    "\t\tNOW (so a 'date:05-01..now' finds all mails from May until now).\n"
     "\n"
-    "\t\t\t<intial-timestamp>..<final-timestamp>\n"
-    "\n"
-    "\t\tEach timestamp is a number representing the number of seconds\n"
-    "\t\tsince 1970-01-01 00:00:00 UTC. This is not the most convenient\n"
-    "\t\tmeans of expressing date ranges, but until notmuch is fixed to\n"
-    "\t\taccept a more convenient form, one can use the date program to\n"
-    "\t\tconstruct timestamps. For example, with the bash shell the\n"
-    "\t\tfollowing syntax would specify a date range to return messages\n"
-    "\t\tfrom 2009-10-01 until the current time:\n"
-    "\n"
-    "\t\t\t$(date +%%s -d 2009-10-01)..$(date +%%s)\n\n";
+    "\t\tFormats can be mixed, so 'date:2001..22' means from 2001-01-01\n"
+    "\t\tuntil the 22nd this months.\n"
+    "\n\n";
 
 command_t commands[] = {
     { "setup", notmuch_setup_command,
-- 
1.6.3.3



More information about the notmuch mailing list