source: stamper/stamper/stamper.py@ 22:43d14913e8fd

Last change on this file since 22:43d14913e8fd was 22:43d14913e8fd, checked in by Óscar M. Lage <info@…>, 10 years ago

Added -s option to sum all the times and show a resume

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