Code

extended date syntax to make range searches even more useful
authorkedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2>
Tue, 22 Apr 2003 20:53:55 +0000 (20:53 +0000)
committerkedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2>
Tue, 22 Apr 2003 20:53:55 +0000 (20:53 +0000)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1671 57a73879-2fb5-44c3-a270-3262357dd7e2

CHANGES.txt
doc/user_guide.txt
roundup/backends/back_anydbm.py
roundup/backends/rdbms_common.py
roundup/date.py
test/test_dates.py
test/test_db.py

index 81cd0e5d9b51c46f1535b1bfe9dae53019e967ea..7cb8e42901dd5c6de6358a51e9f9fcb53d8310c4 100644 (file)
@@ -63,6 +63,7 @@ Feature:
 - HTML templating files now have a .html extension
 - Roundup templates are now distributed much more sanely, allowing for
   3rd-party templates.
+- extended date syntax to make range searches even more useful
 
 Fixed:
 - applied unicode patch. All data is stored in utf-8. Incoming messages
index 8d945309daa5264e664f21f3d2d1e11a9391aada..b40b39fe26f9721a7898fa66f71c9e7f7da7b18a 100644 (file)
@@ -2,7 +2,7 @@
 User Guide
 ==========
 
-:Version: $Revision: 1.20 $
+:Version: $Revision: 1.21 $
 
 .. contents::
 
@@ -99,13 +99,13 @@ of dates in one of two formats:
 
 1. English syntax::
 
-    [[From] <value>][ To <value>]
+    [From <value>][To <value>]
 
    Keywords "From" and "To" are case insensitive. Keyword "From" is optional.
 
 2. "Geek" syntax::
 
-    [<value>][; <value>]
+    [<value>];[<value>]
 
 Either first or second ``<value>`` can be omitted in both syntaxes.
 
@@ -117,24 +117,30 @@ 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 2003)::
 
-       >>> Range("from 2-12 to 4-2")
-       <Range from 2003-02-12.00:00:00 to 2003-04-02.00:00:00>
-       
-       >>> Range("18:00 TO +2m")
-       <Range from 2003-03-08.18:00:00 to 2003-05-08.20:07:48>
-       
-       >>> Range("12:00")
-       <Range from 2003-03-08.12:00:00 to None>
-       
-       >>> Range("tO +3d")
-       <Range from None to 2003-03-11.20:07:48>
-       
-       >>> Range("2002-11-10; 2002-12-12")
-       <Range from 2002-11-10.00:00:00 to 2002-12-12.00:00:00>
-
-       >>> Range("; 20:00 +1d")
-       <Range from None to 2003-03-09.20:00:00>
+    >>> Range("from 2-12 to 4-2")
+    <Range from 2003-02-12.00:00:00 to 2003-04-02.00:00:00>
+    
+    >>> Range("FROM 18:00 TO +2m")
+    <Range from 2003-03-08.18:00:00 to 2003-05-08.20:07:48>
+    
+    >>> Range("12:00;")
+    <Range from 2003-03-08.12:00:00 to None>
+    
+    >>> Range("tO +3d")
+    <Range from None to 2003-03-11.20:07:48>
+    
+    >>> Range("2002-11-10; 2002-12-12")
+    <Range from 2002-11-10.00:00:00 to 2002-12-12.00:00:00>
 
+    >>> Range("; 20:00 +1d")
+    <Range from None to 2003-03-09.20:00:00>
+
+    >>> Range("2003")
+    <Range from 2003-01-01.00:00:00 to 2003-12-31.23:59:59>
+
+    >>> Range("2003-04")
+    <Range from 2003-04-01.00:00:00 to 2003-04-30.23:59:59>
+    
 
 Interval properties
 ~~~~~~~~~~~~~~~~~~~
@@ -486,10 +492,10 @@ The basic usage is::
   -u                -- the user[:password] to use for commands
   -d                -- print full designators not just class id numbers
   -c                -- when outputting lists of data, comma-separate them.
-                      Same as '-S ","'.
+               Same as '-S ","'.
   -S <string>       -- when outputting lists of data, string-separate them
   -s                -- when outputting lists of data, space-separate them.
-                      Same as '-S " "'.
+               Same as '-S " "'.
 
   Only one of -s, -c or -S can be specified.
 
@@ -549,6 +555,8 @@ printed results:
     "11-07.09:32:43"   yyyy-11-07.14:32:43
     "14:25"            yyyy-mm-dd.19:25:00
     "8:47:11"          yyyy-mm-dd.13:47:11
+    "2003"             2003-01-01.00:00:00
+    "2003-04"          2003-04-01.00:00:00
     "."                "right now"
     
 - Link values are printed as item designators. When given as an argument,
@@ -625,7 +633,7 @@ You can also display issue contents::
 or status::
 
     shell% roundup-admin get name `/tools/roundup/bin/roundup-admin \
-             -dc -i /var/roundup/sysadmin get status issue3,issue1`
+          -dc -i /var/roundup/sysadmin get status issue3,issue1`
     unread
     deferred
 
@@ -644,7 +652,7 @@ which is the same as::
 Also the tautological::
 
    shell% roundup-admin get name \
-         `roundup-admin -dc get status \`roundup-admin -dc find issue \
+      `roundup-admin -dc get status \`roundup-admin -dc find issue \
           status=chatting\``
    chatting
    chatting
index 670a0aff0267b61202f62a0d22b85b4e3686c257..8299415e12658d3cb1b88178b958bbfc710b443a 100644 (file)
@@ -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.119 2003-04-20 11:58:45 kedder Exp $
+#$Id: back_anydbm.py,v 1.120 2003-04-22 20:53:54 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
@@ -1767,10 +1767,10 @@ class Class(hyperdb.Class):
                     elif t == DATE or t == INTERVAL:
                         if node[k] is None: break
                         if v.to_value:
-                            if not (v.from_value < node[k] and v.to_value > node[k]):
+                            if not (v.from_value <= node[k] and v.to_value >= node[k]):
                                 break
                         else:
-                            if not (v.from_value < node[k]):
+                            if not (v.from_value <= node[k]):
                                 break
                     elif t == OTHER:
                         # straight value comparison for the other types
index 454fe3e3acd459bd906cc441ceb4c67bdbc87cba..2d4a67a1cc2d9c2a5f64c49b46dfa9232c2f306f 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: rdbms_common.py,v 1.54 2003-04-20 11:58:45 kedder Exp $
+# $Id: rdbms_common.py,v 1.55 2003-04-22 20:53:54 kedder Exp $
 ''' Relational database (SQL) backend common code.
 
 Basics:
@@ -1852,10 +1852,10 @@ class Class(hyperdb.Class):
                         # 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))                            
+                            where.append('_%s >= %s'%(k, a))                            
                             args.append(date_rng.from_value.serialise())
                         if (date_rng.to_value):
-                            where.append('_%s < %s'%(k, a))
+                            where.append('_%s <= %s'%(k, a))
                             args.append(date_rng.to_value.serialise())
                     except ValueError:
                         # If range creation fails - ignore that search parameter
@@ -1870,10 +1870,10 @@ class Class(hyperdb.Class):
                         # Try to filter on range of intervals
                         date_rng = Range(v, date.Interval)
                         if (date_rng.from_value):
-                            where.append('_%s > %s'%(k, a))                            
+                            where.append('_%s >= %s'%(k, a))
                             args.append(date_rng.from_value.serialise())
                         if (date_rng.to_value):
-                            where.append('_%s < %s'%(k, a))
+                            where.append('_%s <= %s'%(k, a))
                             args.append(date_rng.to_value.serialise())
                     except ValueError:
                         # If range creation fails - ignore that search parameter
index 95777a33b4ca27eceb5506558576c46e1b1f60ab..2a543cdd69788103640d14d06b03f1b424c7bd2d 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: date.py,v 1.52 2003-04-21 14:29:39 kedder Exp $
+# $Id: date.py,v 1.53 2003-04-22 20:53:54 kedder Exp $
 
 __doc__ = """
 Date, time and time interval handling.
@@ -59,6 +59,8 @@ class Date:
       "11-07.09:32:43" means <Date yyyy-11-07.14:32:43>
       "14:25" means <Date yyyy-mm-dd.19:25:00>
       "8:47:11" means <Date yyyy-mm-dd.13:47:11>
+      "2003" means <Date 2003-01-01.00:00:00>
+      "2003-06" means <Date 2003-06-01.00:00:00>
       "." means "right now"
 
     The Date class should understand simple date expressions of the form
@@ -89,6 +91,7 @@ class Date:
     minute, second) is the serialisation format returned by the serialise()
     method, and is accepted as an argument on instatiation.
     '''
+    
     def __init__(self, spec='.', offset=0, add_granularity=0):
         """Construct a date given a specification and a time zone offset.
 
@@ -104,8 +107,10 @@ class Date:
             self.year, self.month, self.day, self.hour, self.minute, \
                 self.second, x, x, x = time.gmtime(ts)
 
+    usagespec='[yyyy]-[mm]-[dd].[H]H:MM[:SS][offset]'
     def set(self, spec, offset=0, date_re=re.compile(r'''
-            (((?P<y>\d\d\d\d)[/-])?(?P<m>\d\d?)?[/-](?P<d>\d\d?))? # [yyyy-]mm-dd
+            ((?P<y>\d\d\d\d)([/-](?P<m>\d\d?)([/-](?P<d>\d\d?))?)? # yyyy[-mm[-dd]]
+            |(?P<a>\d\d?)[/-](?P<b>\d\d?))?              # or mm-dd
             (?P<n>\.)?                                     # .
             (((?P<H>\d?\d):(?P<M>\d\d))?(:(?P<S>\d\d))?)?  # hh:mm:ss
             (?P<o>.+)?                                     # offset
@@ -125,24 +130,27 @@ class Date:
         # not serialised data, try usual format
         m = date_re.match(spec)
         if m is None:
-            raise ValueError, _('Not a date spec: [[yyyy-]mm-dd].'
-                '[[h]h:mm[:ss]][offset]')
+            raise ValueError, _('Not a date spec: %s' % self.usagespec)
 
         info = m.groupdict()
 
         if add_granularity:
-            _add_granularity(info, 'SMHdmy')
+            _add_granularity(info, 'SMHdmyab')
 
         # get the current date as our default
         y,m,d,H,M,S,x,x,x = time.gmtime(time.time())
 
-        # override year, month, day parts
-        if info['m'] is not None and info['d'] is not None:
-            m = int(info['m'])
-            d = int(info['d'])
+        if info['y'] is not None or info['a'] is not None:
             if info['y'] is not None:
                 y = int(info['y'])
-            # time defaults to 00:00:00 GMT - offset (local midnight)
+                m,d = (1,1)
+                if info['m'] is not None:
+                    m = int(info['m'])
+                    if info['d'] is not None:
+                        d = int(info['d'])
+            if info['a'] is not None:
+                m = int(info['a'])
+                d = int(info['b'])
             H = -offset
             M = S = 0
 
@@ -165,8 +173,7 @@ class Date:
             try:
                 self.applyInterval(Interval(info['o'], allowdate=0))
             except ValueError:
-                raise ValueError, _('Not a date spec: [[yyyy-]mm-dd].'
-                    '[[h]h:mm[:ss]][offset]')
+                raise ValueError, _('Not a date spec: %s' % self.usagespec)
 
     def addInterval(self, interval):
         ''' Add the interval to this date, returning the date tuple
index 54a4857089ae4ac3043b219ca9803cc7ba2ac919..69c1f4e9263c1dcfc740fbc2a023ca02b5bd3ee5 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: test_dates.py,v 1.23 2003-04-21 14:29:40 kedder Exp $ 
+# $Id: test_dates.py,v 1.24 2003-04-22 20:53:54 kedder Exp $ 
 
 import unittest, time
 
@@ -56,6 +56,8 @@ class DateTestCase(unittest.TestCase):
         ae(str(date), '%s-%02d-%02d.14:25:00'%(y, m, d))
         date = Date("8:47:11")
         ae(str(date), '%s-%02d-%02d.08:47:11'%(y, m, d))
+        ae(str(Date('2003')), '2003-01-01.00:00:00')
+        ae(str(Date('2004-06')), '2004-06-01.00:00:00')
 
     def testDateError(self):
         self.assertRaises(ValueError, Date, "12")
@@ -249,6 +251,8 @@ class DateTestCase(unittest.TestCase):
         ae = self.assertEqual
         ae(str(Date('2003-2-12', add_granularity=1)), '2003-02-12.23:59:59')
         ae(str(Date('2003-1-1.23:00', add_granularity=1)), '2003-01-01.23:00:59')
+        ae(str(Date('2003', add_granularity=1)), '2003-12-31.23:59:59')
+        ae(str(Date('2003-5', add_granularity=1)), '2003-05-31.23:59:59')
         ae(str(Interval('+1w', add_granularity=1)), '+ 14d')
         ae(str(Interval('-2m 3w', add_granularity=1)), '- 2m 14d')
 
index 8fc18d4b2e43a2708f1494d103a52096331c6936..da9f3c7fdbbdb9c907125deec9e5282bf7bf84dc 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: test_db.py,v 1.87 2003-04-21 22:38:48 richard Exp $ 
+# $Id: test_db.py,v 1.88 2003-04-22 20:53:55 kedder Exp $ 
 
 import unittest, os, shutil, time
 
@@ -703,6 +703,7 @@ class anydbmDBTestCase(MyTestCase):
         ae(filt(None, {'deadline': 'from 2003-02-16'}), ['2', '3', '4'])
         ae(filt(None, {'deadline': '2003-02-16;'}), ['2', '3', '4'])
         # year and month granularity
+        ae(filt(None, {'deadline': '2002'}), [])
         ae(filt(None, {'deadline': '2003'}), ['1', '2', '3'])
         ae(filt(None, {'deadline': '2004'}), ['4'])
         ae(filt(None, {'deadline': '2003-02'}), ['2', '3'])
@@ -713,7 +714,7 @@ class anydbmDBTestCase(MyTestCase):
         ae(filt(None, {'foo': 'from 0:50 to 2:00'}), ['1'])
         ae(filt(None, {'foo': 'from 0:50 to 1d 2:00'}), ['1', '2'])
         ae(filt(None, {'foo': 'from 5:50'}), ['2'])
-        ae(filt(None, {'foo': 'to 0:50'}), [])
+        ae(filt(None, {'foo': 'to 0:05'}), [])
 
     def testFilteringIntervalSort(self):
         ae, filt = self.filteringSetup()