needed to extract the quarter from a given date - python

Problem 1
As an analyst, you had to present the quarterly performance results of your client. The data which you were provided was on daily basis. To complete this task, you needed to extract the quarter from a given date. For example:
if the date lies between 1st Jan, 2020 - 31st March, 2020, you need to extract the corresponding quarter as '2020-Q1'
if the date lies between 1st April, 2020 - 30th June, 2020, the extracted quarter will be '2020-Q2'
if the date lies between 1st July, 2020 - 30th September, 2020, the extracted quarter will be '2020-Q3'
if the date lies between 1st October, 2020 - 31st Decemeber, 2020 then the extracted quarter will be '2020-Q4'

Hi and welcome to stackoverflow. I'm sure that this answer to a similar question will certainly help you.
Here is a quick summary of the above answer. Basically, you just need to check what month a given date is from and convert it to a number (1 to 12). If you subtract 1 from this (so it runs from 0 for january to 11 for december) and devide this number by 3 using integer division you get the quarter information you are looking for in the format 0 to 3. If you just add 1 again you get the correct quarter you are looking for.
Or using python code taken from the answer I linked above:
def printQuarter(month):
print((month-1)//3 + 1)
printQuarter(1) # january, result: 1
printQuarter(4) # april, result: 2
printQuarter(12) # december, result: 4
Alternatively, you could of course use if-else statements to solve this problem, as you suggested with the if-statement tag in your question. This certainly works but is far less elegant. If for some reason you still want to do this, you could do e.g.:
if month <= 3:
print("Q1")
elif month <= 6:
print("Q2")
elif month <= 9:
print("Q3")
else:
print("Q4")

I had an answer framed for you some time back! Alas! SO was down :-) . You can use an approach for finding the quarter using month of the date itself. See below.
from datetime import datetime
_date_str = "1 April, 2020"
def get_quarter_str(date_str):
known_date = datetime.strptime(date_str, "%d %B, %Y")
quarter = ((known_date.month - 1) // 3) + 1
return f"{known_date.year} - Q{quarter}"
print(get_quarter_str(_date_str))

Define a dictionary with the 'limits' of each quarter. I would do something like this:
from datetime import date
quarters = {
'2020-Q1': ((2020, 1, 1), (2020, 3, 31)),
'2020-Q2': ((2020, 4, 1), (2020, 6, 30)),
'2020-Q3': ((2020, 7, 1), (2020, 9, 30)),
'2020-Q4': ((2020, 10, 1), (2020, 12, 31))
}
def get_quarter(my_date):
try:
if not isinstance(my_date, date): #Check if the input is a date
raise TypeError
for quarter, limits in quarters.items():
if date(*limits[0]) <= my_date <= date(*limits[1]):
return quarter
except TypeError:
... # Handle exception
def main():
my_date = date(2020, 4, 15) #Define a valid input
print(get_quarter(my_date)) #Run the function
if __name__ == '__main__':
main()
This example prints out:
'2020-Q2'

date_year = '2020'
date_month = '07'
if (date_month >= "01") & (date_month <="03"):
print("quarter = ", date_year + '-Q1')
elif (date_month >= "04") & (date_month <="06"):
print("quarter = ", date_year + '-Q2')
elif (date_month >= "07") & (date_month <="09"):
print("quarter = ", date_year + '-Q3')
else:
print("quarter = ", date_year + '-Q4')

Related

How to increment date (month and year) and set the day to default based on specific conditions?

My Date should always fall on 8th or 22nd that comes off the input date.
For Example:
If the input date is 20190415 then the output date should be 20190422 as that's the nearest date and if input date is 20190424 then the output date should be 20190508.
Example1:
input_date = 20190415
Expected output_date = 20190422
Example2:
input_date = 20190424
Expected output_date = 20190508
Example3:
input_date = 20190506
Expected output_date = 20190508
Example4:
input_date = 20191223
Expected output_date = 20200108
How do we achieve this using Python?
You can check if the day is greater than 22, and if so you set it to the 8th of the next month. If it's between 8 and 22 you set it to 22 of the same month and if it's below the 8th you set it to the 8th of the month. There's probably more elegant ways to do it using date math, but this will work for your scenario.
Use the datetime module to find out what the "next month" is. One way to do it is to add a timedelta of 1 month to the first of the current month, and then change the date on that datetime object to the 8th. Here's a quick example of how that might look like:
from datetime import date, timedelta
input_date = date(2019, 12, 23)
if input_date.day > 22:
output_date = date(input_date.year, input_date.month) + timedelta(days=31)
output_date = output_date.replace(day = 8)
You can read a lot more about the details of how the datetime module works on the official documentation. It's kind of a long read, but I actually have that page bookmarked because I always have to go back and reference how to actually use the module :)
Considering the input as string, next date can be calculated using timedelta, check out the below code:
if 8<datetime.strptime(input_date, "%Y%m%d").day < 22:
delta = 22 - datetime.strptime(input_date, "%Y%m%d").day
print((datetime.strptime(input_date, "%Y%m%d") +
timedelta(days=delta)).strftime("%Y%m%d"))
elif datetime.strptime(str(input_date), "%Y%m%d").day < 8:
delta = 8 - datetime.strptime(input_date, "%Y%m%d").day
print((datetime.strptime(input_date, "%Y%m%d") +
timedelta(days=delta)).strftime("%Y%m%d"))
else:
delta = (datetime.strptime(input_date, "%Y%m%d")+ relativedelta(months=+1)).day -8
print((datetime.strptime(input_date, "%Y%m%d") + relativedelta(months=+1) -
timedelta(days=delta)).strftime("%Y%m%d") )

Getting the business day number using a date and holiday calendar in python [duplicate]

I need to subtract business days from the current date.
I currently have some code which needs always to be running on the most recent business day. So that may be today if we're Monday thru Friday, but if it's Saturday or Sunday then I need to set it back to the Friday before the weekend. I currently have some pretty clunky code to do this:
lastBusDay = datetime.datetime.today()
if datetime.date.weekday(lastBusDay) == 5: #if it's Saturday
lastBusDay = lastBusDay - datetime.timedelta(days = 1) #then make it Friday
elif datetime.date.weekday(lastBusDay) == 6: #if it's Sunday
lastBusDay = lastBusDay - datetime.timedelta(days = 2); #then make it Friday
Is there a better way?
Can I tell timedelta to work in weekdays rather than calendar days for example?
Use pandas!
import datetime
# BDay is business day, not birthday...
from pandas.tseries.offsets import BDay
today = datetime.datetime.today()
print(today - BDay(4))
Since today is Thursday, Sept 26, that will give you an output of:
datetime.datetime(2013, 9, 20, 14, 8, 4, 89761)
If you want to skip US holidays as well as weekends, this worked for me (using pandas 0.23.3):
import pandas as pd
from pandas.tseries.holiday import USFederalHolidayCalendar
from pandas.tseries.offsets import CustomBusinessDay
US_BUSINESS_DAY = CustomBusinessDay(calendar=USFederalHolidayCalendar())
july_5 = pd.datetime(2018, 7, 5)
result = july_5 - 2 * US_BUSINESS_DAY # 2018-7-2
To convert to a python date object I did this:
result.to_pydatetime().date()
Maybe this code could help:
lastBusDay = datetime.datetime.today()
shift = datetime.timedelta(max(1,(lastBusDay.weekday() + 6) % 7 - 3))
lastBusDay = lastBusDay - shift
The idea is that on Mondays yo have to go back 3 days, on Sundays 2, and 1 in any other day.
The statement (lastBusDay.weekday() + 6) % 7 just re-bases the Monday from 0 to 6.
Really don't know if this will be better in terms of performance.
There seem to be several options if you're open to installing extra libraries.
This post describes a way of defining workdays with dateutil.
http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-09/3758.html
BusinessHours lets you custom-define your list of holidays, etc., to define when your working hours (and by extension working days) are.
http://pypi.python.org/pypi/BusinessHours/
DISCLAMER: I'm the author...
I wrote a package that does exactly this, business dates calculations. You can use custom week specification and holidays.
I had this exact problem while working with financial data and didn't find any of the available solutions particularly easy, so I wrote one.
Hope this is useful for other people.
https://pypi.python.org/pypi/business_calendar/
If somebody is looking for solution respecting holidays (without any huge library like pandas), try this function:
import holidays
import datetime
def previous_working_day(check_day_, holidays=holidays.US()):
offset = max(1, (check_day_.weekday() + 6) % 7 - 3)
most_recent = check_day_ - datetime.timedelta(offset)
if most_recent not in holidays:
return most_recent
else:
return previous_working_day(most_recent, holidays)
check_day = datetime.date(2020, 12, 28)
previous_working_day(check_day)
which produces:
datetime.date(2020, 12, 24)
timeboard package does this.
Suppose your date is 04 Sep 2017. In spite of being a Monday, it was a holiday in the US (the Labor Day). So, the most recent business day was Friday, Sep 1.
>>> import timeboard.calendars.US as US
>>> clnd = US.Weekly8x5()
>>> clnd('04 Sep 2017').rollback().to_timestamp().date()
datetime.date(2017, 9, 1)
In UK, 04 Sep 2017 was the regular business day, so the most recent business day was itself.
>>> import timeboard.calendars.UK as UK
>>> clnd = UK.Weekly8x5()
>>> clnd('04 Sep 2017').rollback().to_timestamp().date()
datetime.date(2017, 9, 4)
DISCLAIMER: I am the author of timeboard.
For the pandas usecase, I found the following to be quite useful and compact, although not completely readable:
Get most recent previous business day:
In [2]: datetime.datetime(2019, 11, 30) + BDay(1) - BDay(1) # Saturday
Out[2]: Timestamp('2019-11-29 00:00:00')
In [3]: datetime.datetime(2019, 11, 29) + BDay(1) - BDay(1) # Friday
Out[3]: Timestamp('2019-11-29 00:00:00')
In the other direction, simply use:
In [4]: datetime.datetime(2019, 11, 30) + BDay(0) # Saturday
Out[4]: Timestamp('2019-12-02 00:00:00')
In [5]: datetime.datetime(2019, 11, 29) + BDay(0) # Friday
Out[5]: Timestamp('2019-11-29 00:00:00')
This will give a generator of working days, of course without holidays, stop is datetime.datetime object. If you need holidays just make additional argument with list of holidays and check with 'IFology' ;-)
def workingdays(stop, start=datetime.date.today()):
while start != stop:
if start.weekday() < 5:
yield start
start += datetime.timedelta(1)
Later on you can count them like
workdays = workingdays(datetime.datetime(2015, 8, 8))
len(list(workdays))
def getNthBusinessDay(startDate, businessDaysInBetween):
currentDate = startDate
daysToAdd = businessDaysInBetween
while daysToAdd > 0:
currentDate += relativedelta(days=1)
day = currentDate.weekday()
if day < 5:
daysToAdd -= 1
return currentDate
When I am writing this answer, today is Friday in USA so next business day shall be Monday, in the meantime yesterday is thanksgiving holiday so previous business day should be Wednesday
So today date of Friday, November 24, 2022, is a perfect time to get the previous, current and next business days.
By having trial and error, I could only find the correct output by combining the method as below:
from datetime import datetime, timedelta
from pandas.tseries.offsets import BDay
from pandas.tseries.offsets import CustomBusinessDay
from pandas.tseries.holiday import USFederalHolidayCalendar
US_BUSINESS_DAY = CustomBusinessDay(calendar=USFederalHolidayCalendar())
TODAY = datetime.today() - 1 * US_BUSINESS_DAY
YESTERDAY = (datetime.today() - timedelta(max(1,(TODAY.weekday() + 6) % 7 - 3))) - 1 * US_BUSINESS_DAY
TOMORROW = TODAY + BDay(1)
DAY_NAME = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday']
BUSINESS_DATE = "[Previous (" + DAY_NAME[YESTERDAY.weekday()] + "):'" + YESTERDAY.strftime('%y%m%d')
BUSINESS_DATE += "', Current (" + DAY_NAME[TODAY.weekday()] + "):'" + TODAY.strftime('%y%m%d')
BUSINESS_DATE += "', Next (" + DAY_NAME[TOMORROW.weekday()] + "):'" + TOMORROW.strftime('%y%m%d') + "']"
print_("Business Date USA = ", BUSINESS_DATE)
Output:
Business Date USA = [Previous (Wednesday):'221123', Current (Friday):'221125', Next (Monday):'221128']
Getting the most recent business day:
pd.bdate_range(end=(pd.to_datetime('today').date()), periods=1)[0])
OR in case you want it as a 'datetime.date' type:
(pd.bdate_range(end=(pd.to_datetime('today').date()), periods=1)[0]).date()
The accepted answer actually gives an incorrect result because today - BDay(0) rounds forward to Monday during the weekend instead of back to Friday like the question states. What you'd want is BusinessDay().rollback() which rolls back to the prior business day (the accepted answer matches BusinessDay().rollforward() logic).
import pandas as pd
import datetime
today = datetime.datetime.today()
prior_bday = pd.tseries.offsets.BusinessDay().rollback(today)
Why don't you try something like:
lastBusDay = datetime.datetime.today()
if datetime.date.weekday(lastBusDay) not in range(0,5):
lastBusDay = 5
another simplify version
lastBusDay = datetime.datetime.today()
wk_day = datetime.date.weekday(lastBusDay)
if wk_day > 4: #if it's Saturday or Sunday
lastBusDay = lastBusDay - datetime.timedelta(days = wk_day-4) #then make it Friday
Solution irrespective of different jurisdictions having different holidays:
If you need to find the right id within a table, you can use this snippet. The Table model is a sqlalchemy model and the dates to search from are in the field day.
def last_relevant_date(db: Session, given_date: date) -> int:
available_days = (db.query(Table.id, Table.day)
.order_by(desc(Table.day))
.limit(100).all())
close_dates = pd.DataFrame(available_days)
close_dates['delta'] = close_dates['day'] - given_date
past_dates = (close_dates
.loc[close_dates['delta'] < pd.Timedelta(0, unit='d')])
table_id = int(past_dates.loc[past_dates['delta'].idxmax()]['id'])
return table_id
This is not a solution that I would recommend when you have to convert in bulk. It is rather generic and expensive as you are not using joins. Moreover, it assumes that you have a relevant day that is one of the 100 most recent days in the model Table. So it tackles data input that may have different dates.
Get first day of month, last day of month and last business day of previous month if last day falls on weekend Saturday/Sunday
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
day = datetime(2023, 1, 10)
#last day of (n) previous month (n=months)
#n = 0 -- for current month
n=1
lastDayMonth = ((day - relativedelta(months=n) + relativedelta(day=31)).date());
#First day of previous month (n=months=1)
firstDayMonth = ((day - relativedelta(months=n) + relativedelta(day=1)).date());
print("Last Day of Month - "+ str(lastDayMonth))
print("First Day of Month - "+ str(firstDayMonth))
#Last business day (Friday) of prev (n) month (n=months=1)
lastBusDay = (lastDayMonth - timedelta(max(1,(lastDayMonth.weekday() + 6) % 7 - 3))) if lastDayMonth.weekday() in (5,6) else lastDayMonth
print("Last Business Day of Month - " + str(lastBusDay))
print()
--- Output
Last Day of Month - 2022-12-31
First Day of Month - 2022-12-01
Last Business Day of Month - 2022-12-30

How to find out week no of the month in python?

I have seen many ways to determine week of the year. Like by giving instruction datetime.date(2016, 2, 14).isocalendar()[1] I get 6 as output. Which means 14th feb 2016 falls under 6th Week of the year. But I couldn't find any way by which I could find week of the Month.
Means IF I give input as some_function(2016,2,16)
I should get output as 3, denoting me that 16th Feb 2016 is 3rd week of the Feb 2016
[ this is different question than similar available question, here I'm asking about finding week no of the month and not of the year]
This function did the work what I wanted
from math import ceil
def week_of_month(dt):
first_day = dt.replace(day=1)
dom = dt.day
adjusted_dom = dom + first_day.weekday()
return int(ceil(adjusted_dom/7.0))
I got this function from This StackOverFlow Answer
import datetime
def week_number_of_month(date_value):
week_number = (date_value.isocalendar()[1] - date_value.replace(day=1).isocalendar()[1] + 1)
if week_number == -46:
week_number = 6
return week_number
date_given = datetime.datetime(year=2018, month=12, day=31).date()
week_number_of_month(date_given)

nth weekday calculation in Python - whats wrong with this code?

I'm trying to calculate the nth weekday for a given date. For example, I should be able to calculate the 3rd wednesday in the month for a given date.
I have written 2 versions of a function that is supposed to do that:
from datetime import datetime, timedelta
### version 1
def nth_weekday(the_date, nth_week, week_day):
temp = the_date.replace(day=1)
adj = (nth_week-1)*7 + temp.weekday()-week_day
return temp + timedelta(days=adj)
### version 2
def nth_weekday(the_date, nth_week, week_day):
temp = the_date.replace(day=1)
adj = temp.weekday()-week_day
temp += timedelta(days=adj)
temp += timedelta(weeks=nth_week)
return temp
Console output
# Calculate the 3rd Friday for the date 2011-08-09
x=nth_weekday(datetime(year=2011,month=8,day=9),3,4)
print 'output:',x.strftime('%d%b%y')
# output: 11Aug11 (Expected: '19Aug11')
The logic in both functions is obviously wrong, but I can't seem to locate the bug - can anyone spot what is wrong with the code - and how do I fix it to return the correct value?
Your problem is here:
adj = temp.weekday()-week_day
First of all, you are subtracting things the wrong way: you need to subtract the actual day from the desired one, not the other way around.
Second, you need to ensure that the result of the subtraction is not negative - it should be put in the range 0-6 using % 7.
The result:
adj = (week_day - temp.weekday()) % 7
In addition, in your second version, you need to add nth_week-1 weeks like you do in your first version.
Complete example:
def nth_weekday(the_date, nth_week, week_day):
temp = the_date.replace(day=1)
adj = (week_day - temp.weekday()) % 7
temp += timedelta(days=adj)
temp += timedelta(weeks=nth_week-1)
return temp
>>> nth_weekday(datetime(2011,8,9), 3, 4)
datetime.datetime(2011, 8, 19, 0, 0)
one-liner
You can find the nth weekday with a one liner that uses calendar from the standard library.
import calendar
calendar.Calendar(x).monthdatescalendar(year, month)[n][0]
where:
x : the integer representing your weekday (0 is Monday)
n : the 'nth' part of your question
year, month : the integers year and month
This will return a datetime.date object.
broken down
It can be broken down this way:
calendar.Calendar(x)
creates a calendar object with weekdays starting on your required weekday.
.monthdatescalendar(year, month)
returns all the calendar days of that month.
[n][0]
returns the 0 indexed value of the nth week (the first day of that week, which starts on the xth day).
why it works
The reason for starting the week on your required weekday is that by default 0 (Monday) will be used as the first day of the week and if the month starts on a Wednesday, calendar will consider the first week to start on the first occurrence of Monday (ie. week 2) and you'll be a week behind.
example
If you were to need the third Saturday of September 2013 (that month's US stock option expiry day), you would use the following:
calendar.Calendar(5).monthdatescalendar(2013,9)[3][0]
The problem with the one-liner with the most votes is it doesn't work.
It can however be used as a basis for refinement:
You see this is what you get:
c = calendar.Calendar(calendar.SUNDAY).monthdatescalendar(2018, 7)
for c2 in c:
print(c2[0])
2018-07-01
2018-07-08
2018-07-15
2018-07-22
2018-07-29
c = calendar.Calendar(calendar.SUNDAY).monthdatescalendar(2018, 8)
for c2 in c:
print(c2[0])
2018-07-29
2018-08-05
2018-08-12
2018-08-19
2018-08-26
If you think about it it's trying to organise the calendars into nested lists to print a weeks worth of dates at a time. So stragglers from other months come into play. By using a new list of valid days that fall in the month - this does the trick.
Answer with appended list
import calendar
import datetime
def get_nth_DOW_for_YY_MM(dow, yy, mm, nth) -> datetime.date:
#dow - Python Cal - 6 Sun 0 Mon ... 5 Sat
#nth is 1 based... -1. is ok for last.
i = -1 if nth == -1 or nth == 5 else nth -1
valid_days = []
for d in calendar.Calendar(dow).monthdatescalendar(yy, mm):
if d[0].month == mm:
valid_days.append(d[0])
return valid_days[i]
So here's how it could be called:
firstSundayInJuly2018 = get_nth_DOW_for_YY_MM(calendar.SUNDAY, 2018, 7, 1)
firstSundayInAugust2018 = get_nth_DOW_for_YY_MM(calendar.SUNDAY, 2018, 8, 1)
print(firstSundayInJuly2018)
print(firstSundayInAugust2018)
And here is the output:
2018-07-01
2018-08-05
get_nth_DOW_for_YY_MM() can be refactored using lambda expressions like so:
Answer with lambda expression refactoring
import calendar
import datetime
def get_nth_DOW_for_YY_MM(dow, yy, mm, nth) -> datetime.date:
#dow - Python Cal - 6 Sun 0 Mon ... 5 Sat
#nth is 1 based... -1. is ok for last.
i = -1 if nth == -1 or nth == 5 else nth -1
return list(filter(lambda x: x.month == mm, \
list(map(lambda x: x[0], \
calendar.Calendar(dow).monthdatescalendar(yy, mm) \
)) \
))[i]
The one-liner answer does not seem to work if the target day falls on the first of the month. For instance, if you want the 2nd Friday of every month, then the one-liner approach
calendar.Calendar(4).monthdatescalendar(year, month)[2][0]
for March 2013 will return March 15th 2013 when it should be March 8th 2013. Perhaps add in a check like
if date(year, month, 1).weekday() == x:
delivery_date.append(calendar.Calendar(x).monthdatescalendar(year, month)[n-1][0])
else:
delivery_date.append(calendar.Calendar(x).monthdatescalendar(year, month)[n][0])
Alternatively this will work for Python 2, returns the occurance of weekday in the said month, i.e if 16 June 2018 is the input, then returns the occurance of the day on 16th June 2018
You may substitute the month/year/date integers to anything you might want - right now it's getting the input / date from the system via datetime
Omit out print statements or use pass where they're not needed
import calendar
import datetime
import pprint
month_number = int(datetime.datetime.now().strftime('%m'))
year_number = int(datetime.datetime.now().strftime('%Y'))
date_number = int(datetime.datetime.now().strftime('%d'))
day_ofweek = str(datetime.datetime.now().strftime('%A'))
def weekday_occurance():
print "\nFinding current date here\n"
for week in xrange(5):
try:
calendar.monthcalendar(year_number, month_number)[week].index(date_number)
occurance = week + 1
print "Date %s of month %s and year %s is %s #%s in this month." % (date_number,month_number,year_number,day_ofweek,occurance)
return occurance
break
except ValueError as e:
print "The date specified is %s which is week %s" % (e,week)
myocc = weekday_occurance()
print myocc
A little tweak would make the one-liner work correctly:
import calendar
calendar.Calendar((weekday+1)%7).monthdatescalendar(year, month)[n_th][-1]
Here n_th should be interpreted as c-style, e.g. 0 is the first index.
Example: to find 1st Sunday in July 2018 one could type:
>>> calendar.Calendar(0).monthdatescalendar(2018, 7)[0][-1]
datetime.date(2018, 7, 1)
People here seem to like one-liner, I will propose below.
import calendar
[cal[0] for cal in calendar.Calendar(x).monthdatescalendar(year, month) if cal[0].month == month][n]
The relativedelta module that's an extension from the Python dateutil package (pip install python-dateutil) does exactly what you want:
from dateutil import relativedelta
import datetime
def nth_weekday(the_date, nth_week, week_day):
return the_date.replace(day=1) + relativedelta.relativedelta(
weekday=week_day(nth_week)
)
print(nth_weekday(datetime.date.today(), 3, relativedelta.FR))
The key part here evaluates to weekday=relativedelta.FR(3): 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)).

Most recent previous business day in Python

I need to subtract business days from the current date.
I currently have some code which needs always to be running on the most recent business day. So that may be today if we're Monday thru Friday, but if it's Saturday or Sunday then I need to set it back to the Friday before the weekend. I currently have some pretty clunky code to do this:
lastBusDay = datetime.datetime.today()
if datetime.date.weekday(lastBusDay) == 5: #if it's Saturday
lastBusDay = lastBusDay - datetime.timedelta(days = 1) #then make it Friday
elif datetime.date.weekday(lastBusDay) == 6: #if it's Sunday
lastBusDay = lastBusDay - datetime.timedelta(days = 2); #then make it Friday
Is there a better way?
Can I tell timedelta to work in weekdays rather than calendar days for example?
Use pandas!
import datetime
# BDay is business day, not birthday...
from pandas.tseries.offsets import BDay
today = datetime.datetime.today()
print(today - BDay(4))
Since today is Thursday, Sept 26, that will give you an output of:
datetime.datetime(2013, 9, 20, 14, 8, 4, 89761)
If you want to skip US holidays as well as weekends, this worked for me (using pandas 0.23.3):
import pandas as pd
from pandas.tseries.holiday import USFederalHolidayCalendar
from pandas.tseries.offsets import CustomBusinessDay
US_BUSINESS_DAY = CustomBusinessDay(calendar=USFederalHolidayCalendar())
july_5 = pd.datetime(2018, 7, 5)
result = july_5 - 2 * US_BUSINESS_DAY # 2018-7-2
To convert to a python date object I did this:
result.to_pydatetime().date()
Maybe this code could help:
lastBusDay = datetime.datetime.today()
shift = datetime.timedelta(max(1,(lastBusDay.weekday() + 6) % 7 - 3))
lastBusDay = lastBusDay - shift
The idea is that on Mondays yo have to go back 3 days, on Sundays 2, and 1 in any other day.
The statement (lastBusDay.weekday() + 6) % 7 just re-bases the Monday from 0 to 6.
Really don't know if this will be better in terms of performance.
There seem to be several options if you're open to installing extra libraries.
This post describes a way of defining workdays with dateutil.
http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-09/3758.html
BusinessHours lets you custom-define your list of holidays, etc., to define when your working hours (and by extension working days) are.
http://pypi.python.org/pypi/BusinessHours/
DISCLAMER: I'm the author...
I wrote a package that does exactly this, business dates calculations. You can use custom week specification and holidays.
I had this exact problem while working with financial data and didn't find any of the available solutions particularly easy, so I wrote one.
Hope this is useful for other people.
https://pypi.python.org/pypi/business_calendar/
If somebody is looking for solution respecting holidays (without any huge library like pandas), try this function:
import holidays
import datetime
def previous_working_day(check_day_, holidays=holidays.US()):
offset = max(1, (check_day_.weekday() + 6) % 7 - 3)
most_recent = check_day_ - datetime.timedelta(offset)
if most_recent not in holidays:
return most_recent
else:
return previous_working_day(most_recent, holidays)
check_day = datetime.date(2020, 12, 28)
previous_working_day(check_day)
which produces:
datetime.date(2020, 12, 24)
timeboard package does this.
Suppose your date is 04 Sep 2017. In spite of being a Monday, it was a holiday in the US (the Labor Day). So, the most recent business day was Friday, Sep 1.
>>> import timeboard.calendars.US as US
>>> clnd = US.Weekly8x5()
>>> clnd('04 Sep 2017').rollback().to_timestamp().date()
datetime.date(2017, 9, 1)
In UK, 04 Sep 2017 was the regular business day, so the most recent business day was itself.
>>> import timeboard.calendars.UK as UK
>>> clnd = UK.Weekly8x5()
>>> clnd('04 Sep 2017').rollback().to_timestamp().date()
datetime.date(2017, 9, 4)
DISCLAIMER: I am the author of timeboard.
For the pandas usecase, I found the following to be quite useful and compact, although not completely readable:
Get most recent previous business day:
In [2]: datetime.datetime(2019, 11, 30) + BDay(1) - BDay(1) # Saturday
Out[2]: Timestamp('2019-11-29 00:00:00')
In [3]: datetime.datetime(2019, 11, 29) + BDay(1) - BDay(1) # Friday
Out[3]: Timestamp('2019-11-29 00:00:00')
In the other direction, simply use:
In [4]: datetime.datetime(2019, 11, 30) + BDay(0) # Saturday
Out[4]: Timestamp('2019-12-02 00:00:00')
In [5]: datetime.datetime(2019, 11, 29) + BDay(0) # Friday
Out[5]: Timestamp('2019-11-29 00:00:00')
This will give a generator of working days, of course without holidays, stop is datetime.datetime object. If you need holidays just make additional argument with list of holidays and check with 'IFology' ;-)
def workingdays(stop, start=datetime.date.today()):
while start != stop:
if start.weekday() < 5:
yield start
start += datetime.timedelta(1)
Later on you can count them like
workdays = workingdays(datetime.datetime(2015, 8, 8))
len(list(workdays))
def getNthBusinessDay(startDate, businessDaysInBetween):
currentDate = startDate
daysToAdd = businessDaysInBetween
while daysToAdd > 0:
currentDate += relativedelta(days=1)
day = currentDate.weekday()
if day < 5:
daysToAdd -= 1
return currentDate
When I am writing this answer, today is Friday in USA so next business day shall be Monday, in the meantime yesterday is thanksgiving holiday so previous business day should be Wednesday
So today date of Friday, November 24, 2022, is a perfect time to get the previous, current and next business days.
By having trial and error, I could only find the correct output by combining the method as below:
from datetime import datetime, timedelta
from pandas.tseries.offsets import BDay
from pandas.tseries.offsets import CustomBusinessDay
from pandas.tseries.holiday import USFederalHolidayCalendar
US_BUSINESS_DAY = CustomBusinessDay(calendar=USFederalHolidayCalendar())
TODAY = datetime.today() - 1 * US_BUSINESS_DAY
YESTERDAY = (datetime.today() - timedelta(max(1,(TODAY.weekday() + 6) % 7 - 3))) - 1 * US_BUSINESS_DAY
TOMORROW = TODAY + BDay(1)
DAY_NAME = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday']
BUSINESS_DATE = "[Previous (" + DAY_NAME[YESTERDAY.weekday()] + "):'" + YESTERDAY.strftime('%y%m%d')
BUSINESS_DATE += "', Current (" + DAY_NAME[TODAY.weekday()] + "):'" + TODAY.strftime('%y%m%d')
BUSINESS_DATE += "', Next (" + DAY_NAME[TOMORROW.weekday()] + "):'" + TOMORROW.strftime('%y%m%d') + "']"
print_("Business Date USA = ", BUSINESS_DATE)
Output:
Business Date USA = [Previous (Wednesday):'221123', Current (Friday):'221125', Next (Monday):'221128']
Getting the most recent business day:
pd.bdate_range(end=(pd.to_datetime('today').date()), periods=1)[0])
OR in case you want it as a 'datetime.date' type:
(pd.bdate_range(end=(pd.to_datetime('today').date()), periods=1)[0]).date()
The accepted answer actually gives an incorrect result because today - BDay(0) rounds forward to Monday during the weekend instead of back to Friday like the question states. What you'd want is BusinessDay().rollback() which rolls back to the prior business day (the accepted answer matches BusinessDay().rollforward() logic).
import pandas as pd
import datetime
today = datetime.datetime.today()
prior_bday = pd.tseries.offsets.BusinessDay().rollback(today)
Why don't you try something like:
lastBusDay = datetime.datetime.today()
if datetime.date.weekday(lastBusDay) not in range(0,5):
lastBusDay = 5
another simplify version
lastBusDay = datetime.datetime.today()
wk_day = datetime.date.weekday(lastBusDay)
if wk_day > 4: #if it's Saturday or Sunday
lastBusDay = lastBusDay - datetime.timedelta(days = wk_day-4) #then make it Friday
Solution irrespective of different jurisdictions having different holidays:
If you need to find the right id within a table, you can use this snippet. The Table model is a sqlalchemy model and the dates to search from are in the field day.
def last_relevant_date(db: Session, given_date: date) -> int:
available_days = (db.query(Table.id, Table.day)
.order_by(desc(Table.day))
.limit(100).all())
close_dates = pd.DataFrame(available_days)
close_dates['delta'] = close_dates['day'] - given_date
past_dates = (close_dates
.loc[close_dates['delta'] < pd.Timedelta(0, unit='d')])
table_id = int(past_dates.loc[past_dates['delta'].idxmax()]['id'])
return table_id
This is not a solution that I would recommend when you have to convert in bulk. It is rather generic and expensive as you are not using joins. Moreover, it assumes that you have a relevant day that is one of the 100 most recent days in the model Table. So it tackles data input that may have different dates.
Get first day of month, last day of month and last business day of previous month if last day falls on weekend Saturday/Sunday
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
day = datetime(2023, 1, 10)
#last day of (n) previous month (n=months)
#n = 0 -- for current month
n=1
lastDayMonth = ((day - relativedelta(months=n) + relativedelta(day=31)).date());
#First day of previous month (n=months=1)
firstDayMonth = ((day - relativedelta(months=n) + relativedelta(day=1)).date());
print("Last Day of Month - "+ str(lastDayMonth))
print("First Day of Month - "+ str(firstDayMonth))
#Last business day (Friday) of prev (n) month (n=months=1)
lastBusDay = (lastDayMonth - timedelta(max(1,(lastDayMonth.weekday() + 6) % 7 - 3))) if lastDayMonth.weekday() in (5,6) else lastDayMonth
print("Last Business Day of Month - " + str(lastBusDay))
print()
--- Output
Last Day of Month - 2022-12-31
First Day of Month - 2022-12-01
Last Business Day of Month - 2022-12-30

Categories