Index: stamper/stamper.py
===================================================================
--- stamper/stamper.py	(revision 25)
+++ stamper/stamper.py	(revision 26)
@@ -3,5 +3,5 @@
 import re
 import pygal
-from datetime import datetime, timedelta
+from datetime import datetime, date, timedelta
 from os.path import expanduser, exists
 from collections import OrderedDict
@@ -60,33 +60,71 @@
         Validate a given filter. Filters can have the following notation:
 
+        - %Y-%m-%d: Times recorded at a given date
+
         - %Y-%m-%d--%Y-%m-%d: Times recorded between two dates
 
-        - _%Y-%m-%d: Times recorded before a given date
-
-        - +%Y-%m-%d: Times recorded after a given date
+        - *%Y-%m-%d: Times recorded up to a  given date
+
+        - %Y-%m-%d*: Times recorded from a given date
 
         - N...N[d|w|m|y]: Times recorded N...N days/weeks/months/years ago
+
+        Important: all date comparisons are made on datetime objects, using
+        00:00 as the time (first second of the given day). This means that
+        for range filters, the first day is included, but the second day is not
         """
-        # First try the date filters, one by one
-        matches = ['%Y-%m-%d', '_%Y-%m-%d', '+%Y-%m-%d']
-        for match in matches:
-            try:
-                if '--' in stamp_filter:
-                    filter_from, filter_to = stamp_filter.split('--')
-                    filter_from = datetime.strptime(filter_from, match)
-                    filter_to = datetime.strptime(filter_to, match)
-                else:
-                    valid_filter = datetime.strptime(stamp_filter, match)
-            except ValueError:
-                pass
-            else:
-                return stamp_filter
-
-        valid_filter = re.search(r'(\d+[dwmyDWMY]{1})', stamp_filter)
-        if valid_filter:
-            return stamp_filter
-
-        # Not a valid filter
-        return None
+        filter_from = None
+        filter_to = None
+
+        if stamp_filter is None:
+            return filter_from, filter_to
+
+        if '--' in stamp_filter:
+            filter_from, filter_to = stamp_filter.split('--')
+            filter_from = datetime.strptime(filter_from, DATE_FORMAT)
+            filter_to = datetime.strptime(filter_to, DATE_FORMAT)
+
+        elif stamp_filter.startswith('*'):
+            filter_to = datetime.strptime(stamp_filter, '*'+DATE_FORMAT)
+            filter_to = filter_to.replace(hour=0, minute=0, second=0)
+
+        elif stamp_filter.endswith('*'):
+            filter_from = datetime.strptime(stamp_filter, DATE_FORMAT+'*')
+            filter_from = filter_from.replace(hour=0, minute=0, second=0)
+
+        elif re.search(r'(\d+[dD]{1})', stamp_filter):
+            number = int(stamp_filter.lower().replace('d', ''))
+            delta = timedelta(days=number)
+            filter_from = datetime.today() - delta
+            filter_from = filter_from.replace(hour=0, minute=0, second=0)
+
+        elif re.search(r'(\d+[wW]{1})', stamp_filter):
+            number = int(stamp_filter.lower().replace('w', '')) * 7
+            delta = timedelta(days=number)
+            filter_from = datetime.today() - delta
+            filter_from = filter_from.replace(hour=0, minute=0, second=0)
+
+        elif re.search(r'(\d+[mM]{1})', stamp_filter):
+            number = int(stamp_filter.lower().replace('m', ''))
+            past = date.today()
+            # start travelling in time, back to N months ago
+            for n in range(number):
+                past = past.replace(day=1) - timedelta(days=1)
+            # Now use the year/month from the past + the current day to set
+            # the proper date
+            filter_from = datetime(past.year, past.month, date.today().day)
+
+        elif re.search(r'(\d+[yY]{1})', stamp_filter):
+            number = int(stamp_filter.lower().replace('y', ''))
+            today = date.today()
+            filter_from = datetime(today.year - number, today.month, today.day)
+
+        else:
+            # maybe they are giving us a fixed date
+            filter_from = datetime.strptime(stamp_filter, DATE_FORMAT)
+            filter_from = filter_from.replace(hour=0, minute=0, second=0)
+            filter_to = filter_from + timedelta(days=1)
+
+        return filter_from, filter_to
 
     def customers(self):
@@ -98,10 +136,20 @@
         return customers
 
-    def totals(self, stamp_filter=None):
+    def totals(self, filter_from=None, filter_to=None):
         totals = {}
         for stamp in self.stamps:
             customer = stamp['customer']
+            # customer will be None for "start" stamps, having no end time
             if customer:
-                # c will be None for "start" stamps, having no end time
+                start = datetime.strptime(stamp['start'], DATETIME_FORMAT)
+                end = datetime.strptime(stamp['end'], DATETIME_FORMAT)
+                if filter_from and start < filter_from:
+                    # if there is a filter setting a starting date for the
+                    # report and the current stamp is from an earlier date, do
+                    # not add it to the totals
+                    continue
+                if filter_to and start > filter_to:
+                    # similar for the end date
+                    continue
                 if customer not in totals:
                     totals[customer] = 0
@@ -109,12 +157,28 @@
         return totals
 
-    def details(self):
+    def details(self, filter_customer=None, filter_from=None, filter_to=None):
         details = OrderedDict()
         totals = OrderedDict()
         total_customer = OrderedDict()
         for stamp in self.stamps:
-            if stamp['customer']:
+            customer = stamp['customer']
+            if customer:
+                if filter_customer and customer != filter_customer:
+                    # we are getting the details for only one customer, if this
+                    # stamp is not for that customer, simply move on and ignore
+                    # it
+                    continue
+                start = datetime.strptime(stamp['start'], DATETIME_FORMAT)
+                start_day = start.strftime('%Y-%m-%d')
+                end = datetime.strptime(stamp['end'], DATETIME_FORMAT)
+                if filter_from and start < filter_from:
+                    # if there is a filter setting a starting date for the
+                    # report and the current stamp is from an earlier date, do
+                    # not add it to the totals
+                    continue
+                if filter_to and start > filter_to:
+                    # similar for the end date
+                    continue
                 # avoid "start" stamps
-                start_day = stamp['start'].split(' ')[0]
                 if start_day not in details:
                     details[start_day] = []
@@ -125,8 +189,7 @@
                     ' -> %(worktime)s %(customer)s %(action)s' % {
                         'worktime': str(timedelta(seconds=worktime)),
-                        'customer': stamp['customer'],
+                        'customer': customer,
                         'action': stamp['action']
                     })
-                customer = stamp['customer']
                 totals[start_day] += worktime
                 if start_day not in total_customer:
@@ -139,33 +202,24 @@
         return details, totals, total_customer
 
-    def details_by_customer(self, customer):
-        details = OrderedDict()
-        totals = OrderedDict()
-        for stamp in self.stamps:
-            if stamp['customer'] == customer:
-                start_day = stamp['start'].split(' ')[0]
-                if start_day not in details:
-                    details[start_day] = []
-                if start_day not in totals:
-                    totals[start_day] = 0
-                worktime = self.worktime(stamp['start'], stamp['end'])
-                details[start_day].append(
-                    ' -> %(worktime)s %(customer)s %(action)s' % {
-                        'worktime': str(timedelta(seconds=worktime)),
-                        'customer': stamp['customer'],
-                        'action': stamp['action']
-                    })
-                totals[start_day] += worktime
-        for day in totals:
-            totals[day] = str(timedelta(seconds=totals[day]))
-        return details, totals
-
     def show_stamps(self, customer=None, stamp_filter=None, verbose=False,
         sum=False, graph=False):
-        if stamp_filter:
-            stamp_filter = self.validate_filter(stamp_filter)
-
-        totals = self.totals(stamp_filter)
-
+
+        filter_from, filter_to = self.validate_filter(stamp_filter)
+
+        # If the user asks for verbose information, show it before the
+        # totals (mimicing what the original stamp tool does)
+        if verbose:
+            details, totals, total_customer = self.details(customer,
+                                                           filter_from,
+                                                           filter_to)
+            for day in details:
+                print('------ %(day)s ------' % {'day': day})
+                for line in details[day]:
+                    print(line)
+                print(' Total: %(total)s' % {'total': totals[day]})
+            print '-'*79
+
+        # now calculate the totals and show them
+        totals = self.totals(filter_from, filter_to)
         if customer:
             seconds=totals.get(customer, 0)
@@ -179,15 +233,4 @@
                 print(' %(customer)s: %(total)s' % {'customer': c,
                                                     'total': total})
-
-        if verbose:
-            if customer:
-                details, totals = self.details_by_customer(customer)
-            else:
-                details, totals, total_customer = self.details()
-            for day in details:
-                print('------ %(day)s ------' % {'day': day})
-                for line in details[day]:
-                    print(line)
-                print(' Total: %(total)s' % {'total': totals[day]})
 
         if sum:
