Measuring Months Between Two Dates : Legislative Definition Of Months
Solution 1:
This can be calculated without converting to date types except for the edge case where dates are the last day of the month (where they actually correspond to day zero of the next month).
from datetime import date
def isLastDay(y,m,d):
return date.fromordinal(date(y,m,d).toordinal()+1).month != m
def legalMonthDif(date1,date2):
y1,m1,d1 = map(int,date1.split("-"))
y2,m2,d2 = map(int,date2.split("-"))
if isLastDay(y1,m1,d1): m1,d1 = m1+1,0
if isLastDay(y2,m2,d2): m2,d2 = m2+1,0
return y2*12+m2 -y1*12-m1 -(d2<d1)
output:
legalMonthDif('2019-08-15','2020-02-05') #5
legalMonthDif('2019-08-31','2019-11-30') #3
legalMonthDif('2019-08-25','2019-09-10') #0
legalMonthDif('2019-08-25','2019-09-25') #1
legalMonthDif('2019-08-31','2019-11-30') #3
legalMonthDif('2019-08-01','2019-12-01') #4
legalMonthDif('2019-08-31','2019-12-01') #3
legalMonthDif('2019-08-15','2019-12-01') #3
You could also do it completely without the datetime library by implementing a daysOfMonth function to compute the number of days in any month:
def daysOfMonth(y,m):
return30+(m+m//8)%2-(m==2)*(2-(y%4==0andnoty%100==0ory%400==0))
def legalMonthDif(date1,date2):
y1,m1,d1 = map(int,date1.split("-"))
y2,m2,d2 = map(int,date2.split("-"))
if daysOfMonth(y1,m1) == d1: m1,d1 = m1+1,0if daysOfMonth(y2,m2) == d2: m2,d2 = m2+1,0return y2*12+m2 -y1*12-m1 -(d2<d1)
Solution 2:
dates = [('2019-07-16','2019-08-15'),('2019-08-31','2019-09-30'),
('2019-08-15','2020-02-05'),('2019-08-31','2019-11-30'),
('2019-08-25','2019-09-10'),('2019-08-25','2019-09-25'),
('2019-08-31','2019-12-01'),('2019-08-15' , '2019-12-01'),
('2019-08-01', '2019-11-30'),('2019-08-01', '2019-12-01')]
Using Pandas date-time functionality. This relies on the fact that adding months to a timestamp will truncate to the end of the month if the resulting date doesn't exist - providing a means to test for the (b)(ii) part of the spec.
import pandas as pd
def f(a,b):
earlier,later = sorted((a,b))
rel_months = later.month - earlier.month
delta_months = rel_months + (later.year - earlier.year) *12
period_end = earlier + pd.DateOffset(months=delta_months)
# sentinals for implementing logic of (b)(ii) of the definition
period_end_isEOM = period_end + pd.tseries.offsets.MonthEnd(0)
later_isEOM = later == later + pd.tseries.offsets.MonthEnd(0)
next_month = period_end + pd.tseries.offsets.MonthBegin(0)
# beginwith the delta - period_end == later -then adjust
months = delta_months
# this is straightforward
if period_end > later:
months -=1
# did period_end get truncated to the endof a month
if period_end_isEOM and (period_end.day < earlier.day):
# actual endofperiod would be beginning of next month
if later < next_month: # probably also means later_isEOM or later == period_end
months -=1return months
for a,b in dates:
a, b = map(pd.Timestamp, (a,b))
c = f(a,b)
print(f'{a.date()} - {b.date()} --> {c}')
>>>2019-07-16-2019-08-15--> 02019-08-31-2019-09-30--> 02019-08-15-2020-02-05--> 52019-08-31-2019-11-30--> 22019-08-25-2019-09-10--> 02019-08-25-2019-09-25--> 12019-08-31-2019-12-01--> 32019-08-15-2019-12-01--> 32019-08-01-2019-11-30--> 32019-08-01-2019-12-01--> 4>>>
pd.TimeStamp
is an instance of datetime.datetime
This appears to work - only the OP can judge - but I can't help thinking that there is some builtin functionality I'm still not utilizing. Should be able to subclass pandas.DateOffset and customize it to make the calcs easier.
Solutions using a subclass of Pandas.DateOffset.
from pandas import DateOffset, Timestamp
from pandas.tseries.offsets import MonthBegin
classLegislativeMonth(DateOffset):
def__init__(self, n=1, normalize=False, months=1):
# restricted to months
kwds = {'months':months}
super().__init__(n=1, normalize=False, **kwds)
defapply(self,other):
end_date = super().apply(other)
if end_date.day < other.day:
# truncated to month end
end_date = end_date + MonthBegin(1)
return end_date
for a,b in dates:
earlier,later = sorted(map(Timestamp, (a,b)))
delta_months = later.month - earlier.month
delta_months += (later.year - earlier.year) * 12
end_of_period = earlier + LegislativeMonth(months=delta_months)
if end_of_period > later:
delta_months -= 1print(f'{earlier.date()} - {later.date()} --> {delta_months}')
# another
one_month = LegislativeMonth(months=1)
for a,b in dates:
earlier,later = sorted(map(Timestamp, (a,b)))
end_period = earlier
months = 0while later >= end_period + one_month:
months += 1
end_period += one_month
print(f'{earlier.date()} - {later.date()} --> {months}')
Finally it looks like relativedelta
will do what you want if you ensure that it is called with the earlier date as the first item - (earlier,later)
from datetime import datetime
from dateutil.relativedelta import relativedelta
for a,b in dates:
## earlier,later = sorted(map(Timestamp, (a,b)))
earlier,later = sorted((datetime.strptime(a, '%Y-%m-%d'),
datetime.strptime(b, '%Y-%m-%d')))
rd = relativedelta(earlier,later)
print(f'{earlier.date()} - {later.date()} --> {abs(rd.months)}')
Using the dates at the top of this post all print the following:
2019-07-16-2019-08-15-->02019-08-31-2019-09-30-->02019-08-15-2020-02-05-->52019-08-31-2019-11-30-->22019-08-25-2019-09-10-->02019-08-25-2019-09-25-->12019-08-31-2019-12-01-->32019-08-15-2019-12-01-->32019-08-01-2019-11-30-->32019-08-01-2019-12-01-->4
Solution 3:
I ended up writing the below functions which capture the intended functionality of this legislation:
def find_corresponding_date(start_date):
day= start_date.day
month= start_date.month
year= start_date.year
next_month =month+1
next_year =year
if month==12:
next_month =1
next_year =year+1
try:
new_date = py_datetime(year=next_year, month=next_month, day=day)
except ValueError:
next_month = next_month +1
if next_month ==13:
next_month =1
next_year = next_year +1
new_date = py_datetime(year=next_year, month=next_month, day=1)
return new_date
else:
return new_date
def toPyDateTime(numpyDate):
return py_datetime.strptime(str(numpyDate), "%Y-%m-%d")
def count_months(sdate, edate):
start_date = toPyDateTime(sdate)
end_date = toPyDateTime(edate)
count =0
corres_date = start_date
while(True):
corres_date = find_corresponding_date(corres_date)
if(corres_date > end_date):
return count
break
else:
count = count +1
Post a Comment for "Measuring Months Between Two Dates : Legislative Definition Of Months"