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)
@ikramelmbarki
Copy link

please what does the numbers after the point represent, the decimal ones

@andrewyager
Copy link
Author

I'm assuming you are talking about lines 37 - 44. The decimal numbers are the expected result of monthdiff; i.e. the fraction of the month that has passed between two points. We assert this in two ways; either by calculating it (1.0/31.0) or by having pre-calculated it to help identify any potential issues.

@ikramelmbarki
Copy link

what kind of issue? if we have as an example 2.02
2 mean that we have 2 months between the two dates but 0.02?
I tried your function on my personal project but what I want is the difference between each day of the data set and the current day
monthdiff(datetime(2020,4,1).date(), date.today(), decimal_places = 2)
I tried this and it works but I don't know how to apply it to the whole data set and if that possible
I'll be glad if you help

@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