forked from noahvanhoucke/HelpingSantasHelpers
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhours.py
94 lines (83 loc) · 4.68 KB
/
hours.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
import datetime
class Hours:
""" Hours class takes care of time accounting. Note that convention is
9:00-9:05 is work taking place on the 00, 01, 02, 03, 04 minutes, so 5 minutes of work.
Elf is available for next work at 9:05
Class members day_start, day_end are in minutes relative to the day
"""
def __init__(self):
self.hours_per_day = 10 # 10 hour day: 9 - 19
self.day_start = 9 * 60
self.day_end = (9 + self.hours_per_day) * 60
self.reference_start_time = datetime.datetime(2014, 1, 1, 0, 0)
self.minutes_in_24h = 24 * 60
@staticmethod
def convert_to_minute(arrival):
""" Converts the arrival time string to minutes since the reference start time,
Jan 1, 2014 at 00:00 (aka, midnight Dec 31, 2013)
:param arrival: string in format '2014 12 17 7 03' for Dec 17, 2014 at 7:03 am
:return: integer (minutes since arrival time)
"""
time = arrival.split(' ')
dd = datetime.datetime(int(time[0]), int(time[1]), int(time[2]), int(time[3]), int(time[4]))
age = dd - datetime.datetime(2014, 1, 1, 0, 0)
return int(age.total_seconds() / 60)
def is_sanctioned_time(self, minute):
""" Return boolean True or False if a given time (in minutes) is a sanctioned working day minute. """
return ((minute - self.day_start) % self.minutes_in_24h) < (self.hours_per_day * 60)
def get_sanctioned_breakdown(self, start_minute, duration):
""" Whole days (24-hr time periods) contribute fixed quantities of sanctioned and unsanctioned time. After
accounting for the whole days in the duration, the remainder minutes are tabulated as un/sanctioned.
:param start_minute:
:param duration:
:return:
"""
full_days = duration / (self.minutes_in_24h)
sanctioned = full_days * self.hours_per_day * 60
unsanctioned = full_days * (24 - self.hours_per_day) * 60
remainder_start = start_minute + full_days * self.minutes_in_24h
for minute in xrange(remainder_start, start_minute+duration):
if self.is_sanctioned_time(minute):
sanctioned += 1
else:
unsanctioned += 1
return sanctioned, unsanctioned
def next_sanctioned_minute(self, minute):
""" Given a minute, finds the next sanctioned minute.
:param minute: integer representing a minute since reference time
:return: next sanctioned minute
"""
# next minute is a sanctioned minute
if self.is_sanctioned_time(minute) and self.is_sanctioned_time(minute+1):
return minute + 1
num_days = minute / self.minutes_in_24h
return self.day_start + (num_days + 1) * self.minutes_in_24h
def apply_resting_period(self, start, num_unsanctioned):
""" Enforces the rest period and returns the minute when the elf is next available for work.
Rest period is applied during sanctioned hours, so 5 hours rest period requires 5 hours sanctioned.
As a result, rest periods will end during the middle of sanctioned hours or at exactly the end of
sanctioned hours. If the rest period ends exactly when the rest period ends, this returns the next
morning at 9:00 am as the next available minute.
:param start: minute the REST period starts
:param num_unsanctioned: always > 0 number of unsanctioned minutes that need resting minutes
:return: next available minute after rest period has been applied. If rest period ends with the working day
returns the next morning at 9:00 am
"""
num_days_since_jan1 = start / self.minutes_in_24h
rest_time = num_unsanctioned
rest_time_in_working_days = rest_time / (60 * self.hours_per_day)
rest_time_remaining_minutes = rest_time % (60 * self.hours_per_day)
# rest time is only applied to sanctioned work hours. If local_start is at an unsanctioned time,
# need to set it to be the next start of day
local_start = start % self.minutes_in_24h # minute of the day (relative to a current day) the work starts
if local_start < self.day_start:
local_start = self.day_start
elif local_start > self.day_end:
num_days_since_jan1 += 1
local_start = self.day_start
if local_start + rest_time_remaining_minutes > self.day_end:
rest_time_in_working_days += 1
rest_time_remaining_minutes -= (self.day_end - local_start)
local_start = self.day_start
total_days = num_days_since_jan1 + rest_time_in_working_days
return total_days * self.minutes_in_24h + local_start + rest_time_remaining_minutes