25

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!

spelchekr
  • 933
  • 3
  • 11
  • 19
  • I think the question had been answered here: https://stackoverflow.com/questions/11883058/nth-weekday-calculation-in-python-whats-wrong-with-this-code – mfolusiak Sep 27 '20 at 16:53

15 Answers15

39

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.

Community
  • 1
  • 1
Adam Smith
  • 52,157
  • 12
  • 73
  • 112
21

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
pourhaus
  • 566
  • 6
  • 9
  • 1
    Incredible solution, thank you. Just to clarify the comment `The 15th is the lowest third day in the month`: the earliest a 3rd Friday can happen in a month, is if the 1st day of the month is a Friday, so that 7 days later you have another Friday, and 7 days later you have the 3rd Friday (at the 15th day of the month). – Guilherme Salomé Mar 02 '18 at 00:03
  • It looks like a less general version of [`third_fridays()` function from @JuniorCompressor’s answer](https://stackoverflow.com/a/28681204/4279) (your version may return past dates) – jfs Oct 29 '20 at 19:32
16

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')
J Chow
  • 161
  • 1
  • 3
12

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]
JuniorCompressor
  • 19,631
  • 4
  • 30
  • 57
  • 1
    It looks like an efficient solution but it is not clear how and whether it works (it is easy to prove that it works: enumerate all possible starting dates for 400 years and compare the result with a brute force approach and/or solutions from other answers). An explanation on why `days = 14 + (4 - start_of_month.weekday()) % 7` and/or `next_friday += timedelta(days=28)` **always** works wouldn't hurt either. Use `calendar.FRIDAY` instead of `4` and/or use `timedelta(weeks=4)` instead of `timedelta(days=28)` for clarity. In Python `-1 % 7 == 6` that might be surprising. – jfs Feb 24 '15 at 06:14
  • 1
    `+timedelta(weeks=4)`-based formula fails e.g., for `d=2015-05-31` it returns `2015-06-12` but it should be `2015-06-19`. The formula `s = date(d.year, d.month, 15); s + timedelta(days=(calendar.FRIDAY - s.weekday()) % 7)` always works and returns the 3rd friday of the month. – jfs Feb 24 '15 at 12:38
  • Yeap I thought every third friday has distance 4 weeks. I corrected the implementation. – JuniorCompressor Feb 24 '15 at 13:28
  • `third_fridays()` produces the same result as [`get_third_fris()` from my answer](http://stackoverflow.com/a/28696431/4279). But [your solution is three times faster](https://gist.github.com/zed/11441b4776f7bbecc830). – jfs Feb 24 '15 at 15:35
  • I guess if we reuse timedelta instances, it'll be faster – JuniorCompressor Feb 24 '15 at 15:39
  • there is a small difference in results: my solution returns 3rd friday strictly in the future. Yours may return today as answer. It depends on how you interpret OP's *"3rd Fridays following the current date"* (whether to include the current date if it happens to be the 3rd Friday of the month). – jfs Feb 24 '15 at 15:49
  • 1
    The equivalent is to change `if result[0] < d` to `<=` – JuniorCompressor Feb 24 '15 at 15:54
6

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]
rocketman
  • 180
  • 3
  • 9
4

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
postelrich
  • 3,274
  • 5
  • 38
  • 65
  • You have the best answer here since you generalized. I'm going to go one further with it in my answer. – autonopy Mar 16 '22 at 13:06
3

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

Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
  • 1
    I need the 3rd Friday of the month, not the 3rd Friday after today. Although I could use this to just add 2 weeks from the 1st Friday of the month. How could I get that? – spelchekr Feb 23 '15 at 18:48
  • @spelchekr: I've added [`dateutil.relativedelta`-based solution](http://stackoverflow.com/a/28696431/4279). – jfs Feb 24 '15 at 16:20
3

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
nak
  • 690
  • 6
  • 15
2

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).

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • Forgive my ignorance. I am learning. How is your fridays(now) function not an infinite loop? WHat keeps it from going on forever, finding the fridays from now until eternity (or i guess some machine max)? – lukehawk Oct 19 '16 at 20:33
  • @lukehawk `next(islice` takes the 3rd Friday from the infinite `fridays()` generator (like `seq[2:3]`). See [What does the "yield" keyword do?](http://stackoverflow.com/q/231767/4279) – jfs Oct 20 '16 at 02:54
2

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]
MJMacarty
  • 537
  • 7
  • 17
2

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

autonopy
  • 429
  • 8
  • 12
  • Note that this code is buggy - produces a wrong value of 2022/5/1 for 2nd Monday in May 2022. – nak May 08 '22 at 14:35
0

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.

Pjl
  • 1,752
  • 18
  • 21
0

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)
0

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))

tantrix
  • 1,248
  • 12
  • 14
-2
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?

Joseph
  • 203
  • 1
  • 3
  • 11