Измерение месяцев между двумя датами: законодательное определение месяцев - PullRequest
1 голос
/ 11 марта 2020

Я ищу код, который представляет определение месяцев в части австралийского законодательства - Закон о толковании (1987).

Обратите внимание, что я все еще относительный новичок к Python.

Юридическое определение

Определение гласит следующее:

(1) В любом акте месяц означает период: (a) начинающийся в начале любого дня одного из календарных месяцев; а также. (b) окончание: (i) непосредственно перед началом соответствующего дня следующего календарного месяца; или. (ii) если такого дня нет - в конце следующего календарного месяца.

Мне сообщили, что это определение означает, что если начало месяца начинается с 16/07/2019, для целей а) например, соответствующий месяц не заканчивается до 11: 59: 59: et c: pm 15/08/2019 - или функционально, 16/08/2019.

Для целей б) «конец месяца» определяется аналогично в 11: 59: 59: et c: pm в соответствующий последний день месяца. Так что, если у вас есть две даты - 31/08/2019 и 30/09/2019 - соответствующий месяц не заканчивается до 11: 59: 59: et c: pm 30/09/2019 - или функционально, 01 / 10 / 2019.

Мне нужно вывести разницу между двумя датами в месяцах, чтобы отразить, что законодательство, которое я кодирую, требует разницы между двумя датами, конкретно в месяцах.

I Я пытаюсь сделать это с объектами datetime или datetime64, если это возможно, чтобы избежать ненужного преобразования между переменными.

То, что я пробовал до сих пор.

I ' Мы использовали приведенный ниже код, чтобы найти разницу между двумя датами в месяцах, используя относительную дельту:

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 месяцев, так как есть пять полных месяцев, а затем доля месяца, которая не завершено по дате2. Это возвращает ожидаемый результат и копирует функциональность а) в законодательстве.

Однако, когда я пытаюсь повторить б) с кодом ниже:

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: et c ,

Ожидаемые результаты

Ниже приведены четыре контрольных примера, которые я использовал для проверки результатов этого кода.

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

РЕДАКТИРОВАТЬ: I Вы написали входные данные для вторых двух тестовых случаев и после рассмотрения ответа Алена Т. пересмотрели ниже.

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

Ответы [ 2 ]

2 голосов
/ 11 марта 2020

Это можно рассчитать без преобразования в типы дат, за исключением крайнего случая, когда даты являются последним днем ​​месяца (где они фактически соответствуют нулевому дню следующего месяца).

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

Вы также можете сделать это полностью без библиотеки datetime, реализовав функцию daysOfMonth для вычисления количества дней в любом месяце:

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)
0 голосов
/ 11 марта 2020
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) часть спецификации c.

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

Это работает - судить может только ОП - но я не могу не думать, что есть некоторые встроенные функции, которые я до сих пор не использую. Должен быть в состоянии создать подкласс pandas .DateOffset и настроить его так, чтобы упростить calcs .


Решения с использованием подкласса 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
...