Skip to content

Instantly share code, notes, and snippets.

@andrewyager
Last active June 4, 2020 04:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewyager/6b9284a4f1cdb1779b10 to your computer and use it in GitHub Desktop.
Save andrewyager/6b9284a4f1cdb1779b10 to your computer and use it in GitHub Desktop.
Calculate the fractional months between two dates, inclusively.
from datetime import datetime, date
import calendar
def monthdiff(start_period, end_period, decimal_places = 2):
if start_period > end_period:
raise Exception('Start is after end')
if start_period.year == end_period.year and start_period.month == end_period.month:
days_in_month = calendar.monthrange(start_period.year, start_period.month)[1]
days_to_charge = end_period.day - start_period.day+1
diff = round(float(days_to_charge)/float(days_in_month), decimal_places)
return diff
months = 0
# we have a start date within one month and not at the start, and an end date that is not
# in the same month as the start date
if start_period.day > 1:
last_day_in_start_month = calendar.monthrange(start_period.year, start_period.month)[1]
days_to_charge = last_day_in_start_month - start_period.day +1
months = months + round(float(days_to_charge)/float(last_day_in_start_month), decimal_places)
start_period = datetime(start_period.year, start_period.month+1, 1)
last_day_in_last_month = calendar.monthrange(end_period.year, end_period.month)[1]
if end_period.day != last_day_in_last_month:
# we have less days in the last month
months = months + round(float(end_period.day) / float(last_day_in_last_month), decimal_places)
last_day_in_previous_month = calendar.monthrange(end_period.year, end_period.month - 1)[1]
end_period = datetime(end_period.year, end_period.month - 1, last_day_in_previous_month)
#whatever happens, we now have a period of whole months to calculate the difference between
if start_period != end_period:
months = months + (end_period.year - start_period.year) * 12 + (end_period.month - start_period.month) + 1
# just counter for any final decimal place manipulation
diff = round(months, decimal_places)
return diff
assert monthdiff(datetime(2015,1,1), datetime(2015,1,31)) == 1
assert monthdiff(datetime(2015,1,1), datetime(2015,02,01)) == 1.04
assert monthdiff(datetime(2014,1,1), datetime(2014,12,31)) == 12
assert monthdiff(datetime(2014,7,1), datetime(2015,06,30)) == 12
assert monthdiff(datetime(2015,1,10), datetime(2015,01,20)) == 0.35
assert monthdiff(datetime(2015,1,10), datetime(2015,02,20)) == 0.71 + 0.71
assert monthdiff(datetime(2015,1,31), datetime(2015,02,01)) == round(1.0/31.0,2) + round(1.0/28.0,2)
assert monthdiff(datetime(2013,1,31), datetime(2015,02,01)) == 12*2 + round(1.0/31.0,2) + round(1.0/28.0,2)
@andrewyager
Copy link
Author

andrewyager commented Jun 4, 2020

Why don't you provide your example of dates.

If your dates were 2020-04-01 to 2020-05-03, you would have 1 month (2020-04-01 to midnight 2020-04-30) plus then 3 of the 31 days of may, or 3/31 or 0.096 (or 0.10) of the month of may. That is the portion of the month of May that is included in your calculation.

The other way to view the part of the month is to think about it this way: the month of May has 31 days. So 1 day in the month of may is 1/31. Three days, is therefore 1/31 * 3, or 3/31.

Andrew

@ikramelmbarki
Copy link

I wanna use this to calculate the number of months between each month of the dataset and the current months.
Example 👍
today = date.today()
df_req1["ancienté_par_rapport_year"]=today.year-df_req1["year"]
if I have "2006-06-01" and today I know it's 14 years
I want the same thing for months and days

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment