unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
* [PATCH 1/4] Make the date parser nicer.
@ 2010-01-26 11:43 Sebastian Spaeth
  2010-01-26 11:43 ` [PATCH 2/4] add date parser file from Keith Sebastian Spaeth
  0 siblings, 1 reply; 4+ messages in thread
From: Sebastian Spaeth @ 2010-01-26 11:43 UTC (permalink / raw)
  To: notmuch

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@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

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/4] add date parser file from Keith
  2010-01-26 11:43 [PATCH 1/4] Make the date parser nicer Sebastian Spaeth
@ 2010-01-26 11:43 ` Sebastian Spaeth
  2010-01-26 11:43   ` [PATCH 3/4] compile date.c as well Sebastian Spaeth
  0 siblings, 1 reply; 4+ messages in thread
From: Sebastian Spaeth @ 2010-01-26 11:43 UTC (permalink / raw)
  To: notmuch

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 10306 bytes --]

From: Keith Packard <keithp@keithp.com>

Here's some code which further improves date parsing by allowing lots of
date formats, including things like "today", "thisweek", ISO and US date
formats and month names. You can separate two dates with .. to make a
range, or you can just use the default range ("lastmonth" is everything
From the 1st of the previous month to the 1st of the current month).
---
 lib/date.c |  455 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 455 insertions(+), 0 deletions(-)
 create mode 100644 lib/date.c

diff --git a/lib/date.c b/lib/date.c
new file mode 100644
index 0000000..09c5ef9
--- /dev/null
+++ b/lib/date.c
@@ -0,0 +1,455 @@
+/*
+ * Copyright © 2009 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "notmuch.h"
+#include <time.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define DAY	(24 * 60 * 60)
+
+static void
+today(struct tm *result, time_t after) {
+    time_t	t;
+
+    if (after)
+	t = after;
+    else
+	time(&t);
+    localtime_r(&t, result);
+    result->tm_sec = result->tm_min = result->tm_hour = 0;
+}
+
+static int parse_today(const char *text, time_t *first, time_t *last, time_t after) {
+    if (strcasecmp(text, "today") == 0) {
+	struct tm n;
+	today(&n, 0);
+	*first = mktime(&n);
+	*last = *first + DAY;
+	return 0;
+    }
+    return 1;
+}
+
+static int parse_yesterday(const char *text, time_t *first, time_t *last, time_t after) {
+    if (strcasecmp(text, "yesterday") == 0) {
+	struct tm n;
+	today(&n, 0);
+	*last = mktime(&n);
+	*first = *last - DAY;
+	return 0;
+    }
+    return 1;
+}
+
+static int parse_thisweek(const char *text, time_t *first, time_t *last, time_t after) {
+    if (strcasecmp(text, "thisweek") == 0) {
+	struct tm n;
+	today(&n, 0);
+	*first = mktime(&n) - (n.tm_wday * DAY);
+	*last = *first + DAY * 7;
+	return 0;
+    }
+    return 1;
+}
+
+static int parse_lastweek(const char *text, time_t *first, time_t *last, time_t after) {
+    if (strcasecmp(text, "lastweek") == 0) {
+	struct tm n;
+	today(&n, 0);
+	*last = mktime(&n) - (n.tm_wday * DAY);
+	*first = *last - DAY * 7;
+	return 0;
+    }
+    return 1;
+}
+
+static int parse_thismonth(const char *text, time_t *first, time_t *last, time_t after) {
+    if (strcasecmp(text, "thismonth") == 0) {
+	struct tm n;
+	today(&n, 0);
+	n.tm_mday = 1;
+	*first = mktime(&n);
+	if (n.tm_mon++ == 12) {
+	    n.tm_mon = 0;
+	    n.tm_year++;
+	}
+	*last = mktime(&n);
+	return 0;
+    }
+    return 1;
+}
+
+static int parse_lastmonth(const char *text, time_t *first, time_t *last, time_t after) {
+    if (strcasecmp(text, "lastmonth") == 0) {
+	struct tm n;
+	today(&n, 0);
+	n.tm_mday = 1;
+	if (n.tm_mon == 0) {
+	    n.tm_year--;
+	    n.tm_mon = 11;
+	} else
+	    n.tm_mon--;
+	*first = mktime(&n);
+	if (n.tm_mon++ == 12) {
+	    n.tm_mon = 0;
+	    n.tm_year++;
+	}
+	*last = mktime(&n);
+	return 0;
+    }
+    return 1;
+}
+
+static const char *months[12][2] = {
+    { "January", "Jan" },
+    { "February", "Feb" },
+    { "March", "Mar" },
+    { "April", "Apr" },
+    { "May", "May" },
+    { "June", "Jun" },
+    { "July", "Jul" },
+    { "August", "Aug" },
+    { "September", "Sep" },
+    { "October", "Oct" },
+    { "November", "Nov" },
+    { "December", "Dec" },
+};
+
+static int year(const char *text, int *y) {
+    char *end;
+    *y = strtol(text, &end, 10);
+    if (end == text)
+	return 1;
+    if (*end != '\0')
+	return 1;
+    if (*y < 1970 || *y > 2038)
+	return 1;
+    *y -= 1900;
+    return 0;
+}
+
+static int month(const char *text, int *m) {
+    char *end;
+    int i;
+    for (i = 0; i < 12; i++) {
+	if (strcasecmp(text, months[i][0]) == 0 ||
+	    strcasecmp(text, months[i][1]) == 0)
+	{
+	    *m = i;
+	    return 0;
+	}
+    }
+    *m = strtol(text, &end, 10);
+    if (end == text)
+	return 1;
+    if (*end != '\0')
+	return 1;
+    if (*m < 1 || *m > 12)
+	return 1;
+    *m -= 1;
+    return 0;
+}
+
+static int day(const char *text, int *d) {
+    char *end;
+    *d = strtol(text, &end, 10);
+    if (end == text)
+	return 1;
+    if (*end != '\0')
+	return 1;
+    if (*d < 1 || *d > 31)
+	return 1;
+    return 0;
+}
+
+/* month[-day] */
+static int parse_month(const char *text, time_t *first, time_t *last, time_t after) {
+    int		m = 0, d = 0;
+    int		i;
+    struct tm	n;
+    char	tmp[80];
+    char	*t;
+    char	*save;
+    char	*token;
+
+    if(strlen (text) >= sizeof (tmp))
+	return 1;
+    strcpy(tmp, text);
+
+    t = tmp;
+    save = NULL;
+    i = 0;
+    while ((token = strtok_r(t, "-", &save)) != NULL) {
+	i++;
+	switch(i) {
+	case 1:
+	    if (month(token, &m) != 0)
+		return 1;
+	    break;
+	case 2:
+	    if (day(token, &d) != 0)
+		return 1;
+	    break;
+	default:
+	    return 1;
+	}
+	t = NULL;
+    }
+    today(&n, after);
+    if (after) {
+	if (m < n.tm_mon)
+	    n.tm_year++;
+    } else {
+	if (m > n.tm_mon)
+	    n.tm_year--;
+    }
+    switch (i) {
+    case 1:
+	n.tm_mday = 1;
+	n.tm_mon = m;
+	*first = mktime(&n);
+	if (++n.tm_mon > 11) {
+	    n.tm_mon = 0;
+	    n.tm_year++;
+	}
+	*last = mktime(&n);
+	return 0;
+    case 2:
+	n.tm_mday = d;
+	n.tm_mon = m;
+	*first = mktime(&n);
+	*last = *first + DAY;
+	return 0;
+    }
+    return 1;
+}
+
+/* year[-month[-day]] */
+static int parse_iso(const char *text, time_t *first, time_t *last, time_t after) {
+    int		y = 0, m = 0, d = 0;
+    int		i;
+    struct tm	n;
+    char	tmp[80];
+    char	*t;
+    char	*save;
+    char	*token;
+
+    if(strlen (text) >= sizeof (tmp))
+	return 1;
+    strcpy(tmp, text);
+
+    t = tmp;
+    save = NULL;
+    i = 0;
+    while ((token = strtok_r(t, "-", &save)) != NULL) {
+	i++;
+	switch(i) {
+	case 1:
+	    if (year(token, &y) != 0)
+		return 1;
+	    break;
+	case 2:
+	    if (month(token, &m) != 0)
+		return 1;
+	    break;
+	case 3:
+	    if (day(token, &d) != 0)
+		return 1;
+	    break;
+	default:
+	    return 1;
+	}
+	t = NULL;
+    }
+    today(&n, 0);
+    switch (i) {
+    case 1:
+	n.tm_mday = 1;
+	n.tm_mon = 0;
+	n.tm_year = y;
+	*first = mktime(&n);
+	n.tm_year = y + 1;
+	*last = mktime(&n);
+	return 0;
+    case 2:
+	n.tm_mday = 1;
+	n.tm_mon = m;
+	n.tm_year = y;
+	*first = mktime(&n);
+	if (++n.tm_mon > 11) {
+	    n.tm_mon = 0;
+	    n.tm_year++;
+	}
+	*last = mktime(&n);
+	return 0;
+    case 3:
+	n.tm_mday = d;
+	n.tm_mon = m;
+	n.tm_year = y;
+	*first = mktime(&n);
+	*last = *first + DAY;
+	return 0;
+    }
+    return 1;
+}
+
+/* month[/day[/year]] */
+static int parse_us(const char *text, time_t *first, time_t *last, time_t after) {
+    int		y = 0, m = 0, d = 0;
+    int		i;
+    struct tm	n;
+    char	tmp[80];
+    char	*t;
+    char	*save;
+    char	*token;
+
+    if(strlen (text) >= sizeof (tmp))
+	return 1;
+    strcpy(tmp, text);
+
+    t = tmp;
+    save = NULL;
+    i = 0;
+    while ((token = strtok_r(t, "/", &save)) != NULL) {
+	i++;
+	switch(i) {
+	case 1:
+	    if (month(token, &m) != 0)
+		return 1;
+	    break;
+	case 2:
+	    if (day(token, &d) != 0)
+		return 1;
+	    break;
+	case 3:
+	    if (year(token, &y) != 0)
+		return 1;
+	    break;
+	default:
+	    return 1;
+	}
+	t = NULL;
+    }
+    today(&n, after);
+    if (after) {
+	if (m < n.tm_mon)
+	    n.tm_year++;
+    } else {
+	if (m > n.tm_mon)
+	    n.tm_year--;
+    }
+    switch (i) {
+    case 1:
+	n.tm_mday = 1;
+	n.tm_mon = m;
+	*first = mktime(&n);
+	if (++n.tm_mon > 11) {
+	    n.tm_mon = 0;
+	    n.tm_year++;
+	}
+	*last = mktime(&n);
+	return 0;
+    case 2:
+	n.tm_mday = d;
+	n.tm_mon = m;
+	*first = mktime(&n);
+	*last = *first + DAY;
+	return 0;
+    case 3:
+	n.tm_mday = d;
+	n.tm_mon = m;
+	n.tm_year = y;
+	*first = mktime(&n);
+	*last = *first + DAY;
+	return 0;
+    }
+    return 1;
+}
+
+static int (*parsers[])(const char *text, time_t *first, time_t *last, time_t after) = {
+    parse_today,
+    parse_yesterday,
+    parse_thisweek,
+    parse_lastweek,
+    parse_thismonth,
+    parse_lastmonth,
+    parse_month,
+    parse_iso,
+    parse_us,
+    0,
+};
+
+static notmuch_status_t
+notmuch_one_date(const char *text, time_t *first, time_t *last, time_t after)
+{
+    int		i;
+    for (i = 0; parsers[i]; i++)
+	if (parsers[i](text, first, last, after) == 0)
+	    return NOTMUCH_STATUS_SUCCESS;
+    return NOTMUCH_STATUS_INVALID_DATE;
+}
+
+notmuch_status_t
+notmuch_date(const char *text, time_t *first, time_t *last)
+{
+    char	*dots;
+    char	first_text[80], last_text[80];
+    notmuch_status_t	status;
+    time_t	first_first, first_last, last_first, last_last;
+
+    if (strlen(text) > sizeof (first_text))
+	return NOTMUCH_STATUS_INVALID_DATE;
+    dots = strstr(text, "..");
+    if (dots) {
+	strncpy(first_text, text, dots - text);
+	first_text[dots-text] = '\0';
+	status = notmuch_one_date(first_text, &first_first, &first_last, 0);
+	if (status)
+	    return status;
+	status = notmuch_one_date(dots + 2, &last_first, &last_last, first_first);
+	if (status)
+	    return status;
+	*first = first_first;
+	*last = last_last;
+	return 0;
+    }
+    return notmuch_one_date(text, first, last, 0);
+}
+
+#if 1
+int
+main (int argc, char **argv)
+{
+    int	i;
+    for (i = 1; i < argc; i++) {
+	time_t	first, last;
+
+	if (notmuch_date(argv[i], &first, &last) == 0) {
+	    char	first_string[80], last_string[80];
+
+	    ctime_r(&first, first_string);
+	    first_string[strlen(first_string)-1] = '\0';
+	    ctime_r(&last, last_string);
+	    last_string[strlen(last_string)-1] = '\0';
+	    printf ("%s: %s - %s\n", argv[i], first_string, last_string);
+	}
+    }
+}
+#endif
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 3/4] compile date.c as well
  2010-01-26 11:43 ` [PATCH 2/4] add date parser file from Keith Sebastian Spaeth
@ 2010-01-26 11:43   ` Sebastian Spaeth
  2010-01-26 11:43     ` [PATCH 4/4] integrate keithp's date.c into the notmuch date parser and delete my previous own attempt Sebastian Spaeth
  0 siblings, 1 reply; 4+ messages in thread
From: Sebastian Spaeth @ 2010-01-26 11:43 UTC (permalink / raw)
  To: notmuch

Signed-off-by: Sebastian Spaeth <Sebastian@SSpaeth.de>
---
 lib/Makefile.local |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/lib/Makefile.local b/lib/Makefile.local
index 70489e1..44deaf8 100644
--- a/lib/Makefile.local
+++ b/lib/Makefile.local
@@ -3,6 +3,7 @@ extra_cflags += -I$(dir)
 
 libnotmuch_c_srcs =		\
 	$(dir)/libsha1.c	\
+	$(dir)/date.c	\
 	$(dir)/message-file.c	\
 	$(dir)/messages.c	\
 	$(dir)/sha1.c		\
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 4/4] integrate keithp's date.c into the notmuch date parser and delete my previous own attempt
  2010-01-26 11:43   ` [PATCH 3/4] compile date.c as well Sebastian Spaeth
@ 2010-01-26 11:43     ` Sebastian Spaeth
  0 siblings, 0 replies; 4+ messages in thread
From: Sebastian Spaeth @ 2010-01-26 11:43 UTC (permalink / raw)
  To: notmuch

Signed-off-by: Sebastian Spaeth <Sebastian@SSpaeth.de>
---
 lib/database.cc |   84 ++++++++++--------------------------------------------
 lib/date.c      |   59 ++++++--------------------------------
 lib/notmuch.h   |   20 +++++++++++++
 notmuch-new.c   |    1 +
 notmuch.1       |   33 +++++++++++----------
 notmuch.c       |   20 +++++++------
 6 files changed, 74 insertions(+), 143 deletions(-)

diff --git a/lib/database.cc b/lib/database.cc
index b386e1a..78cd898 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -219,6 +219,8 @@ notmuch_status_to_string (notmuch_status_t status)
 	return "Erroneous NULL pointer";
     case NOTMUCH_STATUS_TAG_TOO_LONG:
 	return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)";
+    case NOTMUCH_STATUS_INVALID_DATE:
+	return "Date value did not parse to a valid date";
     case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
 	return "Unbalanced number of calls to notmuch_message_freeze/thaw";
     default:
@@ -497,83 +499,29 @@ _notmuch_database_ensure_writable (notmuch_database_t *notmuch)
 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;
+      time_t begin_first,begin_last, end_first, end_last;
+      int retval;
 
       if (begin.substr(0, 5) != "date:")
 	 return Xapian::BAD_VALUENO;
       begin.erase(0, 5);
 
-      begintime = parsedate(begin, true);
-      endtime   = parsedate(end, false);
+      retval = notmuch_parse_date(begin.c_str(), &begin_first, &begin_last, 0);
 
-      if ((begintime == -1) || (endtime == -1))
-	// parsedate failed, no valid time format
+      if (retval == NOTMUCH_STATUS_INVALID_DATE) {
+	fprintf(stderr,"Begin date failed to parse: %s",begin.c_str());
 	return Xapian::BAD_VALUENO;
+      }
+
+      retval = notmuch_parse_date(end.c_str(),&end_first,&end_last,begin_first);
+      if (retval == NOTMUCH_STATUS_INVALID_DATE) {
+	fprintf(stderr,"End date failed to parse: %s",end.c_str());
+	return Xapian::BAD_VALUENO;
+      }
 
-      begin.assign(Xapian::sortable_serialise(begintime));
-      end.assign(Xapian::sortable_serialise(endtime));
+      begin.assign(Xapian::sortable_serialise(begin_first));
+      end.assign(Xapian::sortable_serialise(end_last));
 
       return NOTMUCH_VALUE_TIMESTAMP;
     }
diff --git a/lib/date.c b/lib/date.c
index 09c5ef9..805a1d9 100644
--- a/lib/date.c
+++ b/lib/date.c
@@ -37,6 +37,7 @@ today(struct tm *result, time_t after) {
 }
 
 static int parse_today(const char *text, time_t *first, time_t *last, time_t after) {
+    (void)after; /*disable unused paramter warning*/
     if (strcasecmp(text, "today") == 0) {
 	struct tm n;
 	today(&n, 0);
@@ -56,6 +57,7 @@ static int parse_yesterday(const char *text, time_t *first, time_t *last, time_t
 	return 0;
     }
     return 1;
+    (void)after; /*disable unused paramter warning*/
 }
 
 static int parse_thisweek(const char *text, time_t *first, time_t *last, time_t after) {
@@ -67,6 +69,7 @@ static int parse_thisweek(const char *text, time_t *first, time_t *last, time_t
 	return 0;
     }
     return 1;
+    (void)after; /*disable unused paramter warning*/
 }
 
 static int parse_lastweek(const char *text, time_t *first, time_t *last, time_t after) {
@@ -78,6 +81,7 @@ static int parse_lastweek(const char *text, time_t *first, time_t *last, time_t
 	return 0;
     }
     return 1;
+    (void)after; /*disable unused paramter warning*/
 }
 
 static int parse_thismonth(const char *text, time_t *first, time_t *last, time_t after) {
@@ -94,6 +98,7 @@ static int parse_thismonth(const char *text, time_t *first, time_t *last, time_t
 	return 0;
     }
     return 1;
+    (void)after; /*disable unused paramter warning*/
 }
 
 static int parse_lastmonth(const char *text, time_t *first, time_t *last, time_t after) {
@@ -115,6 +120,7 @@ static int parse_lastmonth(const char *text, time_t *first, time_t *last, time_t
 	return 0;
     }
     return 1;
+    (void)after; /*disable unused paramter warning*/
 }
 
 static const char *months[12][2] = {
@@ -308,6 +314,7 @@ static int parse_iso(const char *text, time_t *first, time_t *last, time_t after
 	return 0;
     }
     return 1;
+    (void)after; /*disable unused paramter warning*/
 }
 
 /* month[/day[/year]] */
@@ -396,8 +403,8 @@ static int (*parsers[])(const char *text, time_t *first, time_t *last, time_t af
     0,
 };
 
-static notmuch_status_t
-notmuch_one_date(const char *text, time_t *first, time_t *last, time_t after)
+notmuch_status_t
+notmuch_parse_date(const char *text, time_t *first, time_t *last, time_t after)
 {
     int		i;
     for (i = 0; parsers[i]; i++)
@@ -405,51 +412,3 @@ notmuch_one_date(const char *text, time_t *first, time_t *last, time_t after)
 	    return NOTMUCH_STATUS_SUCCESS;
     return NOTMUCH_STATUS_INVALID_DATE;
 }
-
-notmuch_status_t
-notmuch_date(const char *text, time_t *first, time_t *last)
-{
-    char	*dots;
-    char	first_text[80], last_text[80];
-    notmuch_status_t	status;
-    time_t	first_first, first_last, last_first, last_last;
-
-    if (strlen(text) > sizeof (first_text))
-	return NOTMUCH_STATUS_INVALID_DATE;
-    dots = strstr(text, "..");
-    if (dots) {
-	strncpy(first_text, text, dots - text);
-	first_text[dots-text] = '\0';
-	status = notmuch_one_date(first_text, &first_first, &first_last, 0);
-	if (status)
-	    return status;
-	status = notmuch_one_date(dots + 2, &last_first, &last_last, first_first);
-	if (status)
-	    return status;
-	*first = first_first;
-	*last = last_last;
-	return 0;
-    }
-    return notmuch_one_date(text, first, last, 0);
-}
-
-#if 1
-int
-main (int argc, char **argv)
-{
-    int	i;
-    for (i = 1; i < argc; i++) {
-	time_t	first, last;
-
-	if (notmuch_date(argv[i], &first, &last) == 0) {
-	    char	first_string[80], last_string[80];
-
-	    ctime_r(&first, first_string);
-	    first_string[strlen(first_string)-1] = '\0';
-	    ctime_r(&last, last_string);
-	    last_string[strlen(last_string)-1] = '\0';
-	    printf ("%s: %s - %s\n", argv[i], first_string, last_string);
-	}
-    }
-}
-#endif
diff --git a/lib/notmuch.h b/lib/notmuch.h
index 15c9db4..be474bf 100644
--- a/lib/notmuch.h
+++ b/lib/notmuch.h
@@ -78,6 +78,8 @@ typedef int notmuch_bool_t;
  * NOTMUCH_STATUS_TAG_TOO_LONG: A tag value is too long (exceeds
  *	NOTMUCH_TAG_MAX)
  *
+ * NOTMUCH_STATUS_INVALID_DATE: Date parsing failed
+ *
  * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: The notmuch_message_thaw
  *	function has been called more times than notmuch_message_freeze.
  *
@@ -96,6 +98,7 @@ typedef enum _notmuch_status {
     NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID,
     NOTMUCH_STATUS_NULL_POINTER,
     NOTMUCH_STATUS_TAG_TOO_LONG,
+    NOTMUCH_STATUS_INVALID_DATE,
     NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW,
 
     NOTMUCH_STATUS_LAST_STATUS
@@ -1086,6 +1089,23 @@ notmuch_filenames_advance (notmuch_filenames_t *filenames);
 void
 notmuch_filenames_destroy (notmuch_filenames_t *filenames);
 
+notmuch_status_t
+notmuch_parse_date(const char *text, time_t *first, time_t *last, time_t after);
+/* Parse a string into the first and last possible timestamps.
+ * It parses the possible formats and stops if one pattern matches.
+ * Keywords: 'today','yesterday','thisweek','lastweek','thismonth',
+ *           'lastmonth'
+ * Month-day : month[-day]] (month: January, Jan, or 1)\n"
+ * ISO format: year[-month[-day]]
+ * US format : month[/day[/year]]
+ *
+ * 'after' is used to fill in bits from context if left out, e.g. a
+ * 'date:2004..01' will find from 2004-01-01 through 2004-01-31
+ *
+ * Return values:
+ * NOTMUCH_STATUS_SUCCESS
+ * NOTMUCH_STATUS_INVALID_DATE: Error parsing the date string
+ */
 NOTMUCH_END_DECLS
 
 #endif
diff --git a/notmuch-new.c b/notmuch-new.c
index f25c71f..5da31c1 100644
--- a/notmuch-new.c
+++ b/notmuch-new.c
@@ -431,6 +431,7 @@ add_files_recursive (notmuch_database_t *notmuch,
 	    ret = status;
 	    goto DONE;
 	default:
+	case NOTMUCH_STATUS_INVALID_DATE:
 	case NOTMUCH_STATUS_FILE_ERROR:
 	case NOTMUCH_STATUS_NULL_POINTER:
 	case NOTMUCH_STATUS_TAG_TOO_LONG:
diff --git a/notmuch.1 b/notmuch.1
index 38379b1..175fe86 100644
--- a/notmuch.1
+++ b/notmuch.1
@@ -417,22 +417,23 @@ particular time range, (based on the Date: header) with a syntax of:
 
 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.
+can be specified in various formats. It parses the formats in this order and stops if one pattern matches:
+
+	Keywords:
+.B today, 
+.B yesterday, 
+.B thisweek, 
+.B lastweek, 
+.B thismonth, 
+.B lastmonth.
+
+	Month-day: month[-day]] (month: "January", "Jan", 1)
+
+	ISO format: year[-month[-day]]
+
+	US format : month[/day[/year]]
+
+The date parser will try to fill in bits in the enddate from context if left out, e.g. a 'date:2004..01' will find from 2004-01-01 through 2004-01-31.
 
 .SH SEE ALSO
 The emacs-based interface to notmuch (available as
diff --git a/notmuch.c b/notmuch.c
index 808a370..b8fe01a 100644
--- a/notmuch.c
+++ b/notmuch.c
@@ -94,16 +94,18 @@ static const char search_terms_help[] =
     "\t\tFinally, results can be restricted to only messages within a\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\tFormats can be mixed, so 'date:2001..22' means from 2001-01-01\n"
-    "\t\tuntil the 22nd this months.\n"
+    "\t\tIt can be specified in the following formats, parsing will \n"
+    "\t\tstop if the first pattern matches:\n"
+    "\t\tKeywords: 'today','yesterday','thisweek','lastweek',\n"
+    "\t\t'thismonth', 'lastmonth'. \n"
+    "\t\tmonth-day : month[-day]] (month: January, Jan, or 1)\n"
+    "\t\tISO format: year[-month[-day]] (month: January, Jan, or 1)\n"
+    "\t\tUS format : month[/day[/year]]\n"
+    "\n"
+    "\t\tThe parser will fill in bits in the enddate from context if\n"
+    "\t\tleft out, e.g. a 'date:2004..01' will find from 2004-01-01\n"
+    "\t\tthrough 2004-01-31\n"
     "\n\n";
 
 command_t commands[] = {
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2010-01-26 11:44 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-01-26 11:43 [PATCH 1/4] Make the date parser nicer Sebastian Spaeth
2010-01-26 11:43 ` [PATCH 2/4] add date parser file from Keith Sebastian Spaeth
2010-01-26 11:43   ` [PATCH 3/4] compile date.c as well Sebastian Spaeth
2010-01-26 11:43     ` [PATCH 4/4] integrate keithp's date.c into the notmuch date parser and delete my previous own attempt Sebastian Spaeth

Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).