def check_for_completeness(days, actual_work_days): okay = True for d in actual_work_days: if d >= datetime.datetime.today().date(): break if d not in days: log.warn(f"Workday {dt.strftime(d, '%d.%m.%Y')} has no entry") okay = False return okay
def check_weekends(days, weekends): okay = True for d in days: at_work_entries = [ e.name not in ["Vacations", "Sick"] for e in days[d] ] if d.weekday() + 1 in weekends and any(at_work_entries): log.warn( f"Seems like {dt.strftime(d, '%d.%m.%Y')} is set as a weekend day. Were you really working then?" ) log.info(f"Entry: {days[d]}") okay = False return okay
def wrapper(*args, **kwargs): m = f"Check: {msg}" log.info(mk_headline(m, ">", indent=5)) okay = func(*args, **kwargs) if not okay: log.warn( mk_headline( f"{Colour.RED}{Colour.BOLD}Not OK!{Colour.END}")) else: log.info( mk_headline(f"{Colour.GREEN}{Colour.BOLD}OK!{Colour.END}")) log.info(mk_headline(sgn="<")) log.info("") return okay
def calculate_percents(self): for ws in self.ws: ws_obj = Workspace(ws) log.info(mk_headline(f"Times in Workspace {ws}", "*")) # seconds = {} self.total_time = tdelta(0) self.project_seconds = { "Vacations": 0.0, "Courses": 0.0, "Sick": 0.0 } for project in ws_obj.native_projects: p_name = project.name times = project.time_entries.list() for i in times: if not hasattr(i, "stop"): log.warn("Entry %s seems to still be running" % i.description) continue start = i.start.replace(tzinfo=pytz.timezone("UTC")) end = i.stop.replace(tzinfo=pytz.timezone("UTC")) if start.date() < self.start or end.date() > self.end: continue dur = end - start if dur.total_seconds() / 3600. > 11: log.warn("Warning: the entry seems to be too long:") log.warn( f"{p_name} from {start} to {end}; duration {dur}") if start.date() not in self.days: self.days[start.date()] = StrictList(Entry) if p_name not in self.project_seconds: self.project_seconds[p_name] = 0.0 e = None tags = list(set([t.lower() for t in i.tags])) if hasattr( i, "tags") else [] e = Entry(p_name, start, end, dur, tags) add_dur = not (p_name == "Holidays" or (hasattr(i, "tags") and "Pause" in i.tags)) if add_dur: self.project_seconds[p_name] += dur.total_seconds() self.days[start.date()].append(e)
def check_for_gaps_and_overlaps(days): okay = True for d in days: items = sorted(days[d], key=lambda x: x.start) for i, first in enumerate(items[:-1]): second = items[i + 1] if first.start.date() != first.end.date(): log.warn(f" [step] {first.name:<10s} overlaps midnight") if second.start.date() != second.end.date(): log.warn(f" [step] {second.name:<10s} overlaps midnight") f_end_str = dt.strftime( first.end.astimezone(pytz.timezone("Europe/Berlin")), "%H:%M:%S") s_start_str = dt.strftime( second.start.astimezone(pytz.timezone("Europe/Berlin")), "%H:%M:%S") diff = second.start - first.end if diff.total_seconds() >= gap_threshold_seconds: stat = "gap" okay = False elif diff.total_seconds() <= -gap_threshold_seconds: stat = "ovl" okay = False else: stat = None if stat: log.warn( f" [{stat}] {abs(diff.total_seconds()):>8.0f}s; {first.name:10s} and {second.name:10s} on {first.start.date()}: {f_end_str} -> {s_start_str}" ) return okay
def check_for_expected_hours(days, get_working_hours_func): days_sorted = sorted(days.keys()) total_hours = 0.0 overunder_sum = 0.0 for d in days_sorted: pause_hours = 0.0 expected_day_hours = get_working_hours_func(d) actual_day_hours = 0.0 special_day_type = None for e in days[d]: # type: Entry if "pause" in e.tags: pause_hours += e.duration.total_seconds() / 3600.0 continue if "off" in e.tags: special_day_type = e.name entry_time = e.duration entry_hours = entry_time.total_seconds() / 3600.0 actual_day_hours += entry_hours total_hours += actual_day_hours overunder = actual_day_hours - expected_day_hours if overunder < 0.0: p = pause_hours overunder += p pause_hours -= p overunder_sum += overunder c = Colour.RED if overunder < 0.0 else Colour.BLUE overunder_str = f"{Colour.BOLD}{c}{overunder:>+5.2f}{Colour.END}" c = Colour.RED if pause_hours < 1.0 else Colour.BLUE pause_str = f"{Colour.BOLD}{c}{pause_hours:>5.2f}{Colour.END}" result = [dt.strftime(d, '%d.%m.%Y')] if special_day_type is None: result.append(f"{actual_day_hours:>5.2f}h") result.append(f"breaks: {pause_str}h => +/- {overunder_str}h") else: if special_day_type == "Vacations" or special_day_type == "Holidays": result.append( f"{Colour.GREEN}{Colour.BOLD}is a day off{Colour.END}") elif special_day_type == "Sick": result.append( f"{Colour.YELLOW}{Colour.BOLD}Hope you got well!{Colour.END}" ) if pause_hours < 1.0 and not special_day_type: result.append( f"{Colour.BOLD}You need sufficient breaks!{Colour.END}") log.info("; ".join(result)) log.info(mk_headline("Sum of daily hours")) log.info(f" Sum: {total_hours:>6.1f}h") log.info(f" +/- {overunder_sum:>6.1f}h") log.info(mk_headline("So...")) if overunder_sum < 0.0: log.warn(" You have a negative time record in the given period!") elif overunder_sum > 0.0: log.info(" You have worked overtime in the given period!") return overunder_sum >= 0.0