"Passing Go" in a (python) date range
Updated to remove extraneous text and ambiguity.
The Rules:
An employee accrues 8 hours of Paid Time Off on the day after each quarter. Quarters, specifically being:- Jan 1 - Mar 31
- Apr 1 - Jun 30
- Jul 1 - Sep 30
- Oct 1 - Dec 31
The Problem
Using python, I need to define the guts of the following function:def acrued_hours_between(start_date, end_date):
# stuff
return integer
I'm currently using Python, and wondering what the correct approach to something like this would be.
I'm assuming that using DateTime objects, and possibly the dateutil module, would help here, but my brain isn't wrapping around this problem for some reason.
Up开发者_开发百科date
I imagine the calculation being somewhat simple, as the problem is:"How many hours of Paid Time Off are accrued from start_date to end_date?" given the above "rules".
The OP's edit mentions the real underlying problem is:
"How many hours of Paid Time Off are accrued from X-date to Y-date?"
I agree, and I'd compute that in the most direct and straightforward way, e.g.:
import datetime
import itertools
accrual_months_days = (1,1), (4,1), (7,1), (10,1)
def accruals(begin_date, end_date, hours_per=8):
"""Vacation accrued between begin_date and end_date included."""
cur_year = begin_date.year - 1
result = 0
for m, d in itertools.cycle(accrual_months_days):
if m == 1: cur_year += 1
d = datetime.date(cur_year, m, d)
if d < begin_date: continue
if d > end_date: return result
result += hours_per
if __name__ == '__main__': # examples
print accruals(datetime.date(2010, 1, 12), datetime.date(2010, 9, 20))
print accruals(datetime.date(2010, 4, 20), datetime.date(2012, 12, 21))
print accruals(datetime.date(2010, 12, 21), datetime.date(2012, 4, 20))
A direct formula would of course be faster, but could be tricky to do it without bugs -- if nothing else, this "correct by inspection" example can serve to calibrate the faster one automatically, by checking that they agree over a large sample of date pairs (be sure to include in the latter all corner cases such as first and last days of quarters of course).
I would sort all the events for a particular employee in time order and simulate the events in that order checking that the available days of paid time off never falls below zero. A paid time off request is an event with a value -(number of hours). Jan 1st has an event with value +8 hours.
Every time a modification is made to the data, run the simulation again from the start.
The advantage of this method is that it will detect situations in which a new event is valid at that time but causes the number of free days to drop such that a later event which previously was valid now becomes invalid.
This could be optimized by storing intermediate results in a cache but since you will likely only have a few hundred events per employee this optimization probably won't be necessary.
This can be done with plain old integer math:
from datetime import date
def hours_accrued(start, end):
'''hours_accrued(date, date) -> int
Answers the question "How many hours of Paid Time Off
are accrued from X-date to Y-date?"
>>> hours_accrued(date(2010, 4, 20), date(2012, 12, 21))
80
>>> hours_accrued(date(2010, 12, 21), date(2012, 4, 20))
48
'''
return ( 4*(end.year - start.year)
+ ((end.month-1)/3 - (start.month-1)/3) ) * 8
I would count all free days before the date in question, then subtract the number of used days before then in order to come to a value for the maximum number of allowable days.
Set up a tuple for each date range (we'll call them quarters). In the tuple store the quarter (as a cardinal index, or as a begin date), the maximum accrued hours for a quarter, and the number of used hours in a quarter. You'll want to have a set of tuples that are sorted for this to work, so a plain list probably isn't your best option. A dictionary might be a better way to approach this, with the quarter as the key and the max/used entries returned in the tuple, as it can be "sorted".
(Note: I looked at the original explanation and rewrote my answer)
Get a copy of the set of all quarters for a given employee, sorted by the quarter's date. Iterate over each quarter summing the difference between the maximum per-quarter allotment of vacation time and the time "spent" on that quarter until you reach the quarter that the request date falls into. This gives accumulated time.
If accumulated time plus the time alloted for the requested quarter is not as much as the requested hours, fail immediately and reject the request. Otherwise, continue iterating up to the quarter of your quest.
If there is sufficient accumulated time, continue iterating over the copied set, computing the new available times on a per-quarter basis, starting with the left-over time from your initial calculation.
If any quarter has a computed time falling below zero, fail immediately and reject the request. Otherwise, continue until you run out of quarters.
If all quarters are computed, update the original set of data with the copy and grant the request.
精彩评论