Best way to find the months between two dates

Viewed 225693

I have the need to be able to accurately find the months between two dates in python. I have a solution that works but its not very good (as in elegant) or fast.

dateRange = [datetime.strptime(dateRanges[0], "%Y-%m-%d"), datetime.strptime(dateRanges[1], "%Y-%m-%d")]
months = [] 

tmpTime = dateRange[0]
oneWeek = timedelta(weeks=1)
tmpTime = tmpTime.replace(day=1)
dateRange[0] = tmpTime
dateRange[1] = dateRange[1].replace(day=1)
lastMonth = tmpTime.month
months.append(tmpTime)
while tmpTime < dateRange[1]:
    if lastMonth != 12:
        while tmpTime.month <= lastMonth:
            tmpTime += oneWeek
        tmpTime = tmpTime.replace(day=1)
        months.append(tmpTime)
        lastMonth = tmpTime.month

    else:
        while tmpTime.month >= lastMonth:
            tmpTime += oneWeek
        tmpTime = tmpTime.replace(day=1)
        months.append(tmpTime)
        lastMonth = tmpTime.month

So just to explain, what I'm doing here is taking the two dates and converting them from iso format into python datetime objects. Then I loop through adding a week to the start datetime object and check if the numerical value of the month is greater (unless the month is December then it checks if the date is less), If the value is greater I append it to the list of months and keep looping through until I get to my end date.

It works perfectly it just doesn't seem like a good way of doing it...

40 Answers
from dateutil import relativedelta

r = relativedelta.relativedelta(date1, date2)

months_difference = (r.years * 12) + r.months

My simple solution:

import datetime

def months(d1, d2):
    return d1.month - d2.month + 12*(d1.year - d2.year)

d1 = datetime.datetime(2009, 9, 26)  
d2 = datetime.datetime(2019, 9, 26) 

print(months(d1, d2))

Many people have already given you good answers to solve this but I have not read any using list comprehension so I give you what I used for a similar use case :


def compute_months(first_date, second_date):
    year1, month1, year2, month2 = map(
        int, 
        (first_date[:4], first_date[5:7], second_date[:4], second_date[5:7])
    )

    return [
        '{:0>4}-{:0>2}'.format(year, month)
        for year in range(year1, year2 + 1)
        for month in range(month1 if year == year1 else 1, month2 + 1 if year == year2 else 13)
    ]

>>> first_date = "2016-05"
>>> second_date = "2017-11"
>>> compute_months(first_date, second_date)
['2016-05',
 '2016-06',
 '2016-07',
 '2016-08',
 '2016-09',
 '2016-10',
 '2016-11',
 '2016-12',
 '2017-01',
 '2017-02',
 '2017-03',
 '2017-04',
 '2017-05',
 '2017-06',
 '2017-07',
 '2017-08',
 '2017-09',
 '2017-10',
 '2017-11']

Get difference in number of days, months and years between two dates.

import datetime    
from dateutil.relativedelta import relativedelta


iphead_proc_dt = datetime.datetime.now()
new_date = iphead_proc_dt + relativedelta(months=+25, days=+23)

# Get Number of Days difference bewtween two dates
print((new_date - iphead_proc_dt).days)

difference = relativedelta(new_date, iphead_proc_dt)

# Get Number of Months difference bewtween two dates
print(difference.months + 12 * difference.years)

# Get Number of Years difference bewtween two dates
print(difference.years)

It can be done using datetime.timedelta, where the number of days for skipping to next month can be obtained by calender.monthrange. monthrange returns weekday (0-6 ~ Mon-Sun) and number of days (28-31) for a given year and month.
For example: monthrange(2017, 1) returns (6,31).

Here is the script using this logic to iterate between two months.

from datetime import timedelta
import datetime as dt
from calendar import monthrange

def month_iterator(start_month, end_month):
    start_month = dt.datetime.strptime(start_month,
                                   '%Y-%m-%d').date().replace(day=1)
    end_month = dt.datetime.strptime(end_month,
                                 '%Y-%m-%d').date().replace(day=1)
    while start_month <= end_month:
        yield start_month
        start_month = start_month + timedelta(days=monthrange(start_month.year, 
                                                         start_month.month)[1])

`

from datetime import datetime
from dateutil import relativedelta

def get_months(d1, d2):
    date1 = datetime.strptime(str(d1), '%Y-%m-%d')
    date2 = datetime.strptime(str(d2), '%Y-%m-%d')
    print (date2, date1)
    r = relativedelta.relativedelta(date2, date1)
    months = r.months +  12 * r.years
    if r.days > 0:
        months += 1
    print (months)
    return  months


assert  get_months('2018-08-13','2019-06-19') == 11
assert  get_months('2018-01-01','2019-06-19') == 18
assert  get_months('2018-07-20','2019-06-19') == 11
assert  get_months('2018-07-18','2019-06-19') == 12
assert  get_months('2019-03-01','2019-06-19') == 4
assert  get_months('2019-03-20','2019-06-19') == 3
assert  get_months('2019-01-01','2019-06-19') == 6
assert  get_months('2018-09-09','2019-06-19') == 10

it seems that the answers are unsatisfactory and I have since use my own code which is easier to understand

from datetime import datetime
from dateutil import relativedelta

date1 = datetime.strptime(str('2017-01-01'), '%Y-%m-%d')
date2 = datetime.strptime(str('2019-03-19'), '%Y-%m-%d')

difference = relativedelta.relativedelta(date2, date1)
months = difference.months
years = difference.years
# add in the number of months (12) for difference in years
months += 12 * difference.years
months

Here is my solution for this:

def calc_age_months(from_date, to_date):
    from_date = time.strptime(from_date, "%Y-%m-%d")
    to_date = time.strptime(to_date, "%Y-%m-%d")

    age_in_months = (to_date.tm_year - from_date.tm_year)*12 + (to_date.tm_mon - from_date.tm_mon)

    if to_date.tm_mday < from_date.tm_mday:
        return age_in_months -1
    else
        return age_in_months

This will handle some edge cases as well where the difference in months between 31st Dec 2018 and 1st Jan 2019 will be zero (since the difference is only a day).

This is my way to do this:

Start_date = "2000-06-01"
End_date   = "2001-05-01"

month_num  = len(pd.date_range(start = Start_date[:7], end = End_date[:7] ,freq='M'))+1

I just use the month to create a date range and calculate the length.

To get the number of full months between two dates:

import datetime

def difference_in_months(start, end):
    if start.year == end.year:
        months = end.month - start.month
    else:
        months = (12 - start.month) + (end.month)

    if start.day > end.day:
        months = months - 1

    return months

You can use the below code to get month between two dates:

OrderedDict(((start_date + timedelta(_)).strftime(date_format), None) for _ in xrange((end_date - start_date).days)).keys()

where start_date and end_date must be proper date and date_format is the format in which you want your result of date..

In your case, date_format will be %b %Y.

The question, is really asking about the total months between 2 dates and not the difference of it

Hence a revisited answer with some extra functionallity,

from datetime import date, datetime
from dateutil.rrule import rrule, MONTHLY

def month_get_list(dt_to, dt_from, return_datetime=False, as_month=True):
    INDEX_MONTH_MAPPING = {1: 'january', 2: 'february', 3: 'march', 4: 'april', 5: 'may', 6: 'june', 7: 'july',
                           8: 'august',
                           9: 'september', 10: 'october', 11: 'november', 12: 'december'}
    if return_datetime:
        return [dt for dt in rrule(MONTHLY, dtstart=dt_from, until=dt_to)]
    if as_month:
        return [INDEX_MONTH_MAPPING[dt.month] for dt in rrule(MONTHLY, dtstart=dt_from, until=dt_to)]

    return [dt.month for dt in rrule(MONTHLY, dtstart=dt_from, until=dt_to)]

month_list = month_get_list(date(2021, 12, 31), date(2021, 1, 1))
total_months = len(month_list)

Result

month_list = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']
total_months = 12

With as_month set to False

month_list = month_get_list(date(2021, 12, 31), date(2021, 1, 1),

as_month=False)
# month_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

Returing datetime instead

month_list = month_get_list(date(2021, 12, 31), date(2021, 1, 1), return_datetime=True)
month_list = [datetime.datetime(2021, 1, 1, 0, 0), datetime.datetime(2021, 2, 1, 0, 0), datetime.datetime(2021, 3, 1, 0, 0), datetime.datetime(2021, 4, 1, 0, 0), datetime.datetime(2021, 5, 1, 0, 0), datetime.datetime(2021, 6, 1, 0, 0), datetime.datetime(2021, 7, 1, 0, 0), datetime.datetime(2021, 8, 1, 0, 0), datetime.datetime(2021, 9, 1, 0, 0), datetime.datetime(2021, 10, 1, 0, 0), datetime.datetime(2021, 11, 1, 0, 0), datetime.datetime(2021, 12, 1, 0, 0)]

If fractions of month are important to you, leverage the first day of the next month to work around the number of days each month may have, on a step year or not:

from datetime import timedelta, datetime
from dateutil.relativedelta import relativedelta

def month_fraction(this_date):
        this_date_df=this_date.strftime("%Y-%m-%d")
        day1_same_month_as_this_date_df=this_date_df[:8]+'01'
        day1_same_month_as_this_date=datetime.strptime(day1_same_month_as_this_date_df, '%Y-%m-%d').date()
        next_mon    th_as_this_date=this_date+relativedelta(months=1)
        next_month_as_this_date_df=next_month_as_this_date.strftime("%Y-%m-%d")
        day1_next_month_this_date_df=next_month_as_this_date_df[:8]+'01'
        day1_next_month_this_date=datetime.strptime(day1_next_month_this_date_df, '%Y-%m-%d').date()
        last_day_same_month_this_date=day1_next_month_this_date-timedelta(days=1)
        delta_days_from_month_beginning=(this_date-day1_same_month_as_this_date).days
        delta_days_whole_month=(last_day_same_month_this_date-day1_same_month_as_this_date).days    
        fraction_beginning_of_month=round(delta_days_from_month_beginning/delta_days_whole_month,4)
        return fraction_beginning_of_month

def delta_months_JLR(second_date,first_date):
        return (second_date.year - first_date.year) * 12 + second_date.month - first_date.month

def delta_months_float(first_date,second_date):
        outgoing_fraction_first_date  =  month_fraction(first_date)
        incoming_fraction_second_date  =  month_fraction(second_date)
        delta_months=delta_months_JLR(second_date,first_date) #as on John La Rooy’s response
        months_float=round(delta_months-outgoing_fraction_first_date+incoming_fraction_second_date,4)
        return months_float


first_date_df='2021-12-28'
first_date=datetime.strptime(first_date, '%Y-%m-%d').date()
second_date_df='2022-01-02'
second_date=datetime.strptime(second_date, '%Y-%m-%d').date()

print (delta_months_float(first_date,second_date))
0.1333

please give start and end date of the and below function will fine all month starting and ending date.

$months = $this->getMonthsInRange('2022-03-15', '2022-07-12');
    
    
    public function getMonthsInRange($startDate, $endDate)
            {
                $months = array();
                while (strtotime($startDate) <= strtotime($endDate)) {
                    $start_date = date('Y-m-d', strtotime($startDate));
                    $end_date = date("Y-m-t", strtotime($start_date));
                    if(strtotime($end_date) >= strtotime($endDate)) {
                        $end_date = $endDate;
                    }
                    $months[] = array(
                        'start_date' => $start_date,
                        'end_date' => $end_date
                    );
                    $startDate = date('01 M Y', strtotime($startDate . '+ 1 month'));
                }
                return $months;
            }
import datetime
from calendar import monthrange

def date_dif(from_date,to_date):   # Изчислява разлика между две дати
    dd=(to_date-from_date).days
    if dd>=0:
        fromDM=from_date.year*12+from_date.month-1
        toDM=to_date.year*12+to_date.month-1
        mlen=monthrange(int((toDM)/12),(toDM)%12+1)[1]
        d=to_date.day-from_date.day
        dm=toDM-fromDM
        m=(dm-int(d<0))%12
        y=int((dm-int(d<0))/12)
        d+=int(d<0)*mlen
        # diference in Y,M,D, diference months,diference  days, days in to_date month
        return[y,m,d,dm,dd,mlen]
    else:
        return[0,0,0,0,dd,0]
Related