猿问

测量两个日期之间的月份:月份的立法定义

我希望构建代码,代表澳大利亚立法中月份的定义 - 解释法(1987)。


请注意,我仍然是Python的相对新手。


法律定义


定义如下:


(1) 在任何法案中,月是指一个时期:(a)从其中一个日历月的任何一天的开始开始;(b)结束:(i)在下一个日历月相应日期开始前一刻结束;或。(ii) 如果没有这样的日期,即在下一个日历月的月底。


我被告知,这个定义意味着如果一个月的开始时间从16/07/2019开始,例如,出于a)的目的,相关月份直到11:59:59:59:etc:pm在15/08/2019结束 - 或者从功能上讲,16/08/2019。


因此,出于 b) 的目的,“月末”的定义与该月相关最后一天的 11:59:59:etc:etc:pm 类似。因此,如果您有两个日期 - 31 / 08 / 2019和30 / 09 / 2019 - 相关月份直到30 / 09 / 2019的11:59:59:etc:pm为止 - 或者功能上是01 / 10 / 2019。


我需要以月为单位输出两个日期之间的差异,以反映我正在编码的立法要求两个日期之间的差异,特别是以月份为单位。


如果可能的话,我希望使用datetime或datetime64对象来执行此操作,以避免不必要地在变量之间进行转换。


到目前为止,我尝试过什么。


我使用以下代码来查找月份中两个日期之间的差异,使用相对delta:


from datetime import datetime

from dateutil import relativedelta

date1 = datetime.strptime('2019-08-15', '%Y-%m-%d')

date2 = datetime.strptime('2020-02-05', '%Y-%m-%d')

r = relativedelta.relativedelta(date2, date1)

r.months + (12*r.years)

print(r)

我的预期产量是5个月,因为有五个完整的月份,然后是一个月的一小部分,而不是在date2之前完成。这将返回预期的结果,并在立法中复制 a) 的功能。


但是,当我尝试使用以下代码复制b)时:


from datetime import datetime

from dateutil import relativedelta

date1 = datetime.strptime('2019-08-31', '%Y-%m-%d')

date2 = datetime.strptime('2019-11-30', '%Y-%m-%d')

r = relativedelta.relativedelta(date2, date1)

r.months + (12*r.years)

print(r)

这将返回 4 个月的结果。由于2019-11-30不是相关日历月的结束,这是不正确的 - 我应该获得此代码的3个月结果,因为该月直到11:59:59:etc才完成。


预期成果


下面是我用来测试此代码结果的四个测试用例。


from datetime import datetime

from dateutil import relativedelta

date1 = datetime.strptime('2019-08-25', '%Y-%m-%d')

date2 = datetime.strptime('2019-09-10', '%Y-%m-%d')

r = relativedelta.relativedelta(date2, date1)

r.months + (12*r.years)

r.months = 0


from datetime import datetime

from dateutil import relativedelta

date1 = datetime.strptime('2019-08-25', '%Y-%m-%d')

date2 = datetime.strptime('2019-09-25', '%Y-%m-%d')

r = relativedelta.relativedelta(date2, date1)

r.months + (12*r.years)

r.months = 1


绝地无双
浏览 123回答 3
3回答

幕布斯6054654

这可以在不转换为日期类型的情况下进行计算,但边缘情况除外,其中日期是该月的最后一天(它们实际上对应于下个月的第零天)。from datetime import datedef isLastDay(y,m,d):&nbsp; &nbsp; return date.fromordinal(date(y,m,d).toordinal()+1).month != mdef legalMonthDif(date1,date2):&nbsp; &nbsp; y1,m1,d1 = map(int,date1.split("-"))&nbsp; &nbsp; y2,m2,d2 = map(int,date2.split("-"))&nbsp; &nbsp; if isLastDay(y1,m1,d1): m1,d1 = m1+1,0&nbsp; &nbsp; if isLastDay(y2,m2,d2): m2,d2 = m2+1,0&nbsp; &nbsp; return y2*12+m2 -y1*12-m1 -(d2<d1)输出:legalMonthDif('2019-08-15','2020-02-05') #5legalMonthDif('2019-08-31','2019-11-30') #3legalMonthDif('2019-08-25','2019-09-10') #0legalMonthDif('2019-08-25','2019-09-25') #1legalMonthDif('2019-08-31','2019-11-30') #3legalMonthDif('2019-08-01','2019-12-01') #4&nbsp;legalMonthDif('2019-08-31','2019-12-01') #3legalMonthDif('2019-08-15','2019-12-01') #3您也可以通过实现daysOfMonth函数来计算任何月份中的天数,完全无需datetime库即可完成此操作:def daysOfMonth(y,m):&nbsp; &nbsp; return 30+(m+m//8)%2-(m==2)*(2-(y%4==0 and not y%100==0 or y%400==0))def legalMonthDif(date1,date2):&nbsp; &nbsp; y1,m1,d1 = map(int,date1.split("-"))&nbsp; &nbsp; y2,m2,d2 = map(int,date2.split("-"))&nbsp; &nbsp; if daysOfMonth(y1,m1) == d1: m1,d1 = m1+1,0&nbsp; &nbsp; if daysOfMonth(y2,m2) == d2: m2,d2 = m2+1,0&nbsp; &nbsp; return y2*12+m2 -y1*12-m1 -(d2<d1)

收到一只叮咚

我最终编写了以下函数,这些函数捕获了此立法的预期功能:def find_corresponding_date(start_date):day = start_date.daymonth = start_date.monthyear = start_date.yearnext_month = month + 1next_year = yearif month == 12:&nbsp; &nbsp; next_month = 1&nbsp; &nbsp; next_year = year + 1try:&nbsp; &nbsp; new_date = py_datetime(year=next_year, month=next_month, day=day)except ValueError:&nbsp; &nbsp; next_month = next_month + 1&nbsp; &nbsp; if next_month == 13:&nbsp; &nbsp; &nbsp; &nbsp; next_month = 1&nbsp; &nbsp; &nbsp; &nbsp; next_year = next_year + 1&nbsp; &nbsp; new_date = py_datetime(year=next_year, month=next_month, day=1)&nbsp; &nbsp; return new_dateelse:&nbsp; &nbsp; return new_datedef toPyDateTime(numpyDate):&nbsp; &nbsp; return py_datetime.strptime(str(numpyDate), "%Y-%m-%d")def count_months(sdate, edate):&nbsp; &nbsp; start_date = toPyDateTime(sdate)&nbsp; &nbsp; end_date = toPyDateTime(edate)&nbsp; &nbsp; count = 0&nbsp; &nbsp; corres_date = start_date&nbsp; &nbsp; while(True):&nbsp; &nbsp; &nbsp; &nbsp; corres_date = find_corresponding_date(corres_date)&nbsp; &nbsp; &nbsp; &nbsp; if(corres_date > end_date):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return count&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break&nbsp; &nbsp; &nbsp; &nbsp; else:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; count = count + 1

慕村225694

dates = [('2019-07-16','2019-08-15'),('2019-08-31','2019-09-30'),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;('2019-08-15','2020-02-05'),('2019-08-31','2019-11-30'),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;('2019-08-25','2019-09-10'),('2019-08-25','2019-09-25'),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;('2019-08-31','2019-12-01'),('2019-08-15' , '2019-12-01'),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;('2019-08-01', '2019-11-30'),('2019-08-01', '2019-12-01')]使用熊猫日期时间功能。这依赖于这样一个事实,即如果结果日期不存在,则在时间戳中添加月份将被截断到月底 - 提供一种测试规范的(b)(ii)部分的方法。import pandas as pddef f(a,b):&nbsp; &nbsp; earlier,later = sorted((a,b))&nbsp; &nbsp; rel_months = later.month - earlier.month&nbsp; &nbsp; delta_months = rel_months + (later.year - earlier.year) * 12&nbsp; &nbsp; period_end = earlier + pd.DateOffset(months=delta_months)&nbsp; &nbsp; # sentinals for implementing logic of (b)(ii) of the definition&nbsp; &nbsp; period_end_isEOM = period_end + pd.tseries.offsets.MonthEnd(0)&nbsp; &nbsp; later_isEOM = later == later + pd.tseries.offsets.MonthEnd(0)&nbsp; &nbsp; next_month = period_end + pd.tseries.offsets.MonthBegin(0)&nbsp; &nbsp; # begin with the delta - period_end == later - then adjust&nbsp; &nbsp; months = delta_months&nbsp; &nbsp; # this is straightforward&nbsp; &nbsp; if period_end > later:&nbsp; &nbsp; &nbsp; &nbsp; months -= 1&nbsp; &nbsp; # did period_end get truncated to the end of a month&nbsp; &nbsp; if period_end_isEOM and (period_end.day < earlier.day):&nbsp; &nbsp; &nbsp; &nbsp; # actual end of period would be beginning of next month&nbsp; &nbsp; &nbsp; &nbsp; if later < next_month:&nbsp; &nbsp; # probably also means later_isEOM or later == period_end&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; months -= 1&nbsp; &nbsp; return months&nbsp;for a,b in dates:&nbsp; &nbsp;a, b = map(pd.Timestamp, (a,b))&nbsp; &nbsp;c = f(a,b)&nbsp; &nbsp;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>>>&nbsp;pd.时间戳是datetime.datetime这似乎有效 - 只有OP可以判断 - 但我忍不住想到我仍然没有利用一些内置功能。应该能够子类熊猫。日期偏移并自定义它以使计算更容易。使用 Pandas.DateOffset 子类的解决方案。from pandas import DateOffset, Timestampfrom pandas.tseries.offsets import MonthBeginclass LegislativeMonth(DateOffset):&nbsp; &nbsp; def __init__(self, n=1, normalize=False, months=1):&nbsp; &nbsp; &nbsp; &nbsp; # restricted to months&nbsp; &nbsp; &nbsp; &nbsp; kwds = {'months':months}&nbsp; &nbsp; &nbsp; &nbsp; super().__init__(n=1, normalize=False, **kwds)&nbsp; &nbsp; def apply(self,other):&nbsp; &nbsp; &nbsp; &nbsp; end_date = super().apply(other)&nbsp; &nbsp; &nbsp; &nbsp; if end_date.day < other.day:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # truncated to month end&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; end_date = end_date + MonthBegin(1)&nbsp; &nbsp; &nbsp; &nbsp; return end_datefor a,b in dates:&nbsp; &nbsp;earlier,later = sorted(map(Timestamp, (a,b)))&nbsp; &nbsp;delta_months = later.month - earlier.month&nbsp; &nbsp;delta_months += (later.year - earlier.year) * 12&nbsp; &nbsp;end_of_period = earlier + LegislativeMonth(months=delta_months)&nbsp; &nbsp;if end_of_period > later:&nbsp; &nbsp; &nbsp; &nbsp;delta_months -= 1&nbsp; &nbsp;print(f'{earlier.date()} - {later.date()} --> {delta_months}')# anotherone_month = LegislativeMonth(months=1)for a,b in dates:&nbsp; &nbsp;earlier,later = sorted(map(Timestamp, (a,b)))&nbsp; &nbsp;end_period = earlier&nbsp; &nbsp;months = 0&nbsp; &nbsp;while later >= end_period + one_month:&nbsp; &nbsp; &nbsp; &nbsp;months += 1&nbsp; &nbsp; &nbsp; &nbsp;end_period += one_month&nbsp; &nbsp;print(f'{earlier.date()} - {later.date()} --> {months}')最后,如果您确保以较早的日期作为第一项来调用它,它看起来像是会做您想要的 -relativedelta(earlier,later)from datetime import datetimefrom dateutil.relativedelta import relativedeltafor a,b in dates:##&nbsp; &nbsp;earlier,later = sorted(map(Timestamp, (a,b)))&nbsp; &nbsp; earlier,later = sorted((datetime.strptime(a, '%Y-%m-%d'),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; datetime.strptime(b, '%Y-%m-%d')))&nbsp; &nbsp; rd = relativedelta(earlier,later)&nbsp; &nbsp; print(f'{earlier.date()} - {later.date()} --> {abs(rd.months)}')使用此帖子顶部的日期,所有日期都将打印以下内容: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
随时随地看视频慕课网APP

相关分类

Python
我要回答