source: stamper/stamper/stamper.py@ 9:2457f4022c60

Last change on this file since 9:2457f4022c60 was 9:2457f4022c60, checked in by Borja Lopez <borja@…>, 10 years ago

Added show_totals method, to render totals and stamp details

File size: 6.1 KB
Line 
1
2import json
3import re
4from datetime import datetime, timedelta
5from os.path import expanduser, exists
6
7
8STAMPS_FILE = expanduser('~/.workstamps.json')
9DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
10
11
12class Stamper(object):
13
14 def __init__(self, stamps_file=STAMPS_FILE):
15 self.stamps_file = STAMPS_FILE
16 self.ensure_stamps_file()
17 self.stamps = []
18
19 def ensure_stamps_file(self):
20 if not exists(self.stamps_file):
21 with open(self.stamps_file, 'w') as stamps_file:
22 stamps_file.write('')
23
24 def load_stamps(self):
25 with open(self.stamps_file, 'r') as stamps_file:
26 try:
27 self.stamps = json.load(stamps_file)
28 except ValueError:
29 self.stamps = []
30
31 def save_stamps(self):
32 with open(self.stamps_file, 'w') as stamps_file:
33 json.dump(self.stamps, stamps_file, indent=4)
34
35 def stamp(self, start, end, customer, action):
36 self.stamps.append({
37 'start': start,
38 'end': end,
39 'customer': customer,
40 'action': action,
41 })
42
43 def last_stamp(self):
44 if not self.stamps:
45 return None
46 return self.stamps[-1]
47
48 def worktime(self, start, end):
49 worktime = (datetime.strptime(end, DATE_FORMAT) -
50 datetime.strptime(start, DATE_FORMAT))
51 return worktime.seconds
52
53 def validate_filter(self, stamp_filter):
54 """
55 Validate a given filter. Filters can have the following notation:
56
57 - %Y-%m-%d--%Y-%m-%d: Times recorded between two dates
58
59 - -%Y-%m-%d: Times recorded before a given date
60
61 - +%Y-%m-%d: Times recorded after a given date
62
63 - N...N[d|w|m|y]: Times recorded N...N days/weeks/months/years ago
64 """
65 # First try the date filters, one by one
66 matches = ['%Y-%m-%d', '-%Y-%m-%d', '+%Y-%m-%d']
67 for match in matches:
68 try:
69 if '--' in stamp_filter:
70 filter_from, filter_to = stamp_filter.split('--')
71 filter_from = datetime.strptime(stamp_filter, match)
72 filter_to = datetime.strptime(stamp_filter, match)
73 else:
74 valid_filter = datetime.strptime(stamp_filter, match)
75 except ValueError:
76 pass
77 else:
78 return stamp_filter
79
80 valid_filter = re.search(r'(\d+[dwmyDWMY]{1})', stamp_filter)
81 if valid_filter:
82 return stamp_filter
83
84 # Not a valid filter
85 return None
86
87 def customers(self):
88 customers = []
89 for stamp in self.stamps:
90 if stamp['customer'] not in customers:
91 customers.append(stamp['customer'])
92 customers.remove(None)
93 return customers
94
95 def totals(self, stamp_filter=None):
96 totals = {}
97 for stamp in self.stamps:
98 customer = stamp['customer']
99 if customer:
100 # c will be None for "start" stamps, having no end time
101 if customer not in totals:
102 totals[customer] = 0
103 totals[customer] += self.worktime(stamp['start'], stamp['end'])
104 return totals
105
106 def details(self):
107 details = {}
108 totals = {}
109 for stamp in self.stamps:
110 if stamp['customer']:
111 # avoid "start" stamps
112 start_day = stamp['start'].split(' ')[0]
113 if start_day not in details:
114 details[start_day] = []
115 if start_day not in totals:
116 totals[start_day] = 0
117 worktime = self.worktime(stamp['start'], stamp['end'])
118 details[start_day].append(
119 ' -> %(worktime)s %(customer)s %(action)s' % {
120 'worktime': str(timedelta(seconds=worktime)),
121 'customer': stamp['customer'],
122 'action': stamp['action']
123 })
124 totals[start_day] += worktime
125 for day in totals:
126 totals[day] = str(timedelta(seconds=totals[day]))
127 return details, totals
128
129 def details_by_customer(self, customer):
130 details = {}
131 totals = {}
132 for stamp in self.stamps:
133 if stamp['customer'] == customer:
134 start_day = stamp['start'].split(' ')[0]
135 if start_day not in details:
136 details[start_day] = []
137 if start_day not in totals:
138 totals[start_day] = 0
139 worktime = self.worktime(stamp['start'], stamp['end'])
140 details[start_day].append(
141 ' -> %(worktime)s %(customer)s %(action)s' % {
142 'worktime': str(timedelta(seconds=worktime)),
143 'customer': stamp['customer'],
144 'action': stamp['action']
145 })
146 totals[start_day] += worktime
147 for day in totals:
148 totals[day] = str(timedelta(seconds=totals[day]))
149 return details, totals
150
151 def show_stamps(self, customer=None, stamp_filter=None, verbose=False):
152 if stamp_filter:
153 stamp_filter = self.validate_filter(stamp_filter)
154
155 totals = self.totals(stamp_filter)
156
157 if customer:
158 total = timedelta(seconds=totals.get(customer, 0))
159 print(' %(customer)s: %(total)s' % {'customer': customer,
160 'total': total})
161 else:
162 for c in totals:
163 total = timedelta(seconds=totals[c])
164 print(' %(customer)s: %(total)s' % {'customer': c,
165 'total': total})
166
167 if verbose:
168 if customer:
169 details, totals = self.details_by_customer(customer)
170 else:
171 details, totals = self.details()
172 for day in details:
173 print('------ %(day)s ------' % {'day': day})
174 for line in details[day]:
175 print(line)
176 print(' Total: %(total)s' % {'total': totals[day]})
Note: See TracBrowser for help on using the repository browser.