Skip to content

Commit 2c4d3b1

Browse files
author
Andrey Lebedev
committed
added support for searching on ranges of dates
1 parent c8677bb commit 2c4d3b1

File tree

5 files changed

+157
-9
lines changed

5 files changed

+157
-9
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Feature:
4141
- added Node.get() method
4242
- nicer page titles (sf feature 65197)
4343
- relaxed CVS importing (sf feature 693277)
44+
- added support for searching on ranges of dates (see doc/user_guide.txt in
45+
chapter "Searching Page" for details)
4446

4547

4648
Fixed:

doc/user_guide.txt

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
User Guide
33
==========
44

5-
:Version: $Revision: 1.13 $
5+
:Version: $Revision: 1.14 $
66

77
.. contents::
88

@@ -105,6 +105,46 @@ Searching Page
105105

106106
XXX: some information about how searching works
107107

108+
Some fields in the search page (e.g. "Activity" or "Creation date") accept
109+
ranges of dates. You can specify range of dates in one of two formats:
110+
111+
1. Native english syntax:
112+
[[From] <value>][ To <value>]
113+
Keywords "From" and "To" are case insensitive. Keyword "From" is optional.
114+
115+
2. "Geek" syntax:
116+
[<value>][; <value>]
117+
118+
Either first or second <value> can be omitted in both syntaxes.
119+
120+
For example:
121+
122+
if you enter string "from 9:00" to "Creation date" field, roundup
123+
will find all issues, that were created today since 9 AM.
124+
125+
Searching of "-2m; -1m" on activity field gives you issues, which were
126+
active between period of time since 2 months up-till month ago.
127+
128+
Other possible examples (consider local time is Sat Mar 8 22:07:48 EET 2003):
129+
130+
>>> Range("from 2-12 to 4-2")
131+
<Range from 2003-02-12.00:00:00 to 2003-04-02.00:00:00>
132+
133+
>>> Range("18:00 TO +2m")
134+
<Range from 2003-03-08.18:00:00 to 2003-05-08.20:07:48>
135+
136+
>>> Range("12:00")
137+
<Range from 2003-03-08.12:00:00 to None>
138+
139+
>>> Range("tO +3d")
140+
<Range from None to 2003-03-11.20:07:48>
141+
142+
>>> Range("2002-11-10; 2002-12-12")
143+
<Range from 2002-11-10.00:00:00 to 2002-12-12.00:00:00>
144+
145+
>>> Range("; 20:00 +1d")
146+
<Range from None to 2003-03-09.20:00:00>
147+
108148

109149
Under the covers
110150
----------------

roundup/backends/back_anydbm.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
#$Id: back_anydbm.py,v 1.109 2003-03-06 07:33:29 richard Exp $
18+
#$Id: back_anydbm.py,v 1.110 2003-03-08 20:41:45 kedder Exp $
1919
'''
2020
This module defines a backend that saves the hyperdatabase in a database
2121
chosen by anydbm. It is guaranteed to always be available in python
@@ -31,6 +31,7 @@
3131
from roundup.backends import locking
3232
from roundup.hyperdb import String, Password, Date, Interval, Link, \
3333
Multilink, DatabaseError, Boolean, Number, Node
34+
from roundup.date import Range
3435

3536
#
3637
# Now the database
@@ -1582,7 +1583,10 @@ def filter(self, search_matches, filterspec, sort=(None,None),
15821583
LINK = 0
15831584
MULTILINK = 1
15841585
STRING = 2
1586+
DATE = 3
15851587
OTHER = 6
1588+
1589+
timezone = self.db.getUserTimezone()
15861590
for k, v in filterspec.items():
15871591
propclass = props[k]
15881592
if isinstance(propclass, Link):
@@ -1623,14 +1627,22 @@ def filter(self, search_matches, filterspec, sort=(None,None),
16231627
v = v.replace('?', '.')
16241628
v = v.replace('*', '.*?')
16251629
l.append((STRING, k, re.compile(v, re.I)))
1630+
elif isinstance(propclass, Date):
1631+
try:
1632+
date_rng = Range(v, date.Date, offset=timezone)
1633+
l.append((DATE, k, date_rng))
1634+
except ValueError:
1635+
# If range creation fails - ignore that search parameter
1636+
pass
16261637
elif isinstance(propclass, Boolean):
16271638
if type(v) is type(''):
16281639
bv = v.lower() in ('yes', 'true', 'on', '1')
16291640
else:
16301641
bv = v
16311642
l.append((OTHER, k, bv))
1632-
elif isinstance(propclass, Date):
1633-
l.append((OTHER, k, date.Date(v)))
1643+
# kedder: dates are filtered by ranges
1644+
#elif isinstance(propclass, Date):
1645+
# l.append((OTHER, k, date.Date(v)))
16341646
elif isinstance(propclass, Interval):
16351647
l.append((OTHER, k, date.Interval(v)))
16361648
elif isinstance(propclass, Number):
@@ -1680,6 +1692,14 @@ def filter(self, search_matches, filterspec, sort=(None,None),
16801692
# RE search
16811693
if node[k] is None or not v.search(node[k]):
16821694
break
1695+
elif t == DATE:
1696+
if node[k] is None: break
1697+
if v.to_value:
1698+
if not (v.from_value < node[k] and v.to_value > node[k]):
1699+
break
1700+
else:
1701+
if not (v.from_value < node[k]):
1702+
break
16831703
elif t == OTHER:
16841704
# straight value comparison for the other types
16851705
if node[k] != v:

roundup/backends/rdbms_common.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: rdbms_common.py,v 1.40 2003-03-06 07:33:29 richard Exp $
1+
# $Id: rdbms_common.py,v 1.41 2003-03-08 20:41:45 kedder Exp $
22
''' Relational database (SQL) backend common code.
33
44
Basics:
@@ -34,6 +34,7 @@
3434
from blobfiles import FileStorage
3535
from roundup.indexer import Indexer
3636
from sessions import Sessions, OneTimeKeys
37+
from roundup.date import Range
3738

3839
# number of rows to keep in memory
3940
ROW_CACHE_SIZE = 100
@@ -1731,6 +1732,8 @@ def filter(self, search_matches, filterspec, sort=(None,None),
17311732

17321733
cn = self.classname
17331734

1735+
timezone = self.db.getUserTimezone()
1736+
17341737
# figure the WHERE clause from the filterspec
17351738
props = self.getprops()
17361739
frum = ['_'+cn]
@@ -1796,8 +1799,18 @@ def filter(self, search_matches, filterspec, sort=(None,None),
17961799
where.append('_%s in (%s)'%(k, s))
17971800
args = args + [date.Date(x).serialise() for x in v]
17981801
else:
1799-
where.append('_%s=%s'%(k, a))
1800-
args.append(date.Date(v).serialise())
1802+
try:
1803+
# Try to filter on range of dates
1804+
date_rng = Range(v, date.Date, offset=timezone)
1805+
if (date_rng.from_value):
1806+
where.append('_%s > %s'%(k, a))
1807+
args.append(date_rng.from_value.serialise())
1808+
if (date_rng.to_value):
1809+
where.append('_%s < %s'%(k, a))
1810+
args.append(date_rng.to_value.serialise())
1811+
except ValueError:
1812+
# If range creation fails - ignore that search parameter
1813+
pass
18011814
elif isinstance(propclass, Interval):
18021815
if isinstance(v, type([])):
18031816
s = ','.join([a for x in v])

roundup/date.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: date.py,v 1.45 2003-03-06 06:12:30 richard Exp $
18+
# $Id: date.py,v 1.46 2003-03-08 20:41:45 kedder Exp $
1919

2020
__doc__ = """
2121
Date, time and time interval handling.
@@ -588,6 +588,79 @@ def fixTimeOverflow(time):
588588

589589
return (sign, y, m, d, H, M, S)
590590

591+
class Range:
592+
"""
593+
Represents range between two values
594+
Ranges can be created using one of theese two alternative syntaxes:
595+
596+
1. Native english syntax:
597+
[[From] <value>][ To <value>]
598+
Keywords "From" and "To" are case insensitive. Keyword "From" is optional.
599+
600+
2. "Geek" syntax:
601+
[<value>][; <value>]
602+
603+
Either first or second <value> can be omitted in both syntaxes.
604+
605+
Examples (consider local time is Sat Mar 8 22:07:48 EET 2003):
606+
>>> Range("from 2-12 to 4-2")
607+
<Range from 2003-02-12.00:00:00 to 2003-04-02.00:00:00>
608+
609+
>>> Range("18:00 TO +2m")
610+
<Range from 2003-03-08.18:00:00 to 2003-05-08.20:07:48>
611+
612+
>>> Range("12:00")
613+
<Range from 2003-03-08.12:00:00 to None>
614+
615+
>>> Range("tO +3d")
616+
<Range from None to 2003-03-11.20:07:48>
617+
618+
>>> Range("2002-11-10; 2002-12-12")
619+
<Range from 2002-11-10.00:00:00 to 2002-12-12.00:00:00>
620+
621+
>>> Range("; 20:00 +1d")
622+
<Range from None to 2003-03-09.20:00:00>
623+
624+
"""
625+
def __init__(self, spec, type, **params):
626+
"""Initializes Range of type <type> from given <spec> string.
627+
628+
Sets two properties - from_value and to_value. None assigned to any of
629+
this properties means "infinitum" (-infinitum to from_value and
630+
+infinitum to to_value)
631+
"""
632+
self.range_type = type
633+
re_range = r'(?:^|(?:from)?(.+?))(?:to(.+?)$|$)'
634+
re_geek_range = r'(?:^|(.+?))(?:;(.+?)$|$)'
635+
# Check which syntax to use
636+
if spec.find(';') == -1:
637+
# Native english
638+
mch_range = re.search(re_range, spec.strip(), re.IGNORECASE)
639+
else:
640+
# Geek
641+
mch_range = re.search(re_geek_range, spec.strip())
642+
if mch_range:
643+
self.from_value, self.to_value = mch_range.groups()
644+
if self.from_value:
645+
self.from_value = type(self.from_value.strip(), **params)
646+
if self.to_value:
647+
self.to_value = type(self.to_value.strip(), **params)
648+
else:
649+
raise ValueError, "Invalid range"
650+
651+
def __str__(self):
652+
return "from %s to %s" % (self.from_value, self.to_value)
653+
654+
def __repr__(self):
655+
return "<Range %s>" % self.__str__()
656+
657+
def test_range():
658+
rspecs = ("from 2-12 to 4-2", "18:00 TO +2m", "12:00", "tO +3d",
659+
"2002-11-10; 2002-12-12", "; 20:00 +1d")
660+
for rspec in rspecs:
661+
print '>>> Range("%s")' % rspec
662+
print `Range(rspec, Date)`
663+
print
591664

592665
def test():
593666
intervals = (" 3w 1 d 2:00", " + 2d", "3w")
@@ -607,6 +680,6 @@ def test():
607680
print `Date(date) + Interval(interval)`
608681

609682
if __name__ == '__main__':
610-
test()
683+
test_range()
611684

612685
# vim: set filetype=python ts=4 sw=4 et si

0 commit comments

Comments
 (0)