Return list of tuples of date ranges I have a start and end date. I would like to return a list of tuples, which have the start and end dates of each month.
Input
start_date = '2020-10-14'
end_date = '2021-03-26'

Output
[('2020-10-14', '2020-10-31'),
 ('2020-11-01', '2020-11-30'),
 ('2020-12-01', '2020-12-31'),
 ('2021-01-01', '2021-01-31'),
 ('2021-02-01', '2021-02-28'),
 ('2021-03-01', '2021-03-26')]

My code
from datetime import datetime, timedelta, date
import calendar

def get_first_day(year, month):
    return f'{year}-{str(month).zfill(2)}-01'

def get_last_day(year, month):
    # https://stackoverflow.com/a/23447523/5675288
    dt = datetime.strptime(f'{year}-{month}', '%Y-%m')
    dt = (dt.replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1)
    return dt.strftime('%Y-%m-%d')

def diff_month(d1, d2):
    # https://stackoverflow.com/a/4040338/5675288
    return abs((d1.year - d2.year) * 12 + d1.month - d2.month)

def add_months(sourcedate, months):
    # https://stackoverflow.com/a/4131114/5675288
    month = sourcedate.month - 1 + months
    year = sourcedate.year + month // 12
    month = month % 12 + 1
    day = min(sourcedate.day, calendar.monthrange(year,month)[1])
    return date(year, month, day)

def get_dates(start_date, end_date):
    &quot;&quot;&quot;get list of tuples of dates
    [
        (start_date, end-of-month),
        (start-of-next-month, end-of-next-month),
        (start-of-next-next-month, end-of-next-next-month),
        ...
        (start-of-last-month, end_date)
    ]
    &quot;&quot;&quot;
    sd = datetime.strptime(f'{start_date}', '%Y-%m-%d')
    ed = datetime.strptime(f'{end_date}', '%Y-%m-%d')

    diff_months = diff_month(sd, ed)

    first_days = []
    last_days = []

    for month in range(diff_months+1):
        d = add_months(sd, month)
        first_days.append(get_first_day(d.year, d.month))
        last_days.append(get_last_day(d.year, d.month))

    first_days = [start_date] + first_days[1:]
    last_days = last_days[:-1] + [end_date]

    return list(zip(first_days, last_days))

start_date = '2020-10-14'
end_date = '2021-03-26'

print(get_dates(start_date, end_date))

Is there a cleaner / better approach for this?
None of this code should deal in strings, except maybe the final output stage. It can all be thrown away in favour of a single generator function that uses the highly-useful dateutil library. Also type hints will help:
from datetime import date
from typing import Iterable, Tuple
from dateutil.relativedelta import relativedelta


def get_dates(start: date, end: date) -&gt; Iterable[Tuple[date, date]]:

    while True:
        next_start = start + relativedelta(months=1, day=1)
        this_end = next_start - relativedelta(days=1)

        if end &lt;= this_end:
            yield start, end
            break

        yield start, this_end
        start = next_start


def main() -&gt; None:
    start = date.fromisoformat(input('Enter start date: '))
    end = date.fromisoformat(input('Enter end date: '))
    for range_start, range_end in get_dates(start, end):
        print(f'{range_start.isoformat()} - {range_end.isoformat()}')

if __name__ == '__main__':
    main()

Output:
Enter start date: 2020-10-14
Enter end date: 2021-03-26
2020-10-14 - 2020-10-31
2020-11-01 - 2020-11-30
2020-12-01 - 2020-12-31
2021-01-01 - 2021-01-31
2021-02-01 - 2021-02-28
2021-03-01 - 2021-03-26