I have the following problem: Given a year and a month, determine the week numbers that are contained in that particular month, i.e., October 2017 contains week numbers 39,40,41,42,43,44. Caveat: As a rule, week starts in Monday and ends in Sunday. I have a cumbersome code that does the trick but I would like to know if there is a more elegant or Pythonistic way of doing this.
from datetime import datetime
import calendar
def get_week_numbers_in_month(year,month):
list_of_weeks = []
initial_day = 1
ending_day = calendar.monthrange(int(year),int(month))[1] #get the last day of month
initial_week = int(datetime(year,month,initial_day).isocalendar()[1])
ending_week = int(datetime(year,month,ending_day).isocalendar()[1])
counter = initial_week
while(counter <= ending_week):
list_of_weeks.append(counter)
counter += 1
return list_of_weeks
print("Your month contains the following weeks:\n"+str(get_week_numbers_in_month(2017,10)))
# result: Your month contains the following weeks:
# [39, 40, 41, 42, 43, 44]
You could simply return a range and avoid the whole initializing process. Also, note that isocalendar returns a 3-tuple of integers:
from datetime import datetime
import calendar
def get_week_numbers_in_month(year,month):
ending_day = calendar.monthrange(year, month)[1] #get the last day of month
initial_week = datetime(year, month, 1).isocalendar()[1]
ending_week = datetime(year, month, ending_day).isocalendar()[1]
return range(initial_week, ending_week + 1)
print("Your month contains the following weeks:")
print(get_week_numbers_in_month(2017,10))
# range(39, 45)
If you really want to return a list, simply return list(range(...))
My approach 😊
from datetime import datetime
import calendar
def get_week_numbers_in_month(year, month) -> list:
ending_day = calendar.monthrange(year, month)[1] # get the last day of month
initial_week = datetime(year, month, 1).isocalendar()[1]
ending_week = datetime(year, month, ending_day).isocalendar()[1]
res = list(range(initial_week, ending_week + 1))
if not res:
week_num = 52 if initial_week != 53 else 53
res = list(range(initial_week, week_num + 1)) + list(range(1, ending_week + 1))
return res
Output
>>> print(get_week_numbers_in_month(2021, 1))
[52, 1, 2, 3, 4]
>>>
I would have added this as a comment, but do not have enough reputation to do so...
It looks like the approved answer does not actually work for the month of December as it will sometimes spill over into week 1. Also, why is it adding a week to the end when it creates the range?
Accepted answer:
def get_week_numbers_in_month(year,month):
ending_day = calendar.monthrange(year, month)[1] #get the last day of month
initial_week = datetime(year, month, 1).isocalendar()[1]
ending_week = datetime(year, month, ending_day).isocalendar()[1]
return range(initial_week, ending_week + 1)
print(get_week_numbers_in_month(2019,12))
# range(48, 2)
print(list(get_week_numbers_in_month(2019,12)))
# []
You would actually need to add some additional logic to build the list yourself instead of using a range
def get_week_numbers_in_month(year,month):
ending_day = calendar.monthrange(year, month)[1] #get the last day of month
initial_week = datetime(year, month, 1).isocalendar()[1]
ending_week = datetime(year, month, ending_day).isocalendar()[1]
# Adding logic to build week list if month is December (12)
if month = 12:
week_list = []
while week != ending_week:
if week not in week_list:
week_list.append(week)
else:
if week >= 52:
week = 1
else:
week += 1
week_list.append(week)
else:
week_list = list(range(initial_week, ending_week))
return week_list
print(get_week_numbers_in_month(2019,12))
# [48, 49, 50, 51, 52, 1]
Related
This question already has answers here:
How do I calculate the date six months from the current date using the datetime Python module?
(47 answers)
Closed 7 years ago.
I need to increment the month of a datetime value
next_month = datetime.datetime(mydate.year, mydate.month+1, 1)
when the month is 12, it becomes 13 and raises error "month must be in 1..12". (I expected the year would increment)
I wanted to use timedelta, but it doesn't take month argument.
There is relativedelta python package, but i don't want to install it just only for this.
Also there is a solution using strtotime.
time = strtotime(str(mydate));
next_month = date("Y-m-d", strtotime("+1 month", time));
I don't want to convert from datetime to str then to time, and then to datetime; therefore, it's still a library too
Does anyone have any good and simple solution just like using timedelta?
This is short and sweet method to add a month to a date using dateutil's relativedelta.
from datetime import datetime
from dateutil.relativedelta import relativedelta
date_after_month = datetime.today()+ relativedelta(months=1)
print('Today: ',datetime.today().strftime('%d/%m/%Y'))
print('After Month:', date_after_month.strftime('%d/%m/%Y'))
Today: 01/03/2013
After Month: 01/04/2013
A word of warning: relativedelta(months=1) and relativedelta(month=1) have different meanings. Passing month=1 will replace the month in original date to January whereas passing months=1 will add one month to original date.
Note: this will require python-dateutil module. If you are on Linux you need to run this command in the terminal in order to install it.
sudo apt-get update && sudo apt-get install python-dateutil
Explanation : Add month value in python
Edit - based on your comment of dates being needed to be rounded down if there are fewer days in the next month, here is a solution:
import datetime
import calendar
def add_months(sourcedate, months):
month = sourcedate.month - 1 + months
year = sourcedate.year + month // 12
month = month % 12 + 1
day = min(sourcedate.day, calendar.monthrange(year,month)[1])
return datetime.date(year, month, day)
In use:
>>> somedate = datetime.date.today()
>>> somedate
datetime.date(2010, 11, 9)
>>> add_months(somedate,1)
datetime.date(2010, 12, 9)
>>> add_months(somedate,23)
datetime.date(2012, 10, 9)
>>> otherdate = datetime.date(2010,10,31)
>>> add_months(otherdate,1)
datetime.date(2010, 11, 30)
Also, if you're not worried about hours, minutes and seconds you could use date rather than datetime. If you are worried about hours, minutes and seconds you need to modify my code to use datetime and copy hours, minutes and seconds from the source to the result.
Here's my salt :
current = datetime.datetime(mydate.year, mydate.month, 1)
next_month = datetime.datetime(mydate.year + int(mydate.month / 12), ((mydate.month % 12) + 1), 1)
Quick and easy :)
since no one suggested any solution, here is how i solved so far
year, month= divmod(mydate.month+1, 12)
if month == 0:
month = 12
year = year -1
next_month = datetime.datetime(mydate.year + year, month, 1)
Use the monthdelta package, it works just like timedelta but for calendar months rather than days/hours/etc.
Here's an example:
from monthdelta import MonthDelta
def prev_month(date):
"""Back one month and preserve day if possible"""
return date + MonthDelta(-1)
Compare that to the DIY approach:
def prev_month(date):
"""Back one month and preserve day if possible"""
day_of_month = date.day
if day_of_month != 1:
date = date.replace(day=1)
date -= datetime.timedelta(days=1)
while True:
try:
date = date.replace(day=day_of_month)
return date
except ValueError:
day_of_month -= 1
from datetime import timedelta
try:
next = (x.replace(day=1) + timedelta(days=31)).replace(day=x.day)
except ValueError: # January 31 will return last day of February.
next = (x + timedelta(days=31)).replace(day=1) - timedelta(days=1)
If you simply want the first day of the next month:
next = (x.replace(day=1) + timedelta(days=31)).replace(day=1)
To calculate the current, previous and next month:
import datetime
this_month = datetime.date.today().month
last_month = datetime.date.today().month - 1 or 12
next_month = (datetime.date.today().month + 1) % 12 or 12
Perhaps add the number of days in the current month using calendar.monthrange()?
import calendar, datetime
def increment_month(when):
days = calendar.monthrange(when.year, when.month)[1]
return when + datetime.timedelta(days=days)
now = datetime.datetime.now()
print 'It is now %s' % now
print 'In a month, it will be %s' % increment_month(now)
What about this one? (doesn't require any extra libraries)
from datetime import date, timedelta
from calendar import monthrange
today = date.today()
month_later = date(today.year, today.month, monthrange(today.year, today.month)[1]) + timedelta(1)
Simplest solution is to go at the end of the month (we always know that months have at least 28 days) and add enough days to move to the next moth:
>>> from datetime import datetime, timedelta
>>> today = datetime.today()
>>> today
datetime.datetime(2014, 4, 30, 11, 47, 27, 811253)
>>> (today.replace(day=28) + timedelta(days=10)).replace(day=today.day)
datetime.datetime(2014, 5, 30, 11, 47, 27, 811253)
Also works between years:
>>> dec31
datetime.datetime(2015, 12, 31, 11, 47, 27, 811253)
>>> today = dec31
>>> (today.replace(day=28) + timedelta(days=10)).replace(day=today.day)
datetime.datetime(2016, 1, 31, 11, 47, 27, 811253)
Just keep in mind that it is not guaranteed that the next month will have the same day, for example when moving from 31 Jan to 31 Feb it will fail:
>>> today
datetime.datetime(2016, 1, 31, 11, 47, 27, 811253)
>>> (today.replace(day=28) + timedelta(days=10)).replace(day=today.day)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: day is out of range for month
So this is a valid solution if you need to move to the first day of the next month, as you always know that the next month has day 1 (.replace(day=1)). Otherwise, to move to the last available day, you might want to use:
>>> today
datetime.datetime(2016, 1, 31, 11, 47, 27, 811253)
>>> next_month = (today.replace(day=28) + timedelta(days=10))
>>> import calendar
>>> next_month.replace(day=min(today.day,
calendar.monthrange(next_month.year, next_month.month)[1]))
datetime.datetime(2016, 2, 29, 11, 47, 27, 811253)
Similar in ideal to Dave Webb's solution, but without all of that tricky modulo arithmetic:
import datetime, calendar
def increment_month(date):
# Go to first of this month, and add 32 days to get to the next month
next_month = date.replace(day=1) + datetime.timedelta(32)
# Get the day of month that corresponds
day = min(date.day, calendar.monthrange(next_month.year, next_month.month)[1])
return next_month.replace(day=day)
This implementation might have some value for someone who is working with billing.
If you are working with billing, you probably want to get "the same date next month (if possible)" as opposed to "add 1/12 of one year".
What is so confusing about this is you actually need take into account two values if you are doing this continuously. Otherwise for any dates past the 27th, you'll keep losing a few days until you end up at the 27th after leap year.
The values you need to account for:
The value you want to add a month to
The day you started with
This way if you get bumped from the 31st down to the 30th when you add one month, you'll get bumped back up to the 31st for the next month that has that day.
This is how I did it:
def closest_date_next_month(year, month, day):
month = month + 1
if month == 13:
month = 1
year = year + 1
condition = True
while condition:
try:
return datetime.datetime(year, month, day)
except ValueError:
day = day-1
condition = day > 26
raise Exception('Problem getting date next month')
paid_until = closest_date_next_month(
last_paid_until.year,
last_paid_until.month,
original_purchase_date.day) # The trick is here, I'm using the original date, that I started adding from, not the last one
Well with some tweaks and use of timedelta here we go:
from datetime import datetime, timedelta
def inc_date(origin_date):
day = origin_date.day
month = origin_date.month
year = origin_date.year
if origin_date.month == 12:
delta = datetime(year + 1, 1, day) - origin_date
else:
delta = datetime(year, month + 1, day) - origin_date
return origin_date + delta
final_date = inc_date(datetime.today())
print final_date.date()
I was looking to solve the related problem of finding the date for the first of the following month, regardless of the day in the given date. This does not find the same day 1 month later.
So, if all you want is to put in December 12, 2014 (or any day in December) and get back January 1, 2015, try this:
import datetime
def get_next_month(date):
month = (date.month % 12) + 1
year = date.year + (date.month + 1 > 12)
return datetime.datetime(year, month, 1)
A solution without the use of calendar:
def add_month_year(date, years=0, months=0):
year, month = date.year + years, date.month + months + 1
dyear, month = divmod(month - 1, 12)
rdate = datetime.date(year + dyear, month + 1, 1) - datetime.timedelta(1)
return rdate.replace(day = min(rdate.day, date.day))
def add_month(d,n=1): return type(d)(d.year+(d.month+n-1)/12, (d.month+n-1)%12+1, 1)
Just Use This:
import datetime
today = datetime.datetime.today()
nextMonthDatetime = today + datetime.timedelta(days=(today.max.day - today.day)+1)
This is what I came up with
from calendar import monthrange
def same_day_months_after(start_date, months=1):
target_year = start_date.year + ((start_date.month + months) / 12)
target_month = (start_date.month + months) % 12
num_days_target_month = monthrange(target_year, target_month)[1]
return start_date.replace(year=target_year, month=target_month,
day=min(start_date.day, num_days_target_month))
def month_sub(year, month, sub_month):
result_month = 0
result_year = 0
if month > (sub_month % 12):
result_month = month - (sub_month % 12)
result_year = year - (sub_month / 12)
else:
result_month = 12 - (sub_month % 12) + month
result_year = year - (sub_month / 12 + 1)
return (result_year, result_month)
def month_add(year, month, add_month):
return month_sub(year, month, -add_month)
>>> month_add(2015, 7, 1)
(2015, 8)
>>> month_add(2015, 7, 20)
(2017, 3)
>>> month_add(2015, 7, 12)
(2016, 7)
>>> month_add(2015, 7, 24)
(2017, 7)
>>> month_add(2015, 7, -2)
(2015, 5)
>>> month_add(2015, 7, -12)
(2014, 7)
>>> month_add(2015, 7, -13)
(2014, 6)
example using the time object:
start_time = time.gmtime(time.time()) # start now
#increment one month
start_time = time.gmtime(time.mktime([start_time.tm_year, start_time.tm_mon+1, start_time.tm_mday, start_time.tm_hour, start_time.tm_min, start_time.tm_sec, 0, 0, 0]))
My very simple solution, which doesn't require any additional modules:
def addmonth(date):
if date.day < 20:
date2 = date+timedelta(32)
else :
date2 = date+timedelta(25)
date2.replace(date2.year, date2.month, day)
return date2
I'm building out a forecast model in Python and need to calculate the number of remaining days for each day name of a month. For example, on October 25, 2017 there are no remaining Wednesdays in the current month and there is 1 remaining Thursday, Friday, Saturday, Sunday, Monday, and Tuesday.
I'm able to achieve the same result in R with:
first <- as.Date(cut(Sys.Date(), "month"))
last <- as.Date(cut(first + 31, "month")) - 1
sum(format(seq(Sys.Date(), last, "day"), "%w") == 0) -> Sunday
I'm trying to edit the following code block which was initially intended to count weekdays in a month for my purposes but not sure if I'm on the right track
import calendar
weekday_count = 0
cal = calendar.Calendar()
for week in cal.monthdayscalendar(2013, 8):
for i, day in enumerate(week):
# not this month's day or a weekend
if day == 0 or i >= 5:
continue
# or some other control if desired...
weekday_count += 1
print(weekday_count)
You might want to use this as a starting point. It will calculate the number of remaining Mondays given a day, month, year of your choosing.
from datetime import datetime, timedelta
# Monday = 0
def get_remaining_mondays(year, month, day):
count = 0
try:
d = datetime(year, month, day)
wday = d.weekday()
d += timedelta(days=7 - wday)
while d.month == month:
count += 1
d += timedelta(days=7)
except ValueError as v:
return v
return count
print(get_remaining_mondays(2017, 11, 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).
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']
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?