Changeset 26:0bcab03222d0 in stamper


Ignore:
Timestamp:
Jul 30, 2014, 4:31:10 PM (7 years ago)
Author:
Borja Lopez <borja@…>
Branch:
default
Phase:
public
Message:

Rewrote the way filters work (initial version, work in progress).
You can learn more about the filters in the docstring of the
validate_filter method. Basically, you can apply the filters
defined there when invoking stamps:

stamps customer 2014-07-01

stamps customer 2014-07-01* -v

stamps customer 2w

Refactored and cleaned up some code (removed the duplicated details
method, details_by_customer)

Moved the print of the details (verbose) before the code that shows
the totals, so we can get a result that looks closer to what the
original stamp tool does.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • stamper/stamper.py

    r25 r26  
    33import re
    44import pygal
    5 from datetime import datetime, timedelta
     5from datetime import datetime, date, timedelta
    66from os.path import expanduser, exists
    77from collections import OrderedDict
     
    6060        Validate a given filter. Filters can have the following notation:
    6161
     62        - %Y-%m-%d: Times recorded at a given date
     63
    6264        - %Y-%m-%d--%Y-%m-%d: Times recorded between two dates
    6365
    64         - _%Y-%m-%d: Times recorded before a given date
    65 
    66         - +%Y-%m-%d: Times recorded after a given date
     66        - *%Y-%m-%d: Times recorded up to a given date
     67
     68        - %Y-%m-%d*: Times recorded from a given date
    6769
    6870        - N...N[d|w|m|y]: Times recorded N...N days/weeks/months/years ago
     71
     72        Important: all date comparisons are made on datetime objects, using
     73        00:00 as the time (first second of the given day). This means that
     74        for range filters, the first day is included, but the second day is not
    6975        """
    70         # First try the date filters, one by one
    71         matches = ['%Y-%m-%d', '_%Y-%m-%d', '+%Y-%m-%d']
    72         for match in matches:
    73             try:
    74                 if '--' in stamp_filter:
    75                     filter_from, filter_to = stamp_filter.split('--')
    76                     filter_from = datetime.strptime(filter_from, match)
    77                     filter_to = datetime.strptime(filter_to, match)
    78                 else:
    79                     valid_filter = datetime.strptime(stamp_filter, match)
    80             except ValueError:
    81                 pass
    82             else:
    83                 return stamp_filter
    84 
    85         valid_filter = re.search(r'(\d+[dwmyDWMY]{1})', stamp_filter)
    86         if valid_filter:
    87             return stamp_filter
    88 
    89         # Not a valid filter
    90         return None
     76        filter_from = None
     77        filter_to = None
     78
     79        if stamp_filter is None:
     80            return filter_from, filter_to
     81
     82        if '--' in stamp_filter:
     83            filter_from, filter_to = stamp_filter.split('--')
     84            filter_from = datetime.strptime(filter_from, DATE_FORMAT)
     85            filter_to = datetime.strptime(filter_to, DATE_FORMAT)
     86
     87        elif stamp_filter.startswith('*'):
     88            filter_to = datetime.strptime(stamp_filter, '*'+DATE_FORMAT)
     89            filter_to = filter_to.replace(hour=0, minute=0, second=0)
     90
     91        elif stamp_filter.endswith('*'):
     92            filter_from = datetime.strptime(stamp_filter, DATE_FORMAT+'*')
     93            filter_from = filter_from.replace(hour=0, minute=0, second=0)
     94
     95        elif re.search(r'(\d+[dD]{1})', stamp_filter):
     96            number = int(stamp_filter.lower().replace('d', ''))
     97            delta = timedelta(days=number)
     98            filter_from = datetime.today() - delta
     99            filter_from = filter_from.replace(hour=0, minute=0, second=0)
     100
     101        elif re.search(r'(\d+[wW]{1})', stamp_filter):
     102            number = int(stamp_filter.lower().replace('w', '')) * 7
     103            delta = timedelta(days=number)
     104            filter_from = datetime.today() - delta
     105            filter_from = filter_from.replace(hour=0, minute=0, second=0)
     106
     107        elif re.search(r'(\d+[mM]{1})', stamp_filter):
     108            number = int(stamp_filter.lower().replace('m', ''))
     109            past = date.today()
     110            # start travelling in time, back to N months ago
     111            for n in range(number):
     112                past = past.replace(day=1) - timedelta(days=1)
     113            # Now use the year/month from the past + the current day to set
     114            # the proper date
     115            filter_from = datetime(past.year, past.month, date.today().day)
     116
     117        elif re.search(r'(\d+[yY]{1})', stamp_filter):
     118            number = int(stamp_filter.lower().replace('y', ''))
     119            today = date.today()
     120            filter_from = datetime(today.year - number, today.month, today.day)
     121
     122        else:
     123            # maybe they are giving us a fixed date
     124            filter_from = datetime.strptime(stamp_filter, DATE_FORMAT)
     125            filter_from = filter_from.replace(hour=0, minute=0, second=0)
     126            filter_to = filter_from + timedelta(days=1)
     127
     128        return filter_from, filter_to
    91129
    92130    def customers(self):
     
    98136        return customers
    99137
    100     def totals(self, stamp_filter=None):
     138    def totals(self, filter_from=None, filter_to=None):
    101139        totals = {}
    102140        for stamp in self.stamps:
    103141            customer = stamp['customer']
     142            # customer will be None for "start" stamps, having no end time
    104143            if customer:
    105                 # c will be None for "start" stamps, having no end time
     144                start = datetime.strptime(stamp['start'], DATETIME_FORMAT)
     145                end = datetime.strptime(stamp['end'], DATETIME_FORMAT)
     146                if filter_from and start < filter_from:
     147                    # if there is a filter setting a starting date for the
     148                    # report and the current stamp is from an earlier date, do
     149                    # not add it to the totals
     150                    continue
     151                if filter_to and start > filter_to:
     152                    # similar for the end date
     153                    continue
    106154                if customer not in totals:
    107155                    totals[customer] = 0
     
    109157        return totals
    110158
    111     def details(self):
     159    def details(self, filter_customer=None, filter_from=None, filter_to=None):
    112160        details = OrderedDict()
    113161        totals = OrderedDict()
    114162        total_customer = OrderedDict()
    115163        for stamp in self.stamps:
    116             if stamp['customer']:
     164            customer = stamp['customer']
     165            if customer:
     166                if filter_customer and customer != filter_customer:
     167                    # we are getting the details for only one customer, if this
     168                    # stamp is not for that customer, simply move on and ignore
     169                    # it
     170                    continue
     171                start = datetime.strptime(stamp['start'], DATETIME_FORMAT)
     172                start_day = start.strftime('%Y-%m-%d')
     173                end = datetime.strptime(stamp['end'], DATETIME_FORMAT)
     174                if filter_from and start < filter_from:
     175                    # if there is a filter setting a starting date for the
     176                    # report and the current stamp is from an earlier date, do
     177                    # not add it to the totals
     178                    continue
     179                if filter_to and start > filter_to:
     180                    # similar for the end date
     181                    continue
    117182                # avoid "start" stamps
    118                 start_day = stamp['start'].split(' ')[0]
    119183                if start_day not in details:
    120184                    details[start_day] = []
     
    125189                    ' -> %(worktime)s %(customer)s %(action)s' % {
    126190                        'worktime': str(timedelta(seconds=worktime)),
    127                         'customer': stamp['customer'],
     191                        'customer': customer,
    128192                        'action': stamp['action']
    129193                    })
    130                 customer = stamp['customer']
    131194                totals[start_day] += worktime
    132195                if start_day not in total_customer:
     
    139202        return details, totals, total_customer
    140203
    141     def details_by_customer(self, customer):
    142         details = OrderedDict()
    143         totals = OrderedDict()
    144         for stamp in self.stamps:
    145             if stamp['customer'] == customer:
    146                 start_day = stamp['start'].split(' ')[0]
    147                 if start_day not in details:
    148                     details[start_day] = []
    149                 if start_day not in totals:
    150                     totals[start_day] = 0
    151                 worktime = self.worktime(stamp['start'], stamp['end'])
    152                 details[start_day].append(
    153                     ' -> %(worktime)s %(customer)s %(action)s' % {
    154                         'worktime': str(timedelta(seconds=worktime)),
    155                         'customer': stamp['customer'],
    156                         'action': stamp['action']
    157                     })
    158                 totals[start_day] += worktime
    159         for day in totals:
    160             totals[day] = str(timedelta(seconds=totals[day]))
    161         return details, totals
    162 
    163204    def show_stamps(self, customer=None, stamp_filter=None, verbose=False,
    164205        sum=False, graph=False):
    165         if stamp_filter:
    166             stamp_filter = self.validate_filter(stamp_filter)
    167 
    168         totals = self.totals(stamp_filter)
    169 
     206
     207        filter_from, filter_to = self.validate_filter(stamp_filter)
     208
     209        # If the user asks for verbose information, show it before the
     210        # totals (mimicing what the original stamp tool does)
     211        if verbose:
     212            details, totals, total_customer = self.details(customer,
     213                                                           filter_from,
     214                                                           filter_to)
     215            for day in details:
     216                print('------ %(day)s ------' % {'day': day})
     217                for line in details[day]:
     218                    print(line)
     219                print(' Total: %(total)s' % {'total': totals[day]})
     220            print '-'*79
     221
     222        # now calculate the totals and show them
     223        totals = self.totals(filter_from, filter_to)
    170224        if customer:
    171225            seconds=totals.get(customer, 0)
     
    179233                print(' %(customer)s: %(total)s' % {'customer': c,
    180234                                                    'total': total})
    181 
    182         if verbose:
    183             if customer:
    184                 details, totals = self.details_by_customer(customer)
    185             else:
    186                 details, totals, total_customer = self.details()
    187             for day in details:
    188                 print('------ %(day)s ------' % {'day': day})
    189                 for line in details[day]:
    190                     print(line)
    191                 print(' Total: %(total)s' % {'total': totals[day]})
    192235
    193236        if sum:
Note: See TracChangeset for help on using the changeset viewer.