Matt Chaput avatar Matt Chaput committed 7ef3d9b

Simplified DateRangeFacet. Besides decomplicating it, this allows it to support relativedelta objects as gaps.

Comments (0)

Files changed (2)

src/whoosh/sorting.py

         self.hardend = hardend
         self._queries()
     
+    def _rangetype(self):
+        from whoosh import query
+        
+        return query.NumericRange
+    
     def _range_name(self, startval, endval):
         return (startval, endval)
     
     def _queries(self):
-        from whoosh import query
-        
         if not self.gap:
             raise Exception("No gap secified (%r)" % self.gap)
         if isinstance(self.gap, (list, tuple)):
             gaps = [self.gap]
             gapindex = -1
         
+        rangetype = self._rangetype()
         self.querydict = {}
         cstart = self.start
         while cstart < self.end:
                 cend = min(self.end, cend)
             
             rangename = self._range_name(cstart, cend)
-            q = query.NumericRange(self.fieldname, cstart, cend, endexcl=True)
+            q = rangetype(self.fieldname, cstart, cend, endexcl=True)
             self.querydict[rangename] = q
             
-            cstart += thisgap
+            cstart = cend
     
     def categorizer(self, searcher):
         return QueryFacet(self.querydict).categorizer(searcher)
     
 
 class DateRangeFacet(RangeFacet):
-    """Sorts/facets based on date ranges.
+    """Sorts/facets based on date ranges. This is the same as RangeFacet
+    except you are expected to use ``daterange`` objects as the start and end
+    of the range, and ``timedelta`` or ``relativedelta`` objects as the gap(s),
+    and it generates :class:`~whoosh.query.DateRange` queries instead of
+    :class:`~whoosh.query.TermRange` queries.
     
-    For example, to facet the "birthday" field into year-sized buckets::
+    For example, to facet a "birthday" range into 5 year buckets::
     
+        from datetime import datetime
+        from whoosh.support.relativedelta import relativedelta
+        
         startdate = datetime(1920, 0, 0)
         enddate = datetime.now()
-        gap = timedelta(days=365)
+        gap = relativedelta(years=5)
         bdays = RangeFacet("birthday", startdate, enddate, gap)
         results = searcher.search(myquery, groupedby=bdays)
         
     at the end.
     """
     
-    def __init__(self, fieldname, startdate, enddate, delta, hardend=False):
-        """
-        :param fieldname: the datetime field to sort/facet on.
-        :param startdate: the start of the entire range.
-        :param enddate: the end of the entire range.
-        :param delta: a timedelta object representing the size of each "bucket"
-            in the range. This can be a sequence of timedeltas. For example,
-            ``gap=[timedelta(days=1), timedelta(days=5), timedelta(days=10)]``
-            will use 1 day as the size of the first bucket, 5 days as the size
-            of the second bucket, and 10 days as the size of all subsequent
-            buckets.
-        :param hardend: if True, the end of the last bucket is clamped to the
-            value of ``end``. If False (the default), the last bucket is always
-            ``gap`` sized, even if that means the end of the last bucket is
-            after ``end``.
-        """
+    def _rangetype(self):
+        from whoosh import query
         
-        self.fieldname = fieldname
-        self.start = datetime_to_long(startdate)
-        self.end = datetime_to_long(enddate)
-        self.hardend = hardend
-        if isinstance(delta, (list, tuple)):
-            self.gap = [timedelta_to_usecs(d) for d in delta]
-        else:
-            self.gap = timedelta_to_usecs(delta)
-        self._queries()
+        return query.DateRange
     
-    def _range_name(self, startval, endval):
-        return (long_to_datetime(startval), long_to_datetime(endval))
-
 
 class ScoreFacet(FacetType):
     """Uses a document's score as a sorting criterion.

tests/test_sorting.py

                                         (dt(2001, 1, 11, 0, 0), dt(2001, 1, 16, 0, 0)): [0],
                                         None: [2]})
 
+def test_relative_daterange():
+    from whoosh.support.relativedelta import relativedelta
+    dt = datetime
+    
+    schema = fields.Schema(id=fields.STORED, date=fields.DATETIME)
+    ix = RamStorage().create_index(schema)
+    basedate = datetime(2001, 1, 1)
+    count = 0
+    with ix.writer() as w:
+        while basedate < datetime(2001, 12, 1):
+            w.add_document(id=count, date=basedate)
+            basedate += timedelta(days=14, hours=16)
+            count += 1
+    
+    with ix.searcher() as s:
+        gap = relativedelta(months=1)
+        rf = sorting.DateRangeFacet("date", dt(2001, 1, 1), dt(2001, 12, 31), gap)
+        r = s.search(query.Every(), groupedby={"date": rf})
+        assert_equal(r.groups("date"), {(dt(2001, 1, 1), dt(2001, 2, 1)): [0, 1, 2],
+                                        (dt(2001, 2, 1), dt(2001, 3, 1)): [3, 4],
+                                        (dt(2001, 3, 1), dt(2001, 4, 1)): [5, 6],
+                                        (dt(2001, 4, 1), dt(2001, 5, 1)): [7, 8],
+                                        (dt(2001, 5, 1), dt(2001, 6, 1)): [9, 10],
+                                        (dt(2001, 6, 1), dt(2001, 7, 1)): [11, 12],
+                                        (dt(2001, 7, 1), dt(2001, 8, 1)): [13, 14],
+                                        (dt(2001, 8, 1), dt(2001, 9, 1)): [15, 16],
+                                        (dt(2001, 9, 1), dt(2001, 10, 1)): [17, 18],
+                                        (dt(2001, 10, 1), dt(2001, 11, 1)): [19, 20],
+                                        (dt(2001, 11, 1), dt(2001, 12, 1)): [21, 22],
+                                        })
+
 def test_overlapping_facet():
     schema = fields.Schema(id=fields.STORED, tags=fields.KEYWORD)
     ix = RamStorage().create_index(schema)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.