I've seen about 30 similar posts to this, but nothing really doing exactly what I'm looking for and some which just don't work..
I'm trying to return a list of N business dates, to then iterate through a dictionary and pull data out according to the corresponding dates.
Assuming the current date is:
refreshed = str(data['Meta Data']['3. Last Refreshed'])
For completion, the value of above right now is:2020-1-30
I want to be able to calculate n days prior to this date..
I don't really want to import a bunch of funky modules, and have tried a function using a loop and datetime.date.isoweekday() - but I always come across an issue when passing refreshed in.
One of the main issues I'm seeing with some of the examples elsewhere is where the examples are calculating the dates from datetime.date.today() - seemingly it's fine to pass that to isoweekday() but I can't pass refreshed to isoweekday() to calculate it's 0-6 reference. I've tried using strfrtime() to reformat the date into a suitable format for isoweekday but to no avail.
Subtracting days from a date
You can subtract 30 days from a datetime.datetime object by subtracting a datetime.timedelta object:
>>> import datetime
>>> datetime.datetime.today()
datetime.datetime(2020, 10, 31, 10, 20, 0, 704133)
>>> datetime.datetime.today() - datetime.timedelta(30)
datetime.datetime(2020, 10, 1, 10, 19, 49, 680385)
>>> datetime.datetime.strptime('2020-01-30', '%Y-%m-%d') - datetime.timedelta(30)
datetime.datetime(2019, 12, 31, 0, 0)
Skipping week-ends by subtracting 7 days instead of 5
We are starting from date d and you want to subtract N=30 non-week-end days. A general way could be:
Figure out which day of the week is d;
Figure out how many week-ends there are between d and d-N;
Remove the appropriate number of days.
However, you want to subtract 30 days, and 30 is a multiple of 5. This makes things particularly easy: when you subtract 5 days from a date, you are guaranteed to encounter exactly one week-end in those five days. So you can immediately remove 7 days instead of 5.
Removing 30 days is the same as removing 6 times 5 days. So you can remove 6 times 7 days instead, which is achieved by subtracting datetime.timedelta(42) from your date.
Note: this accounts for week-ends, but not for special holidays.
Skipping week-ends iteratively
You can test for days of the week using .weekday(). This is already answered on this other question: Loop through dates except for week-ends
You can add N days using a timedelta:
data['Meta Data']['3. Last Refreshed'] = pd.to_datetime(data['Meta Data']['3. Last Refreshed']) + pd.to_timedelta(4, unit="D")
Replace 4 with your n days.
Related
so I am a beginner with python and have been working with the datetime, time, and timedelta libraries a little bit. I am trying to create a piece of code that gives me the date approximately two months ago(exact_two_months_date) from today (whatever today happens to be). The catch is, I want to find that date approx. two months ago AND begin the actual start_date on the Monday of that week. So in theory, the actual start date will not be exactly two months ago. It will be the week beginning on Monday two months ago from today.
Example pseudocode:
today = '20150425' ## '%Y%m%d' ... Saturday
exact_two_months_date = '20150225' ## EXACTLY two months ago ... Wednesday
start_date = '20150223' ## this is the Monday of that week two months ago
So how do I find the 'start_date' above? If the day exactly two months ago begins on a Saturday or Sunday, then I would just want to go to the next Monday. Hope this is clear and makes sense... Once I find the start date, I would like to increment day by day(only week days) up to 'today'.
Appreciate any feedback, thanks.
Calculating with dates using python-dateutil
If a dependency on a third-party package is an option, then ☞ python-dateutil provides a convenient method to calculate with dates.
Browse the docs for ☞ relativedelta to see the wealth of supported parameters. The more calculations a package needs to do with dates, the more a helper module like dateutil justifies its dependency. For more inspiration on what it has to offer see the ☞ examples page.
Quick run-through:
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> today = datetime.date.today()
>>> two_m_ago = today - relativedelta(months=2)
>>> # print two_m_ago ~> datetime.date(2015, 2, 25)
>>> monday = two_m_ago - datetime.timedelta(days=two_m_ago.weekday())
>>> # print monday ~> datetime.date(2015, 2, 23)
Getting the Monday with weekday()
Once we have the date from two months ago in the variable two_m_ago, we subtract the index of the weekday() from it. This index is 0 for Monday and goes all the way to 6 for Sunday. If two_m_ago already is a Monday, then subtracting by 0 will not cause any changes.
Does something like this work for you?
import datetime
today = datetime.date.today()
delta = datetime.timedelta(days=60) # ~ 2 months
thatDay = today - delta
# subtract weekdays to get monday
thatMonday = thatDay - datetime.timedelta(days=thatDay.weekday())
Honestly, I find working with datetimes to be the hardest thing I ever have to regularly do and I make a lot of mistakes, so I'm going to work through this one and show some of the failures I regularly have with it. Here goes.
Two constraints: 1) Date two months ago, 2) Monday of that week
Date Two Months Ago
Okay, so Python's datetime library has a useful method called replace, which seems like it might help here:
>>> import datetime
>>> now = datetime.date.today()
>>> today
datetime.date(2015, 4, 25)
>>> today.month
4
>>> two_months_ago = today.replace(month=today.month-2)
>>> two_months_ago
datetime.date(2015, 2, 25)
>>> two_months_ago.month
2
But wait: what about negative numbers? That won't work:
>>> older = datetime.date(2015, 01, 01)
>>> older.replace(month=older.month-2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: month must be in 1..12
So there are two solutions:
1) I can build a 1-12 range that cycles forwards or back, or
2) To find two months previous, I can merely replace the day part of my date with the 1st day of the month I'm in and then go back 1 day to the previous month and then replace that day in the previous month with the day I want.
(If you think about it, you'll find that either of these may present bugs if I land on day 31 in a month with fewer days than that, for instance. This is part of what makes datetimes difficult.)
def previous_month(date):
current_day = date.day
first_day = date.replace(day=1)
last_day_prev_month = first_day - datetime.timedelta(days=1)
prev_month_day = last_day_prev_month.replace(day=current_day)
return prev_month_day
>>> today = datetime.date.today()
>>> older = previous_month(today)
>>> older
datetime.date(2015, 3, 25)
Well, let's say we're getting close, though, and we need to include some error-checking to make sure the day we want is a valid date inside the month we land in. Ultimately, the problem is that "two months ago" means a lot more than we think it means when we say it out loud.
Next, we'll take a crack at problem number two: How to get to the Monday of that week?
Well, datetime objects have a weekday method, so this part shouldn't be too hard and here's a nice SO answer on how to do that.
Simple version is: use the difference in weekday integers to figure out how many days to go back and do that using datetime.timedelta(days=days_difference).
Takeaway: Working with datetimes can be tough.
Date manipulation in Python is horribly convoluted. You will save a lot of time by using the arrow package which greatly simplifies these operations.
First install it
pip install arrow
Now your question:
import arrow
# get local current time
now = arrow.now('local')
# move 2 months back
old = now.replace(months=-2)
# what day of the week was that?
dow = old.isoweekday()
# reset old to Monday, for instance at 9:32 in the morning (this is just an example, just to show case)
old = old.replace(days=-dow, hour=9, minute=32, second=0)
print('now is {now}, we went back to {old}'.format(now=now.isoformat(), old=old.isoformat()))
The output:
now is 2015-04-25T20:37:38.174000+02:00, we went back to 2015-02-22T09:32:00.174000+01:00
Note that the various formats, timezones etc. are now transparent and you just need to rely on one package.
This question already has answers here:
What's the best way to find the inverse of datetime.isocalendar()?
(8 answers)
Closed 8 years ago.
I'm writing a script where the user needs to input a week number and a procedure will be ran based on that. However, I ran into a small issue, I know I can get week numbers via something like this:
>>> a=datetime.datetime.now()
>>> a
datetime.datetime(2015, 1, 22, 15, 51, 57, 820058)
>>> a.isocalendar()[1]
4
But I can't find how to do it backwards. Also, the date I require has to be Sunday of that week at 6:00am. Once I have that datetime element I can just do
begin_date = datetime.datetime.strptime(a, "%Y-%m-%d %H:%M:%S")
To get the format I want. I'm still missing the step to get the date. Any thoughts?
We create an initial datetime of for 2015 (2014-12-28 6:00:00 --1st sunday of 1st week), and a timedelta object of 7 days:
w0 = datetime.datetime(2014,12,28,6)
d7 = datetime.timedelta(7)
Then you can simply add multiples of the timedelta object to the initial date like so (n would be your week number):
w0+(d7*n)
>>> now = datetime.datetime.now()
>>> start = datetime.datetime(2015, 1,1)
>>> (now - start).days // 7
3
I have to write numDays(otherdate) function that returns the number of days as a positive integer between this date and the OtherDate.
This function belongs to class Date ADT that initialized to the given Gregorian Date. We have an Gregorian date in self._date(month,day,year) format. It must return difference between two dates.like 25 september and 6 october = it has 12 days difference
def numDays(self,otherDate):
print("Enter a date:")
y=int(input("Year:"))
m=int(input("Month:"))
d=int(input("Day:"))
today=self._date(y,m,d)
diff=today-otherDate
NoOfDays=diff.days
print("The Number of day between today and Julian date is %s"%NoOfDays)
wrote like that but I know there are some mistakes.
The problem is, that you would also have to provide the country and for parts of europe even the state for which you calculate the difference. See wiki at http://en.wikipedia.org/wiki/Gregorian_calendar for details.
Although there is a general formula (which should be very easy to find), I don't think that this is accurate.
On the other side: Who cares. People who could have all passed away long time ago ;-)
If both dates are datetime.date objects then to calculate number of days between today and the other date:
from datetime import date
number_of_days = date.today().toordinal() - other_date.toordinal()
It doesn't take into account timezone-related issues and it assumes that you wrote "Julian date" by mistake.
btw, there are 11 days between 25 Sep and 6 Oct:
>>> date(2012, 10, 6).toordinal() - date(2012, 9, 25).toordinal()
11
>>> date(2012, 9, 26).toordinal() - date(2012, 9, 25).toordinal()
1
>>> date(2012, 9, 25).toordinal() - date(2012, 9, 25).toordinal()
0
To do it without datetime module you need to perform some simple arithmetic. The calendar repeats itself every 400 years, see the algorithm.
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.
Update
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.
Given a date range how to calculate the number of weekends partially or wholly within that range?
(A few definitions as requested:
take 'weekend' to mean Saturday and Sunday.
The date range is inclusive i.e. the end date is part of the range
'wholly or partially' means that any part of the weekend falling within the date range means the whole weekend is counted.)
To simplify I imagine you only actually need to know the duration and what day of the week the initial day is...
I darn well now it's going to involve doing integer division by 7 and some logic to add 1 depending on the remainder but I can't quite work out what...
extra points for answers in Python ;-)
Edit
Here's my final code.
Weekends are Friday and Saturday (as we are counting nights stayed) and days are 0-indexed starting from Monday. I used onebyone's algorithm and Tom's code layout. Thanks a lot folks.
def calc_weekends(start_day, duration):
days_until_weekend = [5, 4, 3, 2, 1, 1, 6]
adjusted_duration = duration - days_until_weekend[start_day]
if adjusted_duration < 0:
weekends = 0
else:
weekends = (adjusted_duration/7)+1
if start_day == 5 and duration % 7 == 0: #Saturday to Saturday is an exception
weekends += 1
return weekends
if __name__ == "__main__":
days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
for start_day in range(0,7):
for duration in range(1,16):
print "%s to %s (%s days): %s weekends" % (days[start_day], days[(start_day+duration) % 7], duration, calc_weekends(start_day, duration))
print
General approach for this kind of thing:
For each day of the week, figure out how many days are required before a period starting on that day "contains a weekend". For instance, if "contains a weekend" means "contains both the Saturday and the Sunday", then we have the following table:
Sunday: 8
Monday: 7
Tuesday: 6
Wednesday: 5
Thursday: 4
Friday: 3
Saturday: 2
For "partially or wholly", we have:
Sunday: 1
Monday: 6
Tuesday: 5
Wednesday: 4
Thursday: 3
Friday: 2
Saturday: 1
Obviously this doesn't have to be coded as a table, now that it's obvious what it looks like.
Then, given the day-of-week of the start of your period, subtract[*] the magic value from the length of the period in days (probably start-end+1, to include both fenceposts). If the result is less than 0, it contains 0 weekends. If it is equal to or greater than 0, then it contains (at least) 1 weekend.
Then you have to deal with the remaining days. In the first case this is easy, one extra weekend per full 7 days. This is also true in the second case for every starting day except Sunday, which only requires 6 more days to include another weekend. So in the second case for periods starting on Sunday you could count 1 weekend at the start of the period, then subtract 1 from the length and recalculate from Monday.
More generally, what's happening here for "whole or part" weekends is that we're checking to see whether we start midway through the interesting bit (the "weekend"). If so, we can either:
1) Count one, move the start date to the end of the interesting bit, and recalculate.
2) Move the start date back to the beginning of the interesting bit, and recalculate.
In the case of weekends, there's only one special case which starts midway, so (1) looks good. But if you were getting the date as a date+time in seconds rather than day, or if you were interested in 5-day working weeks rather than 2-day weekends, then (2) might be simpler to understand.
[*] Unless you're using unsigned types, of course.
My general approach for this sort of thing: don't start messing around trying to reimplement your own date logic - it's hard, ie. you'll screw it up for the edge cases and look bad. Hint: if you have mod 7 arithmetic anywhere in your program, or are treating dates as integers anywhere in your program: you fail. If I saw the "accepted solution" anywhere in (or even near) my codebase, someone would need to start over. It beggars the imagination that anyone who considers themselves a programmer would vote that answer up.
Instead, use the built in date/time logic that comes with Python:
First, get a list of all of the days that you're interested in:
from datetime import date, timedelta
FRI = 5; SAT = 6
# a couple of random test dates
now = date.today()
start_date = now - timedelta(57)
end_date = now - timedelta(13)
print start_date, '...', end_date # debug
days = [date.fromordinal(d) for d in
range( start_date.toordinal(),
end_date.toordinal()+1 )]
Next, filter down to just the days which are weekends. In your case you're interested in Friday and Saturday nights, which are 5 and 6. (Notice how I'm not trying to roll this part into the previous list comprehension, since that'd be hard to verify as correct).
weekend_days = [d for d in days if d.weekday() in (FRI,SAT)]
for day in weekend_days: # debug
print day, day.weekday() # debug
Finally, you want to figure out how many weekends are in your list. This is the tricky part, but there are really only four cases to consider, one for each end for either Friday or Saturday. Concrete examples help make it clearer, plus this is really the sort of thing you want documented in your code:
num_weekends = len(weekend_days) // 2
# if we start on Friday and end on Saturday we're ok,
# otherwise add one weekend
#
# F,S|F,S|F,S ==3 and 3we, +0
# F,S|F,S|F ==2 but 3we, +1
# S|F,S|F,S ==2 but 3we, +1
# S|F,S|F ==2 but 3we, +1
ends = (weekend_days[0].weekday(), weekend_days[-1].weekday())
if ends != (FRI, SAT):
num_weekends += 1
print num_weekends # your answer
Shorter, clearer and easier to understand means that you can have more confidence in your code, and can get on with more interesting problems.
To count whole weekends, just adjust the number of days so that you start on a Monday, then divide by seven. (Note that if the start day is a weekday, add days to move to the previous Monday, and if it is on a weekend, subtract days to move to the next Monday since you already missed this weekend.)
days = {"Saturday":-2, "Sunday":-1, "Monday":0, "Tuesday":1, "Wednesday":2, "Thursday":3, "Friday":4}
def n_full_weekends(n_days, start_day):
n_days += days[start_day]
if n_days <= 0:
n_weekends = 0
else:
n_weekends = n_days//7
return n_weekends
if __name__ == "__main__":
tests = [("Tuesday", 10, 1), ("Monday", 7, 1), ("Wednesday", 21, 3), ("Saturday", 1, 0), ("Friday", 1, 0),
("Friday", 3, 1), ("Wednesday", 3, 0), ("Sunday", 8, 1), ("Sunday", 21, 2)]
for start_day, n_days, expected in tests:
print start_day, n_days, expected, n_full_weekends(n_days, start_day)
If you want to know partial weekends (or weeks), just look at the fractional part of the division by seven.
You would need external logic beside raw math. You need to have a calendar library (or if you have a decent amount of time implement it yourself) to define what a weekend, what day of the week you start on, end on, etc.
Take a look at Python's calendar class.
Without a logical definition of days in your code, a pure mathematical methods would fail on corner case, like a interval of 1 day or, I believe, anything lower then a full week (or lower then 6 days if you allowed partials).