Get the last thursday of the current month using python - python

Following this answer I tried to get the date for last Thursday of the current month. But my code doesn't get out of loop.
from datetime import datetime
from dateutil.relativedelta import relativedelta, TH
todayte = datetime.today()
cmon = todayte.month
nthu = todayte
while nthu.month == cmon:
nthu += relativedelta(weekday=TH(1))
#print nthu.strftime('%d%b%Y').upper()

Looking at the documentation of relativedelta
Notice that if the calculated date is already Monday, for example, using (0, 1) or (0, -1) won’t change the day.
If nthu is already Thursday then adding TH(1) or TH(-1) won't have any effect but result in the same date and that's why your loop is running infinitely.
I will assume maximum 5 weeks in a month and do it like following:
todayte = datetime.today()
cmon = todayte.month
for i in range(1, 6):
t = todayte + relativedelta(weekday=TH(i))
if t.month != cmon:
# since t is exceeded we need last one which we can get by subtracting -2 since it is already a Thursday.
t = t + relativedelta(weekday=TH(-2))
break

Based on Adam Smith's answer on How can I get the 3rd Friday of a month in Python?, you can get the date of the last Thursday of the current month as follows:
import calendar
import datetime
def get_thursday(cal,year,month,thursday_number):
'''
For example, get_thursday(cal, 2017,8,0) returns (2017,8,3)
because the first thursday of August 2017 is 2017-08-03
'''
monthcal = cal.monthdatescalendar(year, month)
selected_thursday = [day for week in monthcal for day in week if \
day.weekday() == calendar.THURSDAY and \
day.month == month][thursday_number]
return selected_thursday
def main():
'''
Show the use of get_thursday()
'''
cal = calendar.Calendar(firstweekday=calendar.MONDAY)
today = datetime.datetime.today()
year = today.year
month = today.month
date = get_thursday(cal,year,month,-1) # -1 because we want the last Thursday
print('date: {0}'.format(date)) # date: 2017-08-31
if __name__ == "__main__":
main()

You should pass 2 to TH instead of 1, as 1 doesn't change anything. Modify your code to:
while (nthu + relativedelta(weekday=TH(2))).month == cmon:
nthu += relativedelta(weekday=TH(2))
print nthu.strftime('%d-%b-%Y').upper()
# prints 26-MAY-2016
Note that I modified the loop's condition in order to break in the last occurrence of the day on the month, otherwise it'll break in the next month (in this case, June).

from datetime import datetime , timedelta
todayte = datetime.today()
cmon = todayte.month
nthu = todayte
while todayte.month == cmon:
todayte += timedelta(days=1)
if todayte.weekday()==3: #this is Thursday
nthu = todayte
print nthu

You can also use calendar package.
Access the calendar in the form of monthcalendar. and notice that, the Friday is the last day of a week.
import calendar
import datetime
now = datetime.datetime.now()
last_sunday = max(week[-1] for week in calendar.monthcalendar(now.year,
now.month))
print('{}-{}-{:2}'.format(now.year, calendar.month_abbr[now.month],
last_sunday))

I think this will be fastest perhaps:
end_of_month = datetime.datetime.today() + relativedelta(day=31)
last_thursday = end_of_month + relativedelta(weekday=TH(-1))

This code can be used in python 3.x for finding last Thursday of current month.
import datetime
dt = datetime.datetime.today()
def lastThurs(dt):
currDate, currMth, currYr = dt, dt.month, dt.year
for i in range(31):
if currDate.month == currMth and currDate.year == currYr and currDate.weekday() == 3:
#print('dt:'+ str(currDate))
lastThuDate = currDate
currDate += datetime.timedelta(1)
return lastThuDate

You can do something like this:
import pandas as pd
from dateutil.relativedelta import relativedelta, TH
expiry_type = 0
today = pd.datetime.today()
expiry_dates = []
if expiry_type == 0:
# Weekly expiry
for i in range(1,13):
expiry_dates.append((today + relativedelta(weekday=TH(i))).date())
else:
# Monthly expiry
for i in range(1,13):
x = (today + relativedelta(weekday=TH(i))).date()
y = (today + relativedelta(weekday=TH(i+1))).date()
if x.month != y.month :
if x.day > y.day :
expiry_dates.append(x)
print(expiry_dates)

import datetime
def get_thursday(_month,_year):
for _i in range(1,32):
if _i > 9:
_dateStr = str(_i)
else:
_dateStr = '0' + str(_i)
_date = str(_year) + '-' + str(_month) + '-' + _dateStr
try:
a = datetime.datetime.strptime(_date, "%Y-%m-%d").strftime('%a')
except:
continue
if a == 'Thu':
_lastThurs = _date
return _lastThurs
x = get_thursday('05','2017')
print(x)

It is straightforward fast and easy to understand, we take the first of every month and then subtract it by 3 coz 3 is Thursday weekday number and then multiply it by either 4 and check if it is in the same month if it is then that is last Thursday otherwise we multiply it with 3 and we get our last Thursday
import datetime as dt
from datetime import timedelta
#start is the first of every month
start = dt.datetime.fromisoformat('2022-08-01')
if start.month == (start + timedelta((3 - start.weekday()) + 4*7)).month:
exp = start + timedelta((3 - start.weekday()) + 4*7)
else:
exp = start + timedelta((3 - start.weekday()) + 3*7)

You can use Calendar to achieve your result. I find it simple.
import calendar
import datetime
testdate= datetime.datetime.now()
weekly_thursday=[]
for week in calendar.monthcalendar(testdate.year,testdate.month):
if week[3] != 0:
weekly_thursday.append(week[3])
weekly_thursday
The list weekly_thursday will have all the Thursdays from the month.
weekly_thursday[-1] will give you the last Thursday of the month.
testdate : datetime.datetime(2022, 9, 9, 6, 35, 16, 752465)
weekly_thursday : [1, 8, 15, 22, 29]
weekly_thursday [-1]:29

Related

Calculation of business working hour in python

I would like to write a function that calculate working business hours in python, to do that I don't like to define a class and use python ready function to calculate.
I tried with following code but the code is not working well. I need to modify the code and change it for the hour instead of minutes too.
Do you have any suggestion?
def getminutes(datetime1,datetime2,worktiming=[9, 17]):
day_hours = (worktiming[1]-worktiming[0])
day_minutes = day_hours * 60 # minutes in a work day
weekends=[6, 7]
# Set initial default variables
dt_start = datetime1.datetime # datetime of start
dt_end = datetime2.datetime # datetime of end
worktime_in_seconds = 0
if dt_start.date() == dt_end.date():
# starts and ends on same workday
full_days = 0
if dt_start in [6, 7]:
return 0
else:
if dt_start.hour < worktiming[0]:
# set start time to opening hour
dt_start = datetime.datetime(
year=dt_start.year,
month=dt_start.month,
day=dt_start.day,
hour=worktiming[0],
minute=0)
if dt_start.hour >= worktiming[1] or \
dt_end.hour < worktiming[0]:
return 0
if dt_end.hour >= worktiming[1]:
dt_end = datetime.datetime(
year=dt_end.year,
month=dt_end.month,
day=dt_end.day,
hour=worktiming[1],
minute=0)
worktime_in_seconds = (dt_end-dt_start).total_seconds()
elif (dt_end-dt_start).days < 0:
# ends before start
return 0
else:
# start and ends on different days
current_day = dt_start # marker for counting workdays
while not current_day.date() == dt_end.date():
if not is_weekend(current_day):
if current_day == dt_start:
# increment hours of first day
if current_day.hour < worktiming[0]:
# starts before the work day
worktime_in_seconds += day_minutes*60 # add 1 full work day
elif current_day.hour >= worktiming[1]:
pass # no time on first day
else:
# starts during the working day
dt_currentday_close = datetime.datetime(
year=dt_start.year,
month=dt_start.month,
day=dt_start.day,
hour= worktiming[1],
minute=0)
worktime_in_seconds += (dt_currentday_close
- dt_start).total_seconds()
else:
# increment one full day
worktime_in_seconds += day_minutes*60
current_day += datetime.timedelta(days=1) # next day
# Time on the last day
if not is_weekend(dt_end):
if dt_end.hour >= worktiming[1]: # finish after close
# Add a full day
worktime_in_seconds += day_minutes*60
elif dt_end.hour < worktiming[0]: # close before opening
pass # no time added
else:
# Add time since opening
dt_end_open = datetime.datetime(
year=dt_end.year,
month=dt_end.month,
day=dt_end.day,
hour=worktiming[0],
minute=0)
worktime_in_seconds += (dt_end-dt_end_open).total_seconds()
return int(worktime_in_seconds / 60)
How can I modify the code that works with the following input ?
getminutes(2019-12-02 09:30:00,2019-12-07 12:15:00,worktiming=[9, 17])
You can use pd.bdate_range(datetime1, datetime2) to compute the number of working days. When converting worktiming to a pandas datetime, it is easy to compute the difference (in seconds) between the two datetimes:
import pandas as pd
datetime1 = "2019-12-02 09:30:00"
datetime2 = "2019-12-07 12:15:00"
def getminutes(datetime1, datetime2, worktiming=[9, 17]):
d1 = pd.to_datetime(datetime1)
d2 = pd.to_datetime(datetime2)
wd = pd.bdate_range(d1, d2) # working days
day_hours = (worktiming[1] - worktiming[0])
day_minutes = day_hours * 60 # minutes in a work day
day_seconds = day_minutes * 60 # seconds in a work day
full_days = len(wd)
day1 = datetime1[:10]
day2 = datetime2[:10]
dt1 = pd.to_datetime(day1 + " " + str(worktiming[0]) + ":00")
dt2 = pd.to_datetime(day2 + " " + str(worktiming[1]) + ":00")
ex1, ex2 = 0, 0
if day1 in wd:
ex1 = max(pd.Timedelta(d1 - dt1).seconds, 0)
if day2 in wd:
ex2 = max(pd.Timedelta(dt2 - d2).seconds, 0)
total_seconds = full_days * day_seconds - ex1 - ex2
total_minutes = total_seconds / 60
total_hours = total_minutes / 60
return int(total_minutes)
print(getminutes(datetime1, datetime2))
Output: 2370

How to remove 0:00:00 from the output of datetime.date() in python?

How to remove 0:00:00 from the output of datetime.date() function? For example:
import datetime
now = datetime.datetime.now()
year1 = now.strftime("%Y")
month2 = now.strftime("%m")
day3 = now.strftime("%d")
year = int(year1)
month = int(month2)
day = int(day3)
first_day = datetime.date(2021,8,1)
second_day = datetime.date(year,month,day)
daysleft = first_day - second_day
print(daysleft)
I get the output:
9 days, 0:00:00
If you didn't understand the question title, My main goal is to remove the 0:00:00 before the period (.). I've seen many other questions like this (in stack overflow/exchange and other websites), but it was nothing in python coding language.
You can get just the days by asking for the .days attribute of a datetime.timedelta object. E.g.:
print('{} days'.format(daysleft.days))
If your only goal is printing the output and you don't care about maintaining daysleft as a datetime object, you can always convert it to a string and .split() it like this:
print(str(daysleft).split(",")[0])
datetime has many useful functions and values
To get only days you can daysleft.days
print(f'{daysleft.days} days')
but you can also reduce other code to
now = datetime.datetime.now()
daysleft = first_day - now.date()
or
today = datetime.date.today()
daysleft = first_day - today
BTW:
If you need year, month, day as numbers then you can get it as
year = now.year
month = now.month
day = now.day
hour = now.hour
minute = now.minute
second = now.second
ms = now.microsecond
weekday = now.weekday() # 0 = monday, ..., 6 = sunday
weekday = now.isoweekday() # 1 = monday, ..., 7 = sunday
Other useful function timedelta
one_day = datetime.timedelta(days=1)
today = datetime.date.today()
yesterday = today - one_day
tomorrow = today + one_day
day_after_tomorrow = today + 2*one_day
Minimal working code with tkinter
import datetime
first_day = datetime.date(2021, 8, 1)
now = datetime.datetime.now()
daysleft = first_day - now.date()
today = datetime.date.today()
daysleft = first_day - today
print(f'{daysleft.days} days')
# ---
import tkinter as tk
root = tk.Tk()
lbl = tk.Label(root, text=f'{daysleft.days} days till {first_day}')
lbl.pack()
root.mainloop()

Deleting files in a directory, keeping files created on a certain day of the month unless it's the weekend, then keeping files from following Monday

I have a script that deletes all files in a directory older than a specified number of days, but I want to keep files created on the 15th day of the month as an archive. I also need the day of the month to shift to the following Monday in months where the 15th falls on a weekend (keeping files creates on the 16th or 17th instead). I came up with a dirty way to do it, but hoping to get some input on a cleaner approach.
Here's my working code:
import datetime
import time
import os
for root, _, filenames in os.walk(directory_to_clean):
for filename in filenames:
file_path = f"{root}/{filename}"
created = os.path.getctime(file_path)
created_day = datetime.fromtimestamp(created).strftime("%d")
if created < time.time()-2592000: ### deleting older than 30 days
day_of_week = datetime.fromtimestamp(created).weekday()
#getting rid of anything with a creation day before the 15th or after the 17th, or if 16th/17th and not a Monday, or if 15th but on a weekend
if 15 <= int(created_day) <= 17 or (created_day == '16' and not weekday == 0) or (created_day == '17' and not weekday == 0) or (created_day == '15' and weekday > 4):
os.remove(file_path)
Your code seems OK but datetime has some functions to make it cleaner
If you convert timestamp to datetime
created_datetime = datetime.datetime.fromtimestamp(created_timestamp)
then you can get day directly as integer
created_day = created_datetime.day
You can also get date without time to compare with other date
created_date = created_datetime.date()
Today date (without time) you can get with
today = datetime.date.today()
or
today = datetime.datetime.now().date()
and then you can get 30 days before using
before_30_days = today - datetime.timedelta(days=30)
or
one_day = datetime.timedelta(days=1)
before_30_days = today - 30*one_day
And you can compare date (without time)
created_date < before_30_days
import datetime
import time
import os
# calculate only once
today = datetime.date.today()
before_30_days = today - datetime.timedelta(days=30)
#one_day = datetime.timedelta(days=1)
#before_30_days = today - 30*one_day
for root, _, filenames in os.walk('test'):
for filename in filenames:
file_path = os.path.join(root, filename)
created_timestamp = os.path.getctime(file_path)
created_datetime = datetime.datetime.fromtimestamp(created_timestamp)
created_date = created_datetime.date()
created_day = created_datetime.day
#print(created_day)
if created_date < before_30_days: ### deleting older than 30 days
day_of_week = created_datetime.weekday() # monday = 0
#day_of_week = created_datetime.isoweekday() # monday = 1
#print(created_date, '|', day_of_week)
# getting rid of anything with a creation day before the 15th or after the 17th,
# or if 16th/17th and not a Monday, or if 15th but on a weekend
if (15 <= created_day <= 17
or (created_day == 16 and not weekday == 0)
or (created_day == 17 and not weekday == 0)
or (created_day == 15 and weekday > 4)):
#os.remove(file_path)
print(created_date, '|', day_of_week, '|', file_path)
I took a different approach and broke things up a bit into smaller functions. (I also may have misread your logic for which files to keep). I tried to get the removal logic down a check for older than X days and NOT created on the day of the month to reserve:
import os
import sys
import datetime
def get_created_info(file_path: str) -> tuple:
created_timestamp = os.path.getctime(file_path)
created_datetime = datetime.datetime.fromtimestamp(created_timestamp)
return created_datetime.date(), created_datetime.day
def get_day_to_keep(today) -> int:
target = datetime.datetime(today.year, today.month, 15)
if target.weekday() == 5: # saturday, so forward 2 days
target=datetime.datetime(today.year, today.month, 17)
if target.weekday() == 6: # sunday, so forward 1 day
target=datetime.datetime(today.year, today.month, 16)
return target.day # return day of month
def remove_old_files(today, number_of_days, path_to_search:str):
age_in_days = today - datetime.timedelta(days=number_of_days)
day_to_keep = get_day_to_keep(today)
for root, _, filenames in os.walk(path_to_search):
for filename in filenames:
file_path = os.path.join(root, filename)
if not os.path.isdir(file_path):
created_date, created_day = get_created_info(file_path)
# remove if too old and NOT created on day to keep
if created_date < age_in_days and created_day != day_to_keep:
#os.remove(file_path)
print('X - removed: ', created_date, '|', created_day, '|', file_path)
else:
print('OK - keep: ', created_date, '|', created_day, '|', file_path)
if __name__ == "__main__":
###########################################################
# modify the 'path_to_search' variable or pass in a command
# line parameter that represents the path to search for
# files to remove.
#
# Usage:
# python script.py /path/to/dir
# -- or --
# python script.py
#
###################################################
path_to_search='/tmp/samples'
if len(sys.argv) == 2:
path_to_search=sys.argv[1]
remove_old_files(datetime.date.today(), 30, path_to_search)

Datetime: Check if date is in 1 week

What I am trying to do is see if date is in 1 week from currdate
from datetime import datetime, timedelta
import yagmail
year = datetime.now().year
month = datetime.now().month
day = datetime.now().day
currdate = '{}-{}-{}'.format(year, month, day)
currdate = datetime.strptime(currdate, '%Y-%m-%d')
date = '2018-04-01'
days = currdate - timedelta(int(date[-2:]))
days = str(days)
print(days)
if days[8:11] == '07':
yag = yagmail.SMTP("##########gmail.com", "######")
content = ['One Of Your Homework\'s Is Due In 1 Week!']
yag.send('###########gmail.com', 'Homework Due Soon!', content)
else:
print('It Isn\'t')
But it prints:
2018-04-07 00:00:00
It Isnt't
And I'm not sure why. Because days[8:11] is 07.
It is not 07. It's 07 (note the trailing space).
The following change will work:
if int(days[8:11]) == 7:
I'd create a function that you pass the date as a string. Something like this:
import datetime
def check_if_less_than_seven_days(x):
d = datetime.datetime.strptime(x, "%Y-%m-%d") # Add .date() if hour doesn't matter
now = datetime.datetime.now() # Add .date() if hour doesn't matter
return (d - now).days < 7
if check_if_less_than_seven_days("2018-04-18"):
print('Do something') # This will not print
if check_if_less_than_seven_days("2018-04-14"):
print('Do something') # This will print
Will print:
'Do something'
I suppose your first line when you initiate datetime.now() three times is just for testing purposes but dont do this as it could end up over different days (if you run this exactly at the milliseconds around midnight..) this will work better in that regard.
now = datetime.datetime.now()
year = now.year
month = now.month
day = now.day
Anyway, read up on datetime timedelta. Just make you logic around that.
https://docs.python.org/3/library/datetime.html#timedelta-objects
import datetime
test_date_string = "2018-04-10"
d = datetime.datetime.strptime(test_date_string, "%Y-%m-%d")
now = datetime.datetime.now()
delta = d - now
elif delta.days < 7:
print("You have less then 7 days to go")
For days[8:11] you get the following output
>>> days[8:11]
'08 '
So you should use days[8:10]=='07' in case you want to use the same method,as it wont have extra space at the end.
>>> days[8:10]
'08'
so you should use
if days[8:10] == '07':

A simple mathematic Operation python

I have a method which returns the number of days within a specified range and excluding some specific days like Friday. here is an example if you take out friday and thursday, from 2016-8-6 to 2016-9-6 the result will be 8 days holiday and 24 working day. in case i want to do the reverse operation how do i find the end date (2016-9-6) if i have only working days and start date.
from datetime import datetime, timedelta
def measure_workingdays(start_date, end_date, off_days):
format = "%Y-%m-%d"
if not isinstance(start_date, datetime):
start_date = datetime.strptime(start_date, format)
if not isinstance(end_date, datetime):
end_date = datetime.strptime(end_date, format)
total_days = (end_date - start_date).days + 1 # + 1 Because it count one day less
holiday = 0
start = start_date
for rec in range(total_days):
day = start.strftime("%a")
if day in off_days:
holiday += 1
start += timedelta(days=1)
print(holiday) # 8
working_days = total_days - holiday
print(working_days) # 24
start_date = "2016-8-6"
start_date = datetime.strptime(start_date, "%Y-%m-%d")
end_date = "2016-9-6"
end_date = datetime.strptime(end_date, "%Y-%m-%d")
off_day = ['Fri','Thu']
working_days = measure_weekdays(start_date, end_date, off_day)
Example of Reverse operation
def measure_weekdays_reverse(start_date, paid, off_days):
format = "%Y-%m-%d"
if not isinstance(start_date, datetime):
start_date = datetime.strptime(start_date, format)
holiday = 0
start = start_date
for rec in range(paid):
day = start.strftime("%a")
if day in off_days:
holiday += 1
start += timedelta(days=1)
print(holiday) # Output 6 instead of 8
last_paid_date = start + timedelta(days=holiday)
print(last_paid_date) # output 2016-09-05 insteaad of 2016-09-06
total_days = measure_weekdays_reverse(start_date, 24, ["Fri","Thu"])
The error is that you only loop a fix number of times (the number of paid days), so if you encounter holidays, you will in fact not iterate enough to find all the true paid days, which may still hide some holidays.
You can fix this by adding an inner loop on the holidays. Change this:
for rec in range(paid):
day = start.strftime("%a")
if day in off_days:
holiday += 1
start += timedelta(days=1)
print(holiday) # Output 6 instead of 8
last_paid_date = start + timedelta(days=holiday)
to this:
for rec in range(paid):
day = start.strftime("%a")
while day in off_days:
holiday += 1
start += timedelta(days=1)
day = start.strftime("%a")
last_paid_date = start
start += timedelta(days=1)
print(holiday)

Categories

Resources