Count number of sundays in current month - python

How can I get the numberof Sundays of the current month in Python?
Anyone got any idea about this?

This gives you the number of sundays in a current month as you wanted:
import calendar
from datetime import datetime
In [367]: len([1 for i in calendar.monthcalendar(datetime.now().year,
datetime.now().month) if i[6] != 0])
Out[367]: 4

I happened to need a solution for this, but was unsatisfactory with the solutions here, so I came up with my own:
import calendar
year = 2016
month = 3
day_to_count = calendar.SUNDAY
matrix = calendar.monthcalendar(year,month)
num_days = sum(1 for x in matrix if x[day_to_count] != 0)

I'd do it like this:
import datetime
today = datetime.date.today()
day = datetime.date(today.year, today.month, 1)
single_day = datetime.timedelta(days=1)
sundays = 0
while day.month == today.month:
if day.weekday() == 6:
sundays += 1
day += single_day
print 'Sundays:', sundays

My take: (saves having to worry about being in the right month etc...)
from calendar import weekday, monthrange, SUNDAY
y, m = 2012, 10
days = [weekday(y, m, d+1) for d in range(*monthrange(y, m))]
print days.count(SUNDAY)
Or, as #mgilson has pointed out, you can do away with the list-comp, and wrap it all up as a generator:
sum(1 for d in range(*monthrange(y,m)) if weekday(y,m,d+1)==SUNDAY)
And I suppose, you could throw in a:
from collections import Counter
days = Counter(weekday(y, m, d + 1) for d in range(*monthrange(y, m)))
print days[SUNDAY]

Another example using calendar and datetime:
import datetime
import calendar
today = datetime.date.today()
m = today.month
y = today.year
sum(1 for week in calendar.monthcalendar(y,m) if week[-1])
Perhaps a slightly faster way to do it would be:
first_day,month_len = monthrange(y,m)
date_of_first_sun = 1+6-first_day
print sum(1 for x in range(date_of_first_sun,month_len+1,7))

You can do this using ISO week numbers:
from datetime import date
bom = date.today().replace(day=1) # first day of current month
eom = (date(bom.year, 12, 31) if bom.month == 12 else
(bom.replace(month=bom.month + 1) - 1)) # last day of current month
_, b_week, _ = bom.isocalendar()
_, e_week, e_weekday = eom.isocalendar()
num_sundays = (e_week - b_week) + (1 if e_weekday == 7 else 0)
In general for a particular day of the week (1 = Monday, 7 = Sunday) the calculation is:
num_days = ((e_week - b_week) +
(-1 if b_weekday > day else 0) +
( 1 if e_weekday >= day else 0))

import calendar
MONTH = 10
sundays = 0
cal = calendar.Calendar()
for day in cal.itermonthdates(2012, MONTH):
if day.weekday() == 6 and day.month == MONTH:
sundays += 1
PAY ATTENTION:
Here are the Calendar.itermonthdates's docs:
Return an iterator for one month. The iterator will yield datetime.date
values and will always iterate through complete weeks, so it will yield
dates outside the specified month.
That's why day.month == MONTH is needed
If you want the weekdays to be in range 0-6, use day.weekday(),
if you want them to be in range 1-7, use day.isoweekday()

My solution.
The following was inspired by #Lingson's answer, but I think it does lesser loops.
import calendar
def get_number_of_weekdays(year: int, month: int) -> list:
main_calendar = calendar.monthcalendar(year, month)
number_of_weeks = len(main_calendar)
number_of_weekdays = []
for i in range(7):
number_of_weekday = number_of_weeks
if main_calendar[0][i] == 0:
number_of_weekday -= 1
if main_calendar[-1][i] == 0:
number_of_weekday -= 1
number_of_weekdays.append(number_of_weekday)
return sum(number_of_weekdays) # In my application I needed the number of each weekday, so you could return just the list to do that.

Related

Better way of incrementing a date month?

I have a date as an int like so for example: 201805 I'd like to have a way where I can give the start date and it'll return back the next date, in this example: 201806. Currently, I have this solution:
def incrementDate(startdate):
try:
newdate = dt.datetime.strptime(str(startdate + 1), "%Y%m")
return str(newdate.year) + newdate.strftime('%m')
except:
newdate = dt.datetime.strptime(str(startdate), "%Y%m")
return int(str(newdate.year + 1) + "01")
Is this a good way of going about this or is there a simple better way?
How about something using relativedelta:
Your first step will be to install the package dateutil:
pip install dateutil
Then you will be able to use it:
import datetime
from dateutil.relativedelta import relativedelta
print(datetime.datetime.strptime(str(201805), "%Y%m") + relativedelta(months=1))
Or a string:
print((datetime.datetime.strptime(str(201805), "%Y%m") + relativedelta(months=1)).strftime("%Y%m"))
Note: relativedelta comes from a separate package called 'dateutil' (here for the details). It includes notably a nice way to add month / days / year .. without creating a custom function.
You can use datetime.timedelta:
from datetime import datetime, timedelta
def incrementDate(startdate):
cur_date = datetime.strptime(str(startdate), '%Y%m')
next_date = cur_date + timedelta(days=32)
return datetime.strftime(next_date, '%Y%m')
print(incrementDate(201806)) # 201807
print(incrementDate(201812)) # 201901
Just for fun, if your input is really an integer that represents a 4 digit year folowed by a 2 digit month, then you can increment the month without any string or datetime conversions.
d = 201812
y, m = d // 100, d % 100
if m == 12:
y += 1
m = 1
else:
m +=1
d = y * 100 + m
print(d)
# 201806
The replace() method fron the datetime objects allows you to update a parameter from the datetime object:
import datetime
today = datetime.datetime.now()
current_month = today.month
year = today.year
# Check for december
if current_month < 12:
next_month = current_month + 1
else:
next_month = 1
year += 1
today= today.replace(month = next_month, year = year )
Reference
To put in in your function:
import datetime
def increment_date(startdate):
date = datetime.strptime(str(startdate), '%Y%m')
current_month = date .month
year = date .year
# Check for december
if current_month < 12:
next_month = current_month + 1
else:
next_month = 1
year += 1
return date .replace(month = next_month, year = year )

Determine current month variable that rolls over to next month after the 15th day- python 2.7

I use the current_month variable to query data but here's the catch- if the day in the month is later then the 15th, I want to set the current month to be the following month. So the current month for 4/16/2016 should be 5/1/2016. I've got code that works but it doesn't feel pythonic. Suggestions would be appreciated.
month = datetime.datetime.now().strftime("%m")
year = datetime.datetime.now().strftime("%Y")
day = datetime.datetime.now().strftime("%d")
#Check if day in the month is past 15th if so set current month
if int(day) > 15:
if int(month) < 9: # check if the month in 1-9 if so pad leading zero
x = int(month)+1
current_month = year+"-0"+str(x)+"-01"
if int(month) == 9: # check if the month in 1-9 if so pad leading zero
x = int(month)+1
current_month = year+"-"+str(x)+"-01"
elif int(month) == 12: # check if the month is Dec if so roll to the next year and set month to Jan
month = "01"
y = int(year)+1
current_month = str(y)+"-"+month+"-01"
else:
x = int(month)+1 # just add one to the month if months are 10 or 11
current_month = year+"-"+str(x)+"-01"
else:
current_month = year+"-"+month+"-01" #prior to the 15'th so just use normal year, month and day
# Get today's date/time
today = datetime.datetime.now()
# add 16 days if after the 15th
if today.day > 15:
today += datetime.timedelta(16)
# Format that date w/ day being 1
current_month = today.strftime("%Y-%m-01")
An alternative to Scott's approach (which works perfectly well and you should probably accept it) is to use a dateutil.relativedelta:
from datetime import datetime
from dateutil.relativedelta import relativedelta
def next_month_after_day(dt, trans_day=15):
rd = relativedelta(months=(dt.day > trans_day), day=1)
return dt + rd
dt1 = datetime(1996, 2, 3)
dt2 = datetime(1996, 2, 15)
dt3 = datetime(1996, 2, 16)
dt4 = datetime(1996, 12, 18)
fmt = "%Y-%m-%d"
for x in (dt1, dt2, dt3, dt4):
dt_before = x
dt_after = next_month_after_day(x)
print('{} -> {}'.format(dt_before.strftime(fmt), dt_after.strftime(fmt)))
The result is:
1996-02-03 -> 1996-02-01
1996-02-15 -> 1996-02-01
1996-02-16 -> 1996-03-01
1996-12-18 -> 1997-01-01
Thanks to Scott for pointing out why my original approach was needlessly complicated (and wrong).

Generate date ranges broken by month for a given period

I'm struggling with writing a pythonic, clean generator method that, given a date period, like ['2014-01-15', '2015-02-03], will give me this:
['2014-01-15', '2014-01-31']
['2014-02-01', '2014-02-28']
...
['2015-02-01', '2015-02-03']
This is what I came up with:
from datetime import datetime
import calendar
def genDatePeriods(startDate, endDate, format='%Y-%m-%d'):
dt1 = datetime.strptime(startDate, format)
dt2 = datetime.strptime(endDate, format)
for year in range(dt1.year, dt2.year + 1):
for month in range(1, 13):
day0 = dt1.day if month == dt1.month and year == dt1.year else 1
day1 = dt2.day if month == dt2.month and year == dt2.year else calendar.monthrange(year, month)[1]
if (year == dt1.year and month < dt1.month) or (year == dt2.year and month > dt2.month):
continue
else:
d0 = (year, month, day0)
d1 = (year, month, day1)
yield [datetime(*d).strftime(format) for d in [d0, d1]]
It works, however I feel like there is a more pythonic/tidy/efficient way to do this. Any ideas?
The following is much more concise, using datetime.date() objects to find the first day of the next month each time, until you reach the end date:
from datetime import datetime, timedelta
def genDatePeriods(startDate, endDate, format='%Y-%m-%d'):
curr = datetime.strptime(startDate, format).date()
end = datetime.strptime(endDate, format).date()
while curr <= end:
# first day of the next month, using modular arithmetic
next_month = curr.replace(
month=curr.month % 12 + 1, year=curr.year + curr.month // 12,
day=1)
curr_formatted = curr.strftime(format)
# end date is next month's first day, minus one day,
# or the given endDate, whichever comes first
end_formatted = min(next_month - timedelta(days=1), end).strftime(format)
yield [curr_formatted, end_formatted]
curr = next_month
Demo:
>>> for res in genDatePeriods('2014-01-15', '2015-02-03'):
... print res
...
['2014-01-15', '2014-01-31']
['2014-02-01', '2014-02-28']
['2014-03-01', '2014-03-31']
['2014-04-01', '2014-04-30']
['2014-05-01', '2014-05-31']
['2014-06-01', '2014-06-30']
['2014-07-01', '2014-07-31']
['2014-08-01', '2014-08-31']
['2014-09-01', '2014-09-30']
['2014-10-01', '2014-10-31']
['2014-11-01', '2014-11-30']
['2014-12-01', '2014-12-31']
['2015-01-01', '2015-01-31']
['2015-02-01', '2015-02-03']

How can I get the 3rd Friday of a month in Python?

I'm trying to get stock data from Yahoo! Finance using Python 2.7.9, but I only need data for the 3rd Friday of the month. I have a function to get the data, but need a way to get the dates. I want something like this:
def get_third_fris(how_many):
# code and stuff
return list_of_fris
So that calling get_third_fris(6) will return a 6-item-long list of 3rd Fridays following the current date. The dates need to be Unix timestamps.
(I have pretty much no experience with time or datetime, so please explain what your code is doing.)
Thanks!
You can use the calendar module to list weeks, then grab the Friday of that week.
import calendar
c = calendar.Calendar(firstweekday=calendar.SUNDAY)
year = 2015; month = 2
monthcal = c.monthdatescalendar(year,month)
third_friday = [day for week in monthcal for day in week if \
day.weekday() == calendar.FRIDAY and \
day.month == month][2]
You can format to Unix timestamp, but it's non-trivial. I'll refer you to this excellent answer which has info based on whether or not your date is timezone-aware.
We do not need to import anything other than datetime. We can assume 7 days in a week and weekday 0 == Monday.
import datetime
def third_friday(year, month):
"""Return datetime.date for monthly option expiration given year and
month
"""
# The 15th is the lowest third day in the month
third = datetime.date(year, month, 15)
# What day of the week is the 15th?
w = third.weekday()
# Friday is weekday 4
if w != 4:
# Replace just the day (of month)
third = third.replace(day=(15 + (4 - w) % 7))
return third
Assuming you want a range of every 3rd Friday, you can just use pandas, sample code:
import pandas as pd
pd.date_range('2017-12-02','2020-08-31',freq='WOM-3FRI')
Output:
DatetimeIndex(['2017-12-15', '2018-01-19', '2018-02-16', '2018-03-16',
'2018-04-20', '2018-05-18', '2018-06-15', '2018-07-20',
'2018-08-17', '2018-09-21', '2018-10-19', '2018-11-16',
'2018-12-21', '2019-01-18', '2019-02-15', '2019-03-15',
'2019-04-19', '2019-05-17', '2019-06-21', '2019-07-19',
'2019-08-16', '2019-09-20', '2019-10-18', '2019-11-15',
'2019-12-20', '2020-01-17', '2020-02-21', '2020-03-20',
'2020-04-17', '2020-05-15', '2020-06-19', '2020-07-17',
'2020-08-21'],
dtype='datetime64[ns]', freq='WOM-3FRI')
You can use standard python functions to find the third friday of this month:
from datetime import timedelta, date
import calendar
def next_third_friday(d):
""" Given a third friday find next third friday"""
d += timedelta(weeks=4)
return d if d.day >= 15 else d + timedelta(weeks=1)
def third_fridays(d, n):
"""Given a date, calculates n next third fridays"""
# Find closest friday to 15th of month
s = date(d.year, d.month, 15)
result = [s + timedelta(days=(calendar.FRIDAY - s.weekday()) % 7)]
# This month's third friday passed. Find next.
if result[0] < d:
result[0] = next_third_friday(result[0])
for i in range(n - 1):
result.append(next_third_friday(result[-1]))
return result
We can apply the above function to get the timestamps of the next fridays:
import time
def timestamp(d):
return int(time.mktime(d.timetuple()))
fridays = third_fridays(date.today(), 2)
print(fridays)
print(map(timestamp, fridays))
Output:
[datetime.date(2015, 3, 20), datetime.date(2015, 4, 17)]
[1426802400, 1429218000]
How about a more straightforward answer:
import calendar
c = calendar.Calendar(firstweekday=calendar.SATURDAY)
monthcal = c.monthdatescalendar(my_year, my_month)
monthly_expire_date = monthcal[2][-1]
I generalized #pourhaus answer to find the nth day of any month:
def nth_day_of_month(month, year, day_of_week, n):
first_possible_day = {1: 1, 2: 8, 3: 15, 4: 22, 5: 29}[n]
d = datetime.date(year, month, first_possible_day)
w = d.weekday()
if w != day_of_week:
d = d.replace(day=(first_possible_day + (day_of_week - w) % 7))
return d
its easy to use dateutil to get the next friday
import dateutil.parser as dparse
from datetime import timedelta
next_friday = dparse.parse("Friday")
one_week = timedelta(days=7)
friday_after_next = next_friday + one_week
last_friday = friday_after_next + one_week
this leverages the fact that there is always a week between fridays ... although Im not sure this answers your question it should at the very least provide you with a good starting point
Using dateutil.relativedelta:
from dateutil.relativedelta import relativedelta, FR # $ pip install python-dateutil
def third_friday_dateutil(now):
"""the 3rd Friday of the month, not the 3rd Friday after today."""
now = now.replace(day=1) # 1st day of the month
now += relativedelta(weeks=2, weekday=FR)
return now
Or using dateutil.rrule:
from datetime import date, timedelta
from dateutil.rrule import rrule, MONTHLY, FR
def third_friday_rrule(now):
return rrule(MONTHLY, count=1, byweekday=FR, bysetpos=3, dtstart=now.replace(day=1))[0]
def get_third_fris_rrule(how_many):
return list(rrule(MONTHLY, count=how_many, byweekday=FR, bysetpos=3, dtstart=date.today()+timedelta(1)))
Here's a brute force solution (15x times faster):
#!/usr/bin/env python
import calendar
from datetime import date, timedelta
from itertools import islice
DAY = timedelta(1)
WEEK = 7*DAY
def fridays(now):
while True:
if now.weekday() == calendar.FRIDAY:
while True:
yield now
now += WEEK
now += DAY
def next_month(now):
"""Return the first date that is in the next month."""
return (now.replace(day=15) + 20*DAY).replace(day=1)
def third_friday_brute_force(now):
"""the 3rd Friday of the month, not the 3rd Friday after today."""
return next(islice(fridays(now.replace(day=1)), 2, 3))
def get_third_fris(how_many):
result = []
now = date.today()
while len(result) < how_many:
fr = third_friday_brute_force(now)
if fr > now: # use only the 3rd Friday after today
result.append(fr)
now = next_month(now)
return result
print(get_third_fris(6))
Output
[datetime.date(2015, 3, 20),
datetime.date(2015, 4, 17),
datetime.date(2015, 5, 15),
datetime.date(2015, 6, 19),
datetime.date(2015, 7, 17),
datetime.date(2015, 8, 21)]
See Converting datetime.date to UTC timestamp in Python
Here's comparison with other solutions and tests (for all possible 400 years patterns).
I generalized my answer so that anyone can use it for any Nth weekday of a month and using minimal default libraries. My use was to find the DST (daylight savings time) dates for the year (2nd sunday in March & 1st sunday in November).
# Libraries:
from datetime import datetime
# Function:
def get_nth_day_of_month(year, month, Nth, weekday):
# Process is to find out what weekday the 1st of the month is
# And then go straight to the desired date by calculating it
first_of_month_weekday = datetime(year, month, 1).weekday()
day_desired = 7 * (Nth-1) + (weekday - first_of_month_weekday)
if day_desired < 1 : day_desired += 7 #correction for some 1st-weekday situations
return datetime(year, month, day_desired)
# Config:
year = 2022
month = 3 #DST starts in March
weekday = 6 #sunday
Nth = 2 #2nd sunday
dst_start = get_nth_day_of_month(year, month, Nth, weekday)
For my case, this generates the start of DST this year:
In [2]: dst_start
Out [2]: datetime.datetime(2022, 3, 13, 0, 0)
Then for the end of DST in 2022:
month = 11
Nth = 1
dst_end = get_nth_day_of_month(year, month, Nth, weekday)
The result is:
In[4]: dst_end
Out[4]: datetime.datetime(2022, 11, 5, 0, 0)
So in 2022, DST runs from 2022-03-13 to 2022-11-05.
Standard:
Days are numbered Monday = 0 to Sunday = 6
Pure python with no external libs.
Returns the expected day-of-month.
Note: Based on answer from #autonopy, but works.
from datetime import datetime
def get_nth_day_of_month(year, month, Nth, weekday):
first_of_month_weekday = datetime(year, month, 1).weekday()
# Find weekday offset from beginning of month
day_offset = (weekday - first_of_month_weekday) + 1
if day_offset < 1:
day_offset += 7 # correction for some 1st-weekday situations
# Add N weeks
return 7 * (Nth - 1) + day_offset
Tests:
>>> # first Monday of Nov 2021
>>> get_nth_day_of_month(2021, 11, 1, 0)
1
>>> # first Monday of January 2022
>>> get_nth_day_of_month(2022, 1, 1, 0)
3
>>> # first Monday of May 2022
>>> get_nth_day_of_month(2022, 5, 1, 0)
2
>>> # Mother's day 2022
>>> get_nth_day_of_month(2022, 5, 2, 0)
9
Assuming you use pandas:
def exp_friday(df):
mask = np.where((df.index.day > 14) &
(df.index.day < 22) &
(df.index.dayofweek == 4), True, False)
return df[mask]
This is a generic function to give you all the dates of a specific week in a list form.
def frecuencia_daymng(self, start_day, year, month, dayofweek):
"""dayofweek starts on MONDAY in 0 index"""
c = calendar.Calendar(firstweekday=start_day)
monthcal = c.monthdatescalendar(year, month)
ldates = []
for tdate in monthcal:
if tdate[dayofweek].month == month:
ldates.append(tdate[dayofweek])
return ldates
Lets say you want all the mondays of the 2020 10.
frecuencia_daymng(calendar.MONDAY, 2020, 10, 0)
This will give you the output.
[datetime.date(2020, 10, 5),
datetime.date(2020, 10, 12),
datetime.date(2020, 10, 19),
datetime.date(2020, 10, 26)]
So now you have the first, second ... etc monday of the month.
My suggestion is to start with the first day of the month, then find the closest Friday.
4 is represented as Friday from the datetime.weekday() method.
So we then subtract the weekday of the first of the month from 4(Friday)
If the result is negative the closest Friday found was the previous month, so we add 7 days, otherwise we already have the first Friday.
Then the result is as simple as adding another 14 days to get the third Friday and then add the timedelta representing the third Friday to the first day of the month.
from datetime import datetime, timedelta
def get_third_friday(year, month):
first_day_of_month = datetime(year, month, 1)
closest_friday = 4 - first_day_of_month.weekday()
if closest_friday < 0:
first_friday = closest_friday + 7
else:
first_friday = closest_friday
third_friday = first_friday + 14
return first_day_of_month + timedelta(third_friday)
Here's a solution where someone has figured it out already: the relativedelta module that's an extension from the Python dateutil package (pip install python-dateutil).
import datetime
from dateutil import relativedelta
def third_fridays(n):
first_of_this_month = datetime.date.today().replace(day=1)
return (
first_of_this_month
+ relativedelta.relativedelta(weekday=relativedelta.FR(3), months=i)
for i in range(n)
)
The key part here of course is the weekday=relativedelta.FR(3) which says exactly what's needed: the third Friday of the month. Here are the relevant part of the docs for the weekday parameter,
weekday:
One of the weekday instances (MO, TU, etc) available in the
relativedelta module. These instances may receive a parameter N,
specifying the Nth weekday, which could be positive or negative
(like MO(+1) or MO(-2)).
(For those new to Python return (...) is a generator expression which you can just treat as something to iterate over, e.g., for friday in third_fridays(18): print(friday))
from dateutil.relativedelta import *
from datetime import *
def find_mth_friday(your_date,m):
mth_friday = your_date + relativedelta(day=1, weekday=FR(m)) #sets day=1 in your_date and adds m fridays to it.
mth_friday_timestamp = int(mth_friday.strftime("%s")) #converting datetime to unix timestamp
return mth_friday_timestamp
def get_third_fris(n):
output_timestamps = []
today = datetime.now() #gets current system date
for i in range(1,n+1): #value of i varies from 1 to 6 if n=6
next_month = today + relativedelta(months=+i) #adds i months to current system date
third_friday = find_mth_friday(next_month,3) #finds third friday of the month using 'find_mth_friday()', the function we defined
output_timestamps.append(third_friday)
return output_timestamps
print(get_third_fris(6)) #let's try invoking our function with n=6 dates
This is what you wanted right?

Add n business days to a given date ignoring holidays and weekends in python

I'm trying to add n (integer) working days to a given date, the date addition has to avoid the holidays and weekends (it's not included in the working days)
Skipping weekends would be pretty easy doing something like this:
import datetime
def date_by_adding_business_days(from_date, add_days):
business_days_to_add = add_days
current_date = from_date
while business_days_to_add > 0:
current_date += datetime.timedelta(days=1)
weekday = current_date.weekday()
if weekday >= 5: # sunday = 6
continue
business_days_to_add -= 1
return current_date
#demo:
print '10 business days from today:'
print date_by_adding_business_days(datetime.date.today(), 10)
The problem with holidays is that they vary a lot by country or even by region, religion, etc. You would need a list/set of holidays for your use case and then skip them in a similar way. A starting point may be the calendar feed that Apple publishes for iCal (in the ics format), the one for the US would be http://files.apple.com/calendars/US32Holidays.ics
You could use the icalendar module to parse this.
If you don't mind using a 3rd party library then dateutil is handy
from dateutil.rrule import *
print "In 4 business days, it's", rrule(DAILY, byweekday=(MO,TU,WE,TH,FR))[4]
You can also look at rruleset and using .exdate() to provide the holidays to skip those in the calculation, and optionally there's a cache option to avoid re-calculating that might be worth looking in to.
There is no real shortcut to do this. Try this approach:
Create a class which has a method skip(self, d) which returns True for dates that should be skipped.
Create a dictionary in the class which contains all holidays as date objects. Don't use datetime or similar because the fractions of a day will kill you.
Return True for any date that is in the dictionary or d.weekday() >= 5
To add N days, use this method:
def advance(d, days):
delta = datetime.timedelta(1)
for x in range(days):
d = d + delta
while holidayHelper.skip(d):
d = d + delta
return d
Thanks based on omz code i made some little changes ...it maybe helpful for other users:
import datetime
def date_by_adding_business_days(from_date, add_days,holidays):
business_days_to_add = add_days
current_date = from_date
while business_days_to_add > 0:
current_date += datetime.timedelta(days=1)
weekday = current_date.weekday()
if weekday >= 5: # sunday = 6
continue
if current_date in holidays:
continue
business_days_to_add -= 1
return current_date
#demo:
Holidays =[datetime.datetime(2012,10,3),datetime.datetime(2012,10,4)]
print date_by_adding_business_days(datetime.datetime(2012,10,2), 10,Holidays)
I wanted a solution that wasn't O(N) and it looked like a fun bit of code golf. Here's what I banged out in case anyone's interested. Works for positive and negative numbers. Let me know if I missed anything.
def add_business_days(d, business_days_to_add):
num_whole_weeks = business_days_to_add / 5
extra_days = num_whole_weeks * 2
first_weekday = d.weekday()
remainder_days = business_days_to_add % 5
natural_day = first_weekday + remainder_days
if natural_day > 4:
if first_weekday == 5:
extra_days += 1
elif first_weekday != 6:
extra_days += 2
return d + timedelta(business_days_to_add + extra_days)
I know it does not handle holidays, but I found this solution more helpful because it is constant in time. It consists of counting the number of whole weeks, adding holidays is a little more complex. I hope it can help somebody :)
def add_days(days):
today = datetime.date.today()
weekday = today.weekday() + ceil(days)
complete_weeks = weekday // 7
added_days = weekday + complete_weeks * 2
return today + datetime.timedelta(days=added_days)
This will take some work since there isn't any defined construct for holidays in any library (by my knowledge at least). You will need to create your own enumeration of those.
Checking for weekend days is done easily by calling .weekday() < 6 on your datetime object.
Refactoring omz code, and using holidays package, this is what I use to add business days taking into account the country's holidays
import datetime
import holidays
def today_is_holiday(date):
isHoliday = date.date() in [key for key in holidays.EN(years = date.year).keys()]
isWeekend = date.weekday() >= 5
return isWeekend or isHoliday
def date_by_adding_business_days(from_date, add_days):
business_days_to_add = add_days
current_date = from_date
while business_days_to_add > 0:
current_date += datetime.timedelta(days=1)
if today_is_holiday(current_date):
continue
business_days_to_add -= 1
return current_date
Hope this helps. It's not O(N) but O(holidays). Also, holidays only works when the offset is positive.
def add_working_days(start, working_days, holidays=()):
"""
Add working_days to start start date , skipping weekends and holidays.
:param start: the date to start from
:type start: datetime.datetime|datetime.date
:param working_days: offset in working days you want to add (can be negative)
:type working_days: int
:param holidays: iterator of datetime.datetime of datetime.date instances
:type holidays: iter(datetime.date|datetime.datetime)
:return: the new date wroking_days date from now
:rtype: datetime.datetime
:raise:
ValueError if working_days < 0 and holidays
"""
assert isinstance(start, (datetime.date, datetime.datetime)), 'start should be a datetime instance'
assert isinstance(working_days, int)
if working_days < 0 and holidays:
raise ValueError('Holidays and a negative offset is not implemented. ')
if working_days == 0:
return start
# first just add the days
new_date = start + datetime.timedelta(working_days)
# now compensate for the weekends.
# the days is 2 times plus the amount of weeks are included in the offset added to the day of the week
# from the start. This compensates for adding 1 to a friday because 4+1 // 5 = 1
new_date += datetime.timedelta(2 * ((working_days + start.weekday()) // 5))
# now compensate for the holidays
# process only the relevant dates so order the list and abort the handling when the holiday is no longer
# relevant. Check each holiday not being in a weekend, otherwise we don't mind because we skip them anyway
# next, if a holiday is found, just add 1 to the date, using the add_working_days function to compensate for
# weekends. Don't pass the holiday to avoid recursion more then 1 call deep.
for hday in sorted(holidays):
if hday < start:
# ignore holidays before start, we don't care
continue
if hday.weekday() > 4:
# skip holidays in weekends
continue
if hday <= new_date:
# only work with holidays up to and including the current new_date.
# increment using recursion to compensate for weekends
new_date = add_working_days(new_date, 1)
else:
break
return new_date
If someone needs to add/substract days, extending #omz's answer:
def add_business_days(from_date, ndays):
business_days_to_add = abs(ndays)
current_date = from_date
sign = ndays/abs(ndays)
while business_days_to_add > 0:
current_date += datetime.timedelta(sign * 1)
weekday = current_date.weekday()
if weekday >= 5: # sunday = 6
continue
business_days_to_add -= 1
return current_date
similar to #omz solution but recursively:
def add_days_skipping_weekends(start_date, days):
if not days:
return start_date
start_date += timedelta(days=1)
if start_date.weekday() < 5:
days -= 1
return add_days_skipping_weekends(start_date, days)
If you are interested in using NumPy, then you can follow the solution below:
import numpy as np
from datetime import datetime, timedelta
def get_future_date_excluding_weekends(date,no_of_days):
"""This methods return future date by adding given number of days excluding
weekends"""
future_date = date + timedelta(no_of_days)
no_of_busy_days = int(np.busday_count(date.date(),future_date.date()))
if no_of_busy_days != no_of_days:
extend_future_date_by = no_of_days - no_of_busy_days
future_date = future_date + timedelta(extend_future_date_by)
return future_date
This is the best solution because it has O(1) complexity (no loop) and no 3-rd party, but it does not take into account the holidays:
def add_working_days_to_date(self, start_date, days_to_add):
from datetime import timedelta
start_weekday = start_date.weekday()
# first week
total_days = start_weekday + days_to_add
if total_days < 5:
return start_date + timedelta(days=total_days)
else:
# first week
total_days = 7 - start_weekday
days_to_add -= 5 - start_weekday
# middle whole weeks
whole_weeks = days_to_add // 5
remaining_days = days_to_add % 5
total_days += whole_weeks * 7
days_to_add -= whole_weeks * 5
# last week
total_days += remaining_days
return start_date + timedelta(days=total_days)
Even though this does not fully solves your problem, I wanted to let it here because the solutions found on the internet for adding working days to dates, all of them have O(n) complexity.
Keep in mind that, if you want to add 500 days to a date, you will go through a loop and make the same set of computations 500 times. The above approach operates in the same amount of time, no matter how many days you have.
This was heavily tested.
Use numpy (you can skip holidays too):
np.busday_offset(
np.datetime64('2022-12-08'),
offsets=range(12),
roll='following',
weekmask="1111100",
holidays=[])
Result:
array(['2022-12-08', '2022-12-09', '2022-12-12', '2022-12-13',
'2022-12-14', '2022-12-15', '2022-12-16', '2022-12-19',
'2022-12-20', '2022-12-21', '2022-12-22', '2022-12-23'],
dtype='datetime64[D]')
I am using following code to handle business date delta. For holidays, you need to create your own list to skip.
today = datetime.now()
t_1 = today - BDay(1)
t_5 = today - BDay(5)
t_1_str = datetime.strftime(t_1,"%Y%m%d")

Categories