finding last business day of a month in python - python

I'm trying to find last business day of of the month. I wrote the code below for that and it works fine but I was wondering if there is a cleaner way of doing it?
from datetime import date,timedelta
import datetime
import calendar
today=datetime.date.today()
last = today.replace(day=calendar.monthrange(today.year,today.month)[1])
if last.weekday()<5:
print last
else:
print last-timedelta(days=1+last.weekday()-5)
Thanks in advance!

I use the following:
from pandas.tseries.offsets import BMonthEnd
from datetime import date
d=date.today()
offset = BMonthEnd()
#Last day of current month
offset.rollforward(d)
#Last day of previous month
offset.rollback(d)

Let's say you want to get the last business days of the month up-to the end of the next two years, the following will work.
import pandas as pd
import datetime
start = datetime.date.today()
end = datetime.date(start.year+2, 12, 31)
bussiness_days_rng =pd.date_range(start, end, freq='BM')

For one-liner fans:
import calendar
def last_business_day_in_month(year: int, month: int) -> int:
return max(calendar.monthcalendar(year, month)[-1][:5])

I use this for the first business day of the month but it can be used for last business day of the month as well:
import time
import datetime
from pandas.tseries.holiday import USFederalHolidayCalendar
from pandas.tseries.offsets import CustomBusinessDay
from dateutil.relativedelta import relativedelta
#Create dates needed to be entered as parameters
today = datetime.date.today()
first = today.replace(day=1)
#End of the Prior Month
eopm = first - datetime.timedelta(days=1)
eopm = eopm.strftime("%Y%m%d")
#Create first business day of current month date
us_bd = CustomBusinessDay(calendar=USFederalHolidayCalendar())
focm = first
nxtMo = today + relativedelta(months=+1)
fonm = nxtMo.replace(day=1)
eocm = fonm - datetime.timedelta(days=1)
first_bd = pd.DatetimeIndex(start = focm, end = eocm, freq= us_bd)
first_bd = first_bd.strftime("%Y%m%d")
#First Business Day of the Month
first_bd = first_bd[0]
#Last Business Day of the Month
lst_day = len(first_bd)-1
last_bd = first_bd[lst_day]
I left some code in there that is not needed for the last business day of the current month, but may be useful to someone.

You can use Pandas to get business days. Refer http://pandas.pydata.org/pandas-docs/stable/timeseries.html
Also you can refer this https://pypi.python.org/pypi/business_calendar/ for simple business days calculation.

with rollforward(d) you will skip to the next month if the date is past the last business day of the current month, so below might be safer for any day of the month:
from datetime import date
import pandas as pd
d = date(2011, 12, 31) # a caturday
pd.bdate_range(end=pd.offsets.MonthEnd().rollforward(d), periods=1)
pd.offsets.BMonthEnd().rollforward(d)

I needed something intuitively readable and opted for the following:
from datetime import datetime, timedelta
import pandas as pd
def isMonthLastBusinessDay(date):
lastDayOfMonth = date + pd.offsets.MonthEnd(0)
isFriday = date.weekday() == 4
if (date.weekday() < 5 and lastDayOfMonth == date) or (isFriday and lastDayOfMonth == date+timedelta(days=1)) or (isFriday and lastDayOfMonth == date+timedelta(days=2)):
return True
else:
return False

Related

How to get the last date of the current week or quarter in python?

I would like to find a simple way to get the last date of the current week or quarter.
To get the last date of the current month I can use the relativdelta function from dateutil:
import pandas as pd
today_date = pd.Timestamp.today().date() #get today's date
from dateutil.relativedelta import relativedelta
current_month_last_date = today_date + relativedelta(day=31) #get last date of current month
What is an equivalent way to get the last date of the current week or quarter?
You can use isocalendar to get week number:
from datetime import date
import calendar
today = date.today()
_, week_number, _ = today.isocalendar()
last_day_of_week = date.fromisocalendar(today.year, week_number, 7)
print(last_day_of_week)
For quarter, you just have to construct the date:
quarter = (today.month-1)//3 + 1
month_of_quarter = quarter*3
last_day_of_quarter = date(today.year, month_of_quarter, calendar.monthrange(today.year, month_of_quarter)[1])
print(last_day_of_quarter)
2023-01-15
2023-03-31

How to add business days in date excluding holidays

I have a dataframe (df) with start_date column's and add_days column's (=10). I want to create target_date (=start_date + add_days) excluding week-end and holidays (holidays as dataframe).
I do some research and I try this.
from datetime import date, timedelta
import datetime as dt
df["star_date"] = pd.to_datetime(df["star_date"])
Holidays['Date_holi'] = pd.to_datetime(Holidays['Date_holi'])
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:
base["Target_date"]=date_by_adding_business_days(df["start_date"], 10, Holidays['Date_holi'])
but i get this error:
AttributeError: 'Series' object has no attribute 'weekday'
Thanks you for your help.
The comments by ALollz are very valid; customizing your date during creation to only keep what is defined as business day for your problem would be optimal.
However, I assume that you cannot define the business day beforehand and that you need to solve the problem with the data frame constructed as is.
Here is one possible solution:
import pandas as pd
import numpy as np
from datetime import timedelta
# Goal is to offset a start date by N business days (weekday + not a holiday)
# Here we fake the dataset as it was not provided
num_row = 1000
df = pd.DataFrame()
df['start_date'] = pd.date_range(start='1/1/1979', periods=num_row, freq='D')
df['add_days'] = pd.Series([10]*num_row)
# Define what is a week day
week_day = [0,1,2,3,4] # Monday to Friday
# Define what is a holiday with month and day without year (you can add more)
holidays = ['10-30','12-24']
def add_days_to_business_day(df, week_day, holidays, increment=10):
'''
modify the dataframe to increment only the days that are part of a weekday
and not part of a pre-defined holiday
>>> add_days_to_business_day(df, [0,1,2,3,4], ['10-31','12-31'])
this will increment by 10 the days from Monday to Friday excluding Halloween and new year-eve
'''
# Increment everything that is in a business day
df.loc[df['start_date'].dt.dayofweek.isin(week_day),'target_date'] = df['start_date'] + timedelta(days=increment)
# Remove every increment done on a holiday
df.loc[df['start_date'].dt.strftime('%m-%d').isin(holidays), 'target_date'] = np.datetime64('NaT')
add_days_to_business_day(df, week_day, holidays)
df
To Note: I'm not using the 'add_days' column since its just a repeated value. I am instead using a parameter for my function increment which will increment by N number of days (with a default of N = 10).
Hope it helps!

How can I get the first day of the next month in Python?

How can I get the first date of the next month in Python? For example, if it's now 2019-12-31, the first day of the next month is 2020-01-01. If it's now 2019-08-01, the first day of the next month is 2019-09-01.
I came up with this:
import datetime
def first_day_of_next_month(dt):
'''Get the first day of the next month. Preserves the timezone.
Args:
dt (datetime.datetime): The current datetime
Returns:
datetime.datetime: The first day of the next month at 00:00:00.
'''
if dt.month == 12:
return datetime.datetime(year=dt.year+1,
month=1,
day=1,
tzinfo=dt.tzinfo)
else:
return datetime.datetime(year=dt.year,
month=dt.month+1,
day=1,
tzinfo=dt.tzinfo)
# Example usage (assuming that today is 2021-01-28):
first_day_of_next_month(datetime.datetime.now())
# Returns: datetime.datetime(2021, 2, 1, 0, 0)
Is it correct? Is there a better way?
Here is a 1-line solution using nothing more than the standard datetime library:
(dt.replace(day=1) + datetime.timedelta(days=32)).replace(day=1)
Examples:
>>> dt = datetime.datetime(2016, 2, 29)
>>> print((dt.replace(day=1) + datetime.timedelta(days=32)).replace(day=1))
2016-03-01 00:00:00
>>> dt = datetime.datetime(2019, 12, 31)
>>> print((dt.replace(day=1) + datetime.timedelta(days=32)).replace(day=1))
2020-01-01 00:00:00
>>> dt = datetime.datetime(2019, 12, 1)
>>> print((dt.replace(day=1) + datetime.timedelta(days=32)).replace(day=1))
2020-01-01 00:00:00
Using dateutil you can do it the most literally possible:
import datetime
from dateutil import relativedelta
today = datetime.date.today()
next_month = today + relativedelta.relativedelta(months=1, day=1)
In English: add 1 month(s) to the today's date and set the day (of the month) to 1. Note the usage of singular and plural forms of day(s) and month(s). Singular sets the attribute to a value, plural adds the number of periods.
You can store this relativedelta.relativedelta object to a variable and the pass it around. Other answers involve more programming logic.
EDIT You can do it with the standard datetime library as well, but it's not so beautiful:
next_month = (today.replace(day=1) + datetime.timedelta(days=32)).replace(day=1)
sets the date to the 1st of the current month, adds 32 days (or any number between 31 and 59 which guarantees to jump into the next month) and then sets the date to the 1st of that month.
you can use calendar to get the number of days in a given month, then add timedelta(days=...), like this:
from datetime import date, timedelta
from calendar import monthrange
days_in_month = lambda dt: monthrange(dt.year, dt.month)[1]
today = date.today()
first_day = today.replace(day=1) + timedelta(days_in_month(today))
print(first_day)
if you're fine with external deps, you can use dateutil (which I love...)
from datetime import date
from dateutil.relativedelta import relativedelta
today = date.today()
first_day = today.replace(day=1) + relativedelta(months=1)
print(first_day)
Extract the year and month, add 1 and form a new date using the year, month and day=1:
from datetime import date
now = date(2020,12,18)
y,m = divmod(now.year*12+now.month,12)
nextMonth = date(y,m+1,1)
print(now,nextMonth)
# 2020-12-18 2021-01-01
Your way looks good yet I would have done it this way:
import datetime
from dateutil import relativedelta
dt = datetime.datetime(year=1998,
month=12,
day=12)
nextmonth = dt + relativedelta.relativedelta(months=1)
nextmonth.replace(day=1)
print(nextmonth)
Using only python standard libraries:
import datetime
today = datetime.date.today()
first_of_next_month = return date.replace(
day=1,
month=date.month % 12 + 1,
year=date.year + (date.month // 12)
)
could be generalized to...
def get_first_of_month(date, month_offset=0):
# zero based indexing of month to make math work
month_count = date.month - 1 + month_offset
return date.replace(
day=1, month=month_count % 12 + 1, year=date.year + (month_count // 12)
)
first_of_next_month = get_first_of_month(today, 1)
Other solutions that don't require 3rd party libraries include:
Toby Petty's answer is another good option.
If the exact timedelta is helpful to you,
a slight modification on Adam.Er8's answer might be convenient:
import calendar, datetime
today = datetime.date.today()
time_until_next_month = datetime.timedelta(
calendar.monthrange(today.year, today.month)[1] - today.day + 1
)
first_of_next_month = today + time_until_next_month
With Zope's DateTime library a very simple solution is possible
from DateTime.DateTime import DateTime
date = DateTime() # today
while date.day() != 1:
date += 1
print(date)
I see so many wonderful solutions to this problem I personally was looking for a solution for getting the first and last day of the previous month when I stmbled on this question.
But here is a solution I like to think is quite simple and elegant:
date = datetime.datetime.now().date()
same_time_next_month = date + datetime.timedelta(days = date.day)
first_day_of_next_month_from_date = same_time_next_month - datetime.timedelta(days = same_time_next_month.day - 1)
Here we simply add the day of the target date to the date to get the same time of the next month, and then remove the number of days elapsed from the new date gotten.
Try this, for starting day of each month, change MonthEnd(1) to MonthBegin(1):
import pandas as pd
from pandas.tseries.offsets import MonthBegin, MonthEnd
date_list = (pd.date_range('2021-01-01', '2022-01-31',
freq='MS') + MonthEnd(1)).strftime('%Y-%m-%d').tolist()
date_list
Out:
['2021-01-31',
'2021-02-28',
'2021-03-31',
'2021-04-30',
'2021-05-31',
'2021-06-30',
'2021-07-31',
'2021-08-31',
'2021-09-30',
'2021-10-31',
'2021-11-30',
'2021-12-31',
'2022-01-31']
With python-dateutil:
from datetime import date
from dateutil.relativedelta import relativedelta
last day of current month:
date.today() + relativedelta(day=31)
first day of next month:
date.today() + relativedelta(day=31) + relativedelta(days=1)

How to get a specific date from the previous month given the current date in python?

I want to get the 20th of previous month, given the current_date()
I am trying to use time.strftime but not able to subtract the value from it.
timestr = time.strftime("%Y-(%m-1)%d")
This is giving me error. The expected output is 2019-03-20 if my current_date is in April. Not sure how to go about it.
I read the posts from SO and most of them address getting the first day / last day of the month. Any help would be appreciated.
from datetime import date, timedelta
today = date.today()
last_day_prev_month = today - timedelta(days=today.day)
twenty_prev_month = last_day_prev_month.replace(day=20)
print(twenty_prev_month) # 2019-03-20
Use datetime.replace
import datetime
current_date = datetime.date.today()
new_date = current_date.replace(
month = current_date.month - 1,
day = 20
)
print(new_date)
#2019-03-20
Edit
That won't work for Jan so this is a workaround:
import datetime
current_date = datetime.date(2019, 2, 17)
month = current_date.month - 1
year = current_date.year
if not month:
month, year = 12, year - 1
new_date = datetime.date(year=year, month=month, day=20)
I imagine it is the way dates are parsed. It is my understanding that with your code it is looking for
2019-(03-1)20 or 2019-(12-1)15, etc..
Because the %y is not a variable, but a message about how the date is to be expected within a string of text, and other characters are what should be expected, but not processed (like "-")
This seems entirely not what you are going for. I would just parse the date like normal and then reformat it to be a month earlier:
import datetime
time = datetime.datetime.today()
print(time)
timestr = time.strftime("%Y-%m-%d")
year, month, day = timestr.split("-")
print("{}-{}-{}".format(year, int(month)-1, day))
This would be easier with timedelta objects, but sadly there isn't one for months, because they are of various lengths.
To be more robust if a new year is involved:
import datetime
time = datetime.datetime.today()
print(time)
timestr = time.strftime("%Y-%m-%d")
year, month, day = timestr.split("-")
if month in [1, "01", "1"]: # I don't remember how January is represented
print("{}-{}-{}".format(int(year) - 1, 12, day)) # use December of last year
else:
print("{}-{}-{}".format(year, int(month)-1, day))
This will help:
from datetime import date, timedelta
dt = date.today() - timedelta(30)// timedelta(days No.)
print('Current Date :',date.today())
print(dt)
It is not possible to do math inside a string passed to time.strftime, but you can do something similar to what you're asking very easily using the time module
in Python 3
# Last month
t = time.gmtime()
print(f"{t.tm_year}-{t.tm_mon-1}-20")
or in Python 2
print("{0}-{1}-{2}".format(t.tm_year, t.tm_mon -1, 20))
If you have fewer constraints, you can just use the datetime module instead.
You could use datetime, dateutil or arrow to find the 20th day of the previous month. See examples below.
Using datetime:
from datetime import date
d = date.today()
month, year = (d.month-1, d.year) if d.month != 1 else (12, d.year-1)
last_month = d.replace(day=20, month=month, year=year)
print(last_month)
Using datetime and timedelta:
from datetime import date
from datetime import timedelta
d = date.today()
last_month = (d - timedelta(days=d.day)).replace(day=20)
print(last_month)
Using datetime and dateutil:
from datetime import date
from dateutil.relativedelta import relativedelta # pip install python-dateutil
d = date.today()
last_month = d.replace(day=20) - relativedelta(months=1)
print(last_month)
Using arrow:
import arrow # pip install arrow
d = arrow.now()
last_month = d.shift(months=-1).replace(day=20).datetime.date()
print(last_month)

Python select random date in current year

In Python can you select a random date from a year. e.g. if the year was 2010 a date returned could be 15/06/2010
It's much simpler to use ordinal dates (according to which today's date is 734158):
from datetime import date
import random
start_date = date.today().replace(day=1, month=1).toordinal()
end_date = date.today().toordinal()
random_day = date.fromordinal(random.randint(start_date, end_date))
This will fail for dates before 1AD.
Not directly, but you could add a random number of days to January 1st. I guess the following should work for the Gregorian calendar:
from datetime import date, timedelta
import random
import calendar
# Assuming you want a random day of the current year
firstJan = date.today().replace(day=1, month=1)
randomDay = firstJan + timedelta(days = random.randint(0, 365 if calendar.isleap(firstJan.year) else 364))
import datetime, time
import random
def year_start(year):
return time.mktime(datetime.date(year, 1, 1).timetuple())
def rand_day(year):
stamp = random.randrange(year_start(year), year_start(year + 1))
return datetime.date.fromtimestamp(stamp)
Edit: Ordinal dates as used in Michael Dunns answer are way better to use then timestamps! One might want to combine the use of ordinals with this though.
import calendar
import datetime
import random
def generate_random_date(future=True, years=1):
today = datetime.date.today()
#Set the default dates
day = today.day
year = today.year
month = today.month
if future:
year = random.randint(year, year + years)
month = random.randint(month, 12)
date_range = calendar.monthrange(year, month)[1] #dates possible this month
day = random.randint(day + 1, date_range) #1 day in the future
else:
year = random.randint(year, year - years)
month = random.randint(1, month)
day = random.randint(1, day - 1)
return datetime.date(year, month, day)
This is an old question, but, you can use my new library ^_^ chancepy here
from chancepy import Chance
randomDate = Chance.date(year=2020)
To get a random date you can use faker
pip install faker
from faker import Faker
fake = Faker()
fake.date_between(start_date='today', end_date='+1y')
if you want from the beginning of the year then:
start_date = datetime.date(year=2023, month=1, day=1)
fake.date_between(start_date, end_date='+1y')

Categories