-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPunchCard.py
195 lines (166 loc) · 7.68 KB
/
PunchCard.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import argparse
import re
import sys
import toml
def calcWorkTime(timeIn, timeOut):
inSplit = timeIn.split(':')
outSplit = timeOut.split(':')
hourIn = float(inSplit[0])
minuteIn = float(inSplit[1])
hourOut = float(outSplit[0])
minuteOut = float(outSplit[1])
if hourIn > hourOut:
newHour = (hourOut + 12) - hourIn
else:
newHour = hourOut - hourIn
newMinutes = minuteOut - minuteIn
if newMinutes < 0:
newHour -= 1
newMinutes += 60
return newHour + (newMinutes / 60)
def calculateDay(dayEntry, projects):
dayHours = {'total': 0.0}
errors = {}
for project in projects:
if project not in dayEntry or not dayEntry[project]:
continue
if not validDay(dayEntry[project]):
errors[project] = '{}: Invalid number of time punches'.format(project)
continue
index = 0
dayHours[project] = 0.0
while(index < len(dayEntry[project])):
if not validTime(dayEntry[project][index]) or not validTime(dayEntry[project][index+1]):
if not validTime(dayEntry[project][index]):
errors[project] = 'Invalid time: {}'.format(dayEntry[project][index])
if not validTime(dayEntry[project][index+1]):
errors[project] = (
'Invalid time: {}'.format(dayEntry[project][index+1]) if project not in errors
else '{}\n\tInvalid time: {}'.format(errors[project], dayEntry[project][index+1]))
dayHours[project] = 0.0
break
dayHours[project] += calcWorkTime(dayEntry[project][index], dayEntry[project][index+1])
index += 2
dayHours['total'] += dayHours[project]
return (dayHours, errors)
def calculateWeek(weekEntry, projects, daysOfTheWeek):
weekHours = {'total': 0.0}
for project in projects:
weekHours[project] = 0.0
for day in daysOfTheWeek:
if day not in weekEntry or not weekEntry[day]:
continue
weekHours['total'] += weekEntry[day]['total']
for project in projects:
if project not in weekEntry[day] or not weekEntry[day][project]:
continue
weekHours[project] += weekEntry[day][project]
return weekHours
def printDaysHours(day, dayHours, timeFormat, projects, errors):
floatTotalHours = round(dayHours['total'], 3)
intTotalHours = int(floatTotalHours)
intTotalMinutes = int((floatTotalHours - intTotalHours)*60)
dayHoursOutput = ''
if timeFormat == 'HH.hhh':
dayHoursOutput = '{}\n{}: {} hours'.format(dayHoursOutput, day, floatTotalHours)
elif timeFormat == 'HH:mm':
dayHoursOutput = '{}\n{}: {} hours {} minutes'.format(dayHoursOutput, day, intTotalHours, intTotalMinutes)
else:
dayHoursOutput = (
'{}\n{}: {} hours {} minutes({} hours)'.format(dayHoursOutput, day, intTotalHours, intTotalMinutes, floatTotalHours)
)
for project in projects:
if project not in dayHours:
if project in errors:
dayHoursOutput = '{}\n\t{}'.format(dayHoursOutput, errors[project])
continue # pragma: no cover because https://bitbucket.org/ned/coveragepy/issues/198/continue-marked-as-not-covered
floatHours = round(dayHours[project], 3)
intHours = int(floatHours)
intMinutes = int((floatHours - intHours)*60)
if project in errors:
dayHoursOutput = '{}\n\t{}'.format(dayHoursOutput, errors[project])
if timeFormat == 'HH.hhh':
dayHoursOutput = '{}\n\t{}: {} hours'.format(dayHoursOutput, project, floatHours)
elif timeFormat == 'HH:mm':
dayHoursOutput = '{}\n\t{}: {} hours {} minutes'.format(dayHoursOutput, project, intHours, intMinutes)
else:
dayHoursOutput = (
'{}\n\t{}: {} hours {} minutes({} hours)'.format(dayHoursOutput, project, intHours, intMinutes, floatHours)
)
return dayHoursOutput
def printWeekHours(weekHours, timeFormat, projects):
floatTotalHours = round(weekHours['total'], 3)
intTotalHours = int(floatTotalHours)
intTotalMinutes = int((floatTotalHours - intTotalHours)*60)
weekHoursOutput = ''
if timeFormat == 'HH.hhh':
weekHoursOutput = '{}\nTotal hours for the week: {} hours'.format(weekHoursOutput, floatTotalHours)
elif timeFormat == 'HH:mm':
weekHoursOutput = (
'{}\nTotal hours for the week: {} hours {} minutes'.format(weekHoursOutput, intTotalHours, intTotalMinutes)
)
else:
weekHoursOutput = '{}\nTotal hours for the week: {} hours {} minutes({} hours)'.format(
weekHoursOutput, intTotalHours, intTotalMinutes, floatTotalHours
)
for project in projects:
if project not in weekHours or not weekHours[project]:
continue # pragma: no cover because https://bitbucket.org/ned/coveragepy/issues/198/continue-marked-as-not-covered
floatHours = round(weekHours[project], 3)
intHours = int(floatHours)
intMinutes = int((floatHours - intHours)*60)
if timeFormat == 'HH.hhh':
weekHoursOutput = '{}\n\t{}: {} hours'.format(weekHoursOutput, project, floatHours)
elif timeFormat == 'HH:mm':
weekHoursOutput = '{}\n\t{}: {} hours {} minutes'.format(weekHoursOutput, project, intHours, intMinutes)
else:
weekHoursOutput = (
'{}\n\t{}: {} hours {} minutes({} hours)'.format(weekHoursOutput, project, intHours, intMinutes, floatHours)
)
return weekHoursOutput
def validDay(day):
return len(day) % 2 == 0
def validTime(time):
if time is None:
return False
pattern = re.compile('^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$')
return pattern.match(time)
def main(config, timeFormat):
daysOfTheWeek = ['saturday', 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday']
projects = config['day']['projects']
week = {'total': 0.0}
punchCardOutput = '{}{}'.format(config['title'], '\n')
for day in daysOfTheWeek:
if day not in config['day'] or not config['day'][day]:
punchCardOutput = (
'{}{}'.format(punchCardOutput, printDaysHours(day.capitalize(), {'total': 0.0}, timeFormat, projects, ''))
)
continue
dayHours = calculateDay(config['day'][day], projects)
punchCardOutput = (
'{}{}'.format(punchCardOutput, printDaysHours(day.capitalize(), dayHours[0], timeFormat, projects, dayHours[1]))
)
week[day] = dayHours[0]
weekHours = calculateWeek(week, projects, daysOfTheWeek)
return '{}{}{}'.format(punchCardOutput, '\n', printWeekHours(weekHours, timeFormat, projects))
if __name__ == '__main__': # pragma: no cover
description = 'This is a simple program meant to calculate my work hours.'
parser = argparse.ArgumentParser(description=description)
group = parser.add_mutually_exclusive_group()
group.add_argument('-H', '--hours', action='store_true', help='Outputs hours only as a decimal')
group.add_argument('-m', '--minutes', action='store_true', help='Outputs hours and minutes')
parser.add_argument('file', nargs='?', type=argparse.FileType('r'), default='-',
help='The file to be read or if no file given read stdin')
args = parser.parse_args()
if args.file.name == '<stdin>':
config = toml.loads(sys.stdin.read())
else:
with open(args.file.name, 'r') as confFile:
config = toml.loads(confFile.read())
if args.hours:
timeFormat = 'HH.hhh'
elif args.minutes:
timeFormat = 'HH:mm'
else:
timeFormat = None
print(main(config, timeFormat))