1

我正在寻找构建代表澳大利亚立法中月份定义的代码 - 解释法(1987)。

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

法律定义

定义如下:

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

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

那么,为了 b) 的目的,“月末”定义在当月相关最后一天的 11:59:59:etc:pm。因此,如果您有两个日期 - 2019 年 8 月 31 日和 2019 年 9 月 30 日 - 相关月份直到 2019 年 9 月 30 日下午 11:59:59:etc:pm 才结束 - 或者功能上,2019 年 1 月 10 日.

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

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

到目前为止我所尝试的。

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

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: 等才完成。

预期成绩

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

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

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)
r.months = 3

from datetime import datetime
from dateutil import relativedelta
date1 = datetime.strptime('2019-08-31', '%Y-%m-%d')
date2 = datetime.strptime('2019-12-01', '%Y-%m-%d')
r = relativedelta.relativedelta(date2, date1)
r.months + (12*r.years)
r.months = 4

编辑:我已经为后两个测试用例编写了输入,在查看了 Alain T. 的回复后,已修改为以下内容。

from datetime import datetime
from dateutil import relativedelta
date1 = datetime.strptime('2019-08-01', '%Y-%m-%d')
date2 = datetime.strptime('2019-11-30', '%Y-%m-%d')
r = relativedelta.relativedelta(date2, date1)
r.months + (12*r.years)
r.months = 3

from datetime import datetime
from dateutil import relativedelta
date1 = datetime.strptime('2019-08-01', '%Y-%m-%d')
date2 = datetime.strptime('2019-12-01', '%Y-%m-%d')
r = relativedelta.relativedelta(date2, date1)
r.months + (12*r.years)
r.months = 4

from datetime import datetime
from dateutil import relativedelta
date1 = datetime.strptime('2019-08-31', '%Y-%m-%d')
date2 = datetime.strptime('2019-12-01', '%Y-%m-%d')
r = relativedelta.relativedelta(date2, date1)
r.months + (12*r.years)
r.months = 3
4

3 回答 3

2

这可以在不转换为日期类型的情况下计算,除了日期是该月的最后一天的边缘情况(它们实际上对应于下个月的第 0 天)。

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)

输出:

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

您也可以通过实现 daysOfMonth 函数来计算任何月份的天数,从而完全不使用 datetime 库:

def daysOfMonth(y,m):
    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):
    y1,m1,d1 = map(int,date1.split("-"))
    y2,m2,d2 = map(int,date2.split("-"))
    if daysOfMonth(y1,m1) == d1: m1,d1 = m1+1,0
    if daysOfMonth(y2,m2) == d2: m2,d2 = m2+1,0
    return y2*12+m2 -y1*12-m1 -(d2<d1)
于 2020-03-10T23:02:28.697 回答
0
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')]

使用 Pandas 日期时间功能。这依赖于这样一个事实,即如果结果日期不存在,将月份添加到时间戳将截断到月底- 提供了一种测试规范的(b)(ii)部分的方法。

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)

    # begin with 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 end of a month
    if period_end_isEOM and (period_end.day < earlier.day):
        # actual end of period would be beginning of next month
        if later < next_month:    # probably also means later_isEOM or later == period_end
            months -= 1
    return 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 --> 0
2019-08-31 - 2019-09-30 --> 0
2019-08-15 - 2020-02-05 --> 5
2019-08-31 - 2019-11-30 --> 2
2019-08-25 - 2019-09-10 --> 0
2019-08-25 - 2019-09-25 --> 1
2019-08-31 - 2019-12-01 --> 3
2019-08-15 - 2019-12-01 --> 3
2019-08-01 - 2019-11-30 --> 3
2019-08-01 - 2019-12-01 --> 4
>>> 

pd.TimeStamp是一个实例datetime.datetime

这似乎可行 - 只有 OP 可以判断 - 但我不禁想到有一些内置功能我仍然没有使用。应该能够继承 pandas.DateOffset并对其进行自定义以使计算更容易。


使用 Pandas.DateOffset 子类的解决方案。

from pandas import DateOffset, Timestamp
from pandas.tseries.offsets import MonthBegin

class LegislativeMonth(DateOffset):
    def __init__(self, n=1, normalize=False, months=1):
        # restricted to months
        kwds = {'months':months}
        super().__init__(n=1, normalize=False, **kwds)
    def apply(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 -= 1
   print(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 = 0
   while later >= end_period + one_month:
       months += 1
       end_period += one_month
   print(f'{earlier.date()} - {later.date()} --> {months}')

最后,如果您确保以较早的日期作为第一项调用它,它看起来relativedelta会做您想做的事情 -(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)}')

使用这篇文章顶部的日期都打印以下内容:

2019-07-16 - 2019-08-15 --> 0
2019-08-31 - 2019-09-30 --> 0
2019-08-15 - 2020-02-05 --> 5
2019-08-31 - 2019-11-30 --> 2
2019-08-25 - 2019-09-10 --> 0
2019-08-25 - 2019-09-25 --> 1
2019-08-31 - 2019-12-01 --> 3
2019-08-15 - 2019-12-01 --> 3
2019-08-01 - 2019-11-30 --> 3
2019-08-01 - 2019-12-01 --> 4
于 2020-03-10T22:54:45.313 回答
0

我最终编写了以下函数,这些函数捕获了该立法的预期功能:

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
于 2021-11-16T23:50:56.237 回答