From a5307e9f20ed121d14e2feac00c89b380fa51694 Mon Sep 17 00:00:00 2001 From: kedder Date: Sat, 8 Mar 2003 20:41:45 +0000 Subject: [PATCH] added support for searching on ranges of dates git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1576 57a73879-2fb5-44c3-a270-3262357dd7e2 --- CHANGES.txt | 2 + doc/user_guide.txt | 42 ++++++++++++++++- roundup/backends/back_anydbm.py | 26 +++++++++-- roundup/backends/rdbms_common.py | 19 ++++++-- roundup/date.py | 77 +++++++++++++++++++++++++++++++- 5 files changed, 157 insertions(+), 9 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 6dd8699..a5d04c9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -41,6 +41,8 @@ Feature: - added Node.get() method - nicer page titles (sf feature 65197) - relaxed CVS importing (sf feature 693277) +- added support for searching on ranges of dates (see doc/user_guide.txt in + chapter "Searching Page" for details) Fixed: diff --git a/doc/user_guide.txt b/doc/user_guide.txt index 567be43..49ca3a3 100644 --- a/doc/user_guide.txt +++ b/doc/user_guide.txt @@ -2,7 +2,7 @@ User Guide ========== -:Version: $Revision: 1.13 $ +:Version: $Revision: 1.14 $ .. contents:: @@ -105,6 +105,46 @@ Searching Page XXX: some information about how searching works +Some fields in the search page (e.g. "Activity" or "Creation date") accept +ranges of dates. You can specify range of dates in one of two formats: + + 1. Native english syntax: + [[From] ][ To ] + Keywords "From" and "To" are case insensitive. Keyword "From" is optional. + + 2. "Geek" syntax: + [][; ] + +Either first or second can be omitted in both syntaxes. + +For example: + +if you enter string "from 9:00" to "Creation date" field, roundup +will find all issues, that were created today since 9 AM. + +Searching of "-2m; -1m" on activity field gives you issues, which were +active between period of time since 2 months up-till month ago. + +Other possible examples (consider local time is Sat Mar 8 22:07:48 EET 2003): + + >>> Range("from 2-12 to 4-2") + + + >>> Range("18:00 TO +2m") + + + >>> Range("12:00") + + + >>> Range("tO +3d") + + + >>> Range("2002-11-10; 2002-12-12") + + + >>> Range("; 20:00 +1d") + + Under the covers ---------------- diff --git a/roundup/backends/back_anydbm.py b/roundup/backends/back_anydbm.py index 3297e2f..417e3d2 100644 --- a/roundup/backends/back_anydbm.py +++ b/roundup/backends/back_anydbm.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -#$Id: back_anydbm.py,v 1.109 2003-03-06 07:33:29 richard Exp $ +#$Id: back_anydbm.py,v 1.110 2003-03-08 20:41:45 kedder Exp $ ''' This module defines a backend that saves the hyperdatabase in a database chosen by anydbm. It is guaranteed to always be available in python @@ -31,6 +31,7 @@ from roundup.indexer import Indexer from roundup.backends import locking from roundup.hyperdb import String, Password, Date, Interval, Link, \ Multilink, DatabaseError, Boolean, Number, Node +from roundup.date import Range # # Now the database @@ -1582,7 +1583,10 @@ class Class(hyperdb.Class): LINK = 0 MULTILINK = 1 STRING = 2 + DATE = 3 OTHER = 6 + + timezone = self.db.getUserTimezone() for k, v in filterspec.items(): propclass = props[k] if isinstance(propclass, Link): @@ -1623,14 +1627,22 @@ class Class(hyperdb.Class): v = v.replace('?', '.') v = v.replace('*', '.*?') l.append((STRING, k, re.compile(v, re.I))) + elif isinstance(propclass, Date): + try: + date_rng = Range(v, date.Date, offset=timezone) + l.append((DATE, k, date_rng)) + except ValueError: + # If range creation fails - ignore that search parameter + pass elif isinstance(propclass, Boolean): if type(v) is type(''): bv = v.lower() in ('yes', 'true', 'on', '1') else: bv = v l.append((OTHER, k, bv)) - elif isinstance(propclass, Date): - l.append((OTHER, k, date.Date(v))) + # kedder: dates are filtered by ranges + #elif isinstance(propclass, Date): + # l.append((OTHER, k, date.Date(v))) elif isinstance(propclass, Interval): l.append((OTHER, k, date.Interval(v))) elif isinstance(propclass, Number): @@ -1680,6 +1692,14 @@ class Class(hyperdb.Class): # RE search if node[k] is None or not v.search(node[k]): break + elif t == DATE: + if node[k] is None: break + if v.to_value: + if not (v.from_value < node[k] and v.to_value > node[k]): + break + else: + if not (v.from_value < node[k]): + break elif t == OTHER: # straight value comparison for the other types if node[k] != v: diff --git a/roundup/backends/rdbms_common.py b/roundup/backends/rdbms_common.py index 6d0fc1e..ce6455c 100644 --- a/roundup/backends/rdbms_common.py +++ b/roundup/backends/rdbms_common.py @@ -1,4 +1,4 @@ -# $Id: rdbms_common.py,v 1.40 2003-03-06 07:33:29 richard Exp $ +# $Id: rdbms_common.py,v 1.41 2003-03-08 20:41:45 kedder Exp $ ''' Relational database (SQL) backend common code. Basics: @@ -34,6 +34,7 @@ from roundup.backends import locking from blobfiles import FileStorage from roundup.indexer import Indexer from sessions import Sessions, OneTimeKeys +from roundup.date import Range # number of rows to keep in memory ROW_CACHE_SIZE = 100 @@ -1731,6 +1732,8 @@ class Class(hyperdb.Class): cn = self.classname + timezone = self.db.getUserTimezone() + # figure the WHERE clause from the filterspec props = self.getprops() frum = ['_'+cn] @@ -1796,8 +1799,18 @@ class Class(hyperdb.Class): where.append('_%s in (%s)'%(k, s)) args = args + [date.Date(x).serialise() for x in v] else: - where.append('_%s=%s'%(k, a)) - args.append(date.Date(v).serialise()) + try: + # Try to filter on range of dates + date_rng = Range(v, date.Date, offset=timezone) + if (date_rng.from_value): + where.append('_%s > %s'%(k, a)) + args.append(date_rng.from_value.serialise()) + if (date_rng.to_value): + where.append('_%s < %s'%(k, a)) + args.append(date_rng.to_value.serialise()) + except ValueError: + # If range creation fails - ignore that search parameter + pass elif isinstance(propclass, Interval): if isinstance(v, type([])): s = ','.join([a for x in v]) diff --git a/roundup/date.py b/roundup/date.py index 9c47917..3e6559f 100644 --- a/roundup/date.py +++ b/roundup/date.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: date.py,v 1.45 2003-03-06 06:12:30 richard Exp $ +# $Id: date.py,v 1.46 2003-03-08 20:41:45 kedder Exp $ __doc__ = """ Date, time and time interval handling. @@ -588,6 +588,79 @@ def fixTimeOverflow(time): return (sign, y, m, d, H, M, S) +class Range: + """ + Represents range between two values + Ranges can be created using one of theese two alternative syntaxes: + + 1. Native english syntax: + [[From] ][ To ] + Keywords "From" and "To" are case insensitive. Keyword "From" is optional. + + 2. "Geek" syntax: + [][; ] + + Either first or second can be omitted in both syntaxes. + + Examples (consider local time is Sat Mar 8 22:07:48 EET 2003): + >>> Range("from 2-12 to 4-2") + + + >>> Range("18:00 TO +2m") + + + >>> Range("12:00") + + + >>> Range("tO +3d") + + + >>> Range("2002-11-10; 2002-12-12") + + + >>> Range("; 20:00 +1d") + + + """ + def __init__(self, spec, type, **params): + """Initializes Range of type from given string. + + Sets two properties - from_value and to_value. None assigned to any of + this properties means "infinitum" (-infinitum to from_value and + +infinitum to to_value) + """ + self.range_type = type + re_range = r'(?:^|(?:from)?(.+?))(?:to(.+?)$|$)' + re_geek_range = r'(?:^|(.+?))(?:;(.+?)$|$)' + # Check which syntax to use + if spec.find(';') == -1: + # Native english + mch_range = re.search(re_range, spec.strip(), re.IGNORECASE) + else: + # Geek + mch_range = re.search(re_geek_range, spec.strip()) + if mch_range: + self.from_value, self.to_value = mch_range.groups() + if self.from_value: + self.from_value = type(self.from_value.strip(), **params) + if self.to_value: + self.to_value = type(self.to_value.strip(), **params) + else: + raise ValueError, "Invalid range" + + def __str__(self): + return "from %s to %s" % (self.from_value, self.to_value) + + def __repr__(self): + return "" % self.__str__() + +def test_range(): + rspecs = ("from 2-12 to 4-2", "18:00 TO +2m", "12:00", "tO +3d", + "2002-11-10; 2002-12-12", "; 20:00 +1d") + for rspec in rspecs: + print '>>> Range("%s")' % rspec + print `Range(rspec, Date)` + print def test(): intervals = (" 3w 1 d 2:00", " + 2d", "3w") @@ -607,6 +680,6 @@ def test(): print `Date(date) + Interval(interval)` if __name__ == '__main__': - test() + test_range() # vim: set filetype=python ts=4 sw=4 et si -- 2.30.2