Найкращий спосіб знайти місяці між двома датами


91

Мені потрібно мати можливість точно знаходити місяці між двома датами в python. У мене є рішення, яке працює, але воно не дуже добре (як у елегантному) або швидке.

dateRange = [datetime.strptime(dateRanges[0], "%Y-%m-%d"), datetime.strptime(dateRanges[1], "%Y-%m-%d")]
months = [] 

tmpTime = dateRange[0]
oneWeek = timedelta(weeks=1)
tmpTime = tmpTime.replace(day=1)
dateRange[0] = tmpTime
dateRange[1] = dateRange[1].replace(day=1)
lastMonth = tmpTime.month
months.append(tmpTime)
while tmpTime < dateRange[1]:
    if lastMonth != 12:
        while tmpTime.month <= lastMonth:
            tmpTime += oneWeek
        tmpTime = tmpTime.replace(day=1)
        months.append(tmpTime)
        lastMonth = tmpTime.month

    else:
        while tmpTime.month >= lastMonth:
            tmpTime += oneWeek
        tmpTime = tmpTime.replace(day=1)
        months.append(tmpTime)
        lastMonth = tmpTime.month

Тож лише для пояснення, що я тут роблю, беру дві дати і перетворюю їх із iso-формату в python-об'єкти datetime. Потім я циклічно додаю тиждень до початкового об’єкту datetime і перевіряю, чи числове значення місяця більше (якщо місяць не грудень, тоді він перевіряє, чи менше дата); Якщо значення більше, я додаю його до списку місяців і продовжуйте цикл, поки я не дістанусь дати закінчення.

Це чудово працює, це просто не здається хорошим способом зробити це ...


Ви запитуєте ЧИСЛО місяців між двома датами, або які фактичні місяці?
Чарльз Хупер,

у моєму рішенні: я не збільшуюся на "кількість секунд на місяць". Я просто збільшую число 1 до 2, а потім від 2 до 3 пізніше.
нонополярність

Я просто хотів, щоб ви знали, що, хоча вам не сподобалась моя відповідь, оскільки вона "мала цикл", ви вибрали відповідь, яка має ДВА цикли. Розуміння списку все ще є циклами.
Чарльз Хупер,

Відповіді:


0

Оновлення 2018-04-20: схоже, що ОП @Joshkunz просив знайти, які місяці знаходяться між двома датами, а не "скільки місяців" між двома датами. Тож я не впевнений, чому @JohnLaRooy голосує більше 100 разів. @Joshkunz зазначив у коментарі під початковим запитанням, що хоче фактичні дати [або місяці], замість того щоб знайти загальну кількість місяців .

Отож виявилось, що запитувалося запитання, між двома датами 2018-04-11до2018-06-01

Apr 2018, May 2018, June 2018 

А що , якщо він знаходиться між 2014-04-11до 2018-06-01? Тоді відповідь буде

Apr 2014, May 2014, ..., Dec 2014, Jan 2015, ..., Jan 2018, ..., June 2018

Ось чому у мене був такий псевдокод багато років тому. Він просто пропонував використовувати два місяці як кінцеві точки та прокручувати їх, збільшуючи на один місяць за один раз. @Joshkunz згадав, що хоче "місяців", а також зазначив, що хоче "дати", не знаючи точно, складно було написати точний код, але ідея полягає в тому, щоб використовувати один простий цикл, щоб прокрутити кінцеві точки, і зростаючи по одному місяцю.

Відповідь 8 років тому в 2010 році:

Якщо додати до тижня, тоді це приблизно зробить роботи в 4,35 рази більше, ніж потрібно. Чому не просто:

1. get start date in array of integer, set it to i: [2008, 3, 12], 
       and change it to [2008, 3, 1]
2. get end date in array: [2010, 10, 26]
3. add the date to your result by parsing i
       increment the month in i
       if month is >= 13, then set it to 1, and increment the year by 1
   until either the year in i is > year in end_date, 
           or (year in i == year in end_date and month in i > month in end_date)

просто псевдо-код поки що не тестував, але я думаю, що ідея в тому ж рядку буде працювати.


1
Добре, я бачу деякі проблеми з такими місяцями, як лютий, якщо збільшення відбувається за місяцями. Замість тижнів.
Joshkunz

Мене не збільшують на "кількість секунд на місяць". Я просто збільшую число 1до 2, а потім від 2до 3пізніше.
нонополярність

194

Почніть із визначення деяких тестових випадків, тоді ви побачите, що функція дуже проста і не потребує циклів

from datetime import datetime

def diff_month(d1, d2):
    return (d1.year - d2.year) * 12 + d1.month - d2.month

assert diff_month(datetime(2010,10,1), datetime(2010,9,1)) == 1
assert diff_month(datetime(2010,10,1), datetime(2009,10,1)) == 12
assert diff_month(datetime(2010,10,1), datetime(2009,11,1)) == 11
assert diff_month(datetime(2010,10,1), datetime(2009,8,1)) == 14

До свого запитання слід додати кілька тестових кейсів, оскільки існує безліч потенційних кутових кейсів - існує кілька способів визначити кількість місяців між двома датами.


2
це дає неправильний результат. це дає 1 місяць між "2015-04-30" і "2015-05-01", що насправді становить лише 1 день.
Рао,

21
@Rao. саме тому я сказав "існує більше одного способу визначити кількість місяців між двома датами". Питання все ще не має офіційного визначення. Ось чому я також пропоную наводити тестові кейси поряд із визначенням.
Джон Ла Рой,

2
Я рекомендую додати abs () навколо субстрактів, щоб дозволити d1 бути меншим за d2: повернути abs (d1.year - d2.year) * 12 + abs (d1.month - d2.month)
Лукаш Шульце

Ви впевнені, @LukasSchulze? Якщо d1 менше d2, вам все одно доведеться відняти це число від першого, так?
Ліліт-Еліна

Але в моєму випадку неважливо, d1 <= d2 чи d2 <= d1. Вас цікавить лише різниця, незалежно від того, яка з двох дат менша / більша.
Лукаш Шульце,

40

Один лайнер для пошуку списку дат, збільшених на місяць, між двома датами.

import datetime
from dateutil.rrule import rrule, MONTHLY

strt_dt = datetime.date(2001,1,1)
end_dt = datetime.date(2005,6,1)

dates = [dt for dt in rrule(MONTHLY, dtstart=strt_dt, until=end_dt)]

Це! Це налаштовує на безліч вигадливих особливих ситуацій завдяки використанню правил. Зверніть увагу, що вихідними значеннями є дати, щоб ви могли перетворити їх у будь-яке, що вам подобається (включаючи рядки), щоб відповідати тому, що показують інші.
Ezekiel Kruglick

Це не вдається, коли strt_dt дорівнює 2001-1-29, через те, що в цьому році не відбувся Страсний день.
CS

2
OP попросив надати перелік місяців між датами початку та закінчення. Лютий пропущений, використовуючи ваш метод на моєму прикладі. Звичайно, ваше рішення все ще можна виправити, наприклад, відкоригувавши дату початку до першого числа місяця.
CS

2
Це гарне рішення, і це економить мені багато часу. Ми можемо зробити це кращим, вказавши дату початку[dt for dt in rrule(MONTHLY, bymonthday=10,dtstart=strt_dt, until=end_dt)]
Пеньджу Чжао

1
Будьте в курсі цього. Вони фактично містять примітку в документації, в якій сказано, що Rrule може мати "дивовижну поведінку, коли, наприклад, дата початку настає в кінці місяця" (див. Примітку в dateutil.readthedocs.io/en/stable/rrule.html ). Одним із способів уникнути цього є заміна дат на перший день місяця: start_date_first = start_date.replace(day=1), end_date_first = end_date.replace(day=1)Тоді правильна порахування місяців здійснюється належним чином.
Алла Сорокіна

36

Це спрацювало для мене -

from datetime import datetime
from dateutil import relativedelta
date1 = datetime.strptime('2011-08-15 12:00:00', '%Y-%m-%d %H:%M:%S')
date2 = datetime.strptime('2012-02-15', '%Y-%m-%d')
r = relativedelta.relativedelta(date2, date1)
r.months + (12*r.years)

видалити непотрібне str()навколо рядкових літералів.
jfs

7
Нічого не варто, якщо дельта більше 1 року, r.monthsпочнеться з 0
jbkkd

2
зазвичай я роблю r.months (r.years + 1), оскільки це відповідає тому, про що говорив
@jbkkd

9
@triunenature це виглядає неправильно, безумовно, це має бути щось на зразок r.months + (r.years * 12)
Moataz Elmasry

2
Так, це не працює, якщо дати мають більше, ніж рік.
HansG600

11

Ви можете легко обчислити це, використовуючи rulu з модуля dateutil :

from dateutil import rrule
from datetime import date

print(list(rrule.rrule(rrule.MONTHLY, dtstart=date(2013, 11, 1), until=date(2014, 2, 1))))

дасть вам:

 [datetime.datetime(2013, 11, 1, 0, 0),
 datetime.datetime(2013, 12, 1, 0, 0),
 datetime.datetime(2014, 1, 1, 0, 0),
 datetime.datetime(2014, 2, 1, 0, 0)]

10

Отримайте кінцевий місяць (відносно року та місяця початкового місяця, наприклад: 2011 січня = 13, якщо ваша дата початку починається з жовтня 2010 року), а потім згенеруйте дати, які починаються з початкового місяця та кінця місяця приблизно так:

dt1, dt2 = dateRange
start_month=dt1.month
end_months=(dt2.year-dt1.year)*12 + dt2.month+1
dates=[datetime.datetime(year=yr, month=mn, day=1) for (yr, mn) in (
          ((m - 1) / 12 + dt1.year, (m - 1) % 12 + 1) for m in range(start_month, end_months)
      )]

якщо обидві дати припадають на один рік, це також можна просто записати так:

dates=[datetime.datetime(year=dt1.year, month=mn, day=1) for mn in range(dt1.month, dt2.month + 1)]

8

Цей пост це забиває! Використовуйте dateutil.relativedelta.

from datetime import datetime
from dateutil import relativedelta
date1 = datetime.strptime(str('2011-08-15 12:00:00'), '%Y-%m-%d %H:%M:%S')
date2 = datetime.strptime(str('2012-02-15'), '%Y-%m-%d')
r = relativedelta.relativedelta(date2, date1)
r.months

1
@edouard Як ви можете бачити на прикладі, який я наводив, дати в різні роки. Тим не менше, str()акторський склад, який я зробив, абсолютно непотрібний.
srodriguex

5
це не працює, якщо дати охоплюють більше року, вам потрібно додатиrelativedelta.relativedelta(date2, date1).years * 12
muon

delta.years*12 + delta.months
user2682863

7

Моє просте рішення:

import datetime

def months(d1, d2):
    return d1.month - d2.month + 12*(d1.year - d2.year)

d1 = datetime.datetime(2009, 9, 26)  
d2 = datetime.datetime(2019, 9, 26) 

print(months(d1, d2))

недооцінене рішення
повернути назад

4

Визначення «місяць» , як 1 / 12 року, то зробити це:

def month_diff(d1, d2): 
    """Return the number of months between d1 and d2, 
    such that d2 + month_diff(d1, d2) == d1
    """
    diff = (12 * d1.year + d1.month) - (12 * d2.year + d2.month)
    return diff

Ви можете спробувати визначити місяць як "період 29, 28, 30 або 31 день (залежно від року)". Але якщо ви це зробите, вам доведеться вирішити додаткову проблему.

Хоча це, як правило , ясно , що 15 червня й + 1 місяць має бути 15 липня - го , це не правило , не ясно , якщо 30 січня е + 1 місяць в лютому або березні. В останньому випадку, ви можете бути змушені обчислювати дату як 30 лютого - го , то «правильно» його 2 березня - го . Але коли ви зробите це, ви виявите , що 2 березня й - 1 місяць, очевидно , 2 лютого - го . Ergo, reductio ad absurdum (ця операція недостатньо чітко визначена).


4

Існує просте рішення, засноване на 360-денних роках, де всі місяці мають 30 днів. Це підходить для більшості випадків використання, коли за двома датами потрібно розрахувати кількість повних місяців плюс дні, що залишились.

from datetime import datetime, timedelta

def months_between(start_date, end_date):
    #Add 1 day to end date to solve different last days of month 
    s1, e1 = start_date , end_date  + timedelta(days=1)
    #Convert to 360 days
    s360 = (s1.year * 12 + s1.month) * 30 + s1.day
    e360 = (e1.year * 12 + e1.month) * 30 + e1.day
    #Count days between the two 360 dates and return tuple (months, days)
    return divmod(e360 - s360, 30)

print "Counting full and half months"
print months_between( datetime(2012, 01, 1), datetime(2012, 03, 31)) #3m
print months_between( datetime(2012, 01, 1), datetime(2012, 03, 15)) #2m 15d
print months_between( datetime(2012, 01, 16), datetime(2012, 03, 31)) #2m 15d
print months_between( datetime(2012, 01, 16), datetime(2012, 03, 15)) #2m
print "Adding +1d and -1d to 31 day month"
print months_between( datetime(2011, 12, 01), datetime(2011, 12, 31)) #1m 0d
print months_between( datetime(2011, 12, 02), datetime(2011, 12, 31)) #-1d => 29d
print months_between( datetime(2011, 12, 01), datetime(2011, 12, 30)) #30d => 1m
print "Adding +1d and -1d to 29 day month"
print months_between( datetime(2012, 02, 01), datetime(2012, 02, 29)) #1m 0d
print months_between( datetime(2012, 02, 02), datetime(2012, 02, 29)) #-1d => 29d
print months_between( datetime(2012, 02, 01), datetime(2012, 02, 28)) #28d
print "Every month has 30 days - 26/M to 5/M+1 always counts 10 days"
print months_between( datetime(2011, 02, 26), datetime(2011, 03, 05))
print months_between( datetime(2012, 02, 26), datetime(2012, 03, 05))
print months_between( datetime(2012, 03, 26), datetime(2012, 04, 05))

4

Дещо приємне рішення від @ Vin-G.

import datetime

def monthrange(start, finish):
  months = (finish.year - start.year) * 12 + finish.month + 1 
  for i in xrange(start.month, months):
    year  = (i - 1) / 12 + start.year 
    month = (i - 1) % 12 + 1
    yield datetime.date(year, month, 1)

4

Ви також можете скористатися бібліотекою стрілок . Це простий приклад:

from datetime import datetime
import arrow

start = datetime(2014, 1, 17)
end = datetime(2014, 6, 20)

for d in arrow.Arrow.range('month', start, end):
    print d.month, d.format('MMMM')

Буде надруковано:

1 January
2 February
3 March
4 April
5 May
6 June

Сподіваюся, це допомагає!


4
from dateutil import relativedelta

relativedelta.relativedelta(date1, date2)

months_difference = (r.years * 12) + r.months

3

Спробуйте щось подібне. Наразі він включає місяць, якщо обидві дати трапляються в одному місяці.

from datetime import datetime,timedelta

def months_between(start,end):
    months = []
    cursor = start

    while cursor <= end:
        if cursor.month not in months:
            months.append(cursor.month)
        cursor += timedelta(weeks=1)

    return months

Результат виглядає так:

>>> start = datetime.now() - timedelta(days=120)
>>> end = datetime.now()
>>> months_between(start,end)
[6, 7, 8, 9, 10]

Це все одно застосовує той самий циклічний підхід, тому я не обов'язково бачу користь ...
Joshkunz

1
Я не розумію, як це проблема. Петлі не ваш ворог.
Чарльз Хупер,

Ну, це потрібно було робити щоразу, коли це запит ajax. Я знаю, що цикли не є ворогом, але, здається, вони є повільним рішенням проблеми, яку слід вирішити набагато простіше.
Joshkunz


3

Ось як це зробити за допомогою Pandas FWIW:

import pandas as pd
pd.date_range("1990/04/03", "2014/12/31", freq="MS")

DatetimeIndex(['1990-05-01', '1990-06-01', '1990-07-01', '1990-08-01',
               '1990-09-01', '1990-10-01', '1990-11-01', '1990-12-01',
               '1991-01-01', '1991-02-01',
               ...
               '2014-03-01', '2014-04-01', '2014-05-01', '2014-06-01',
               '2014-07-01', '2014-08-01', '2014-09-01', '2014-10-01',
               '2014-11-01', '2014-12-01'],
              dtype='datetime64[ns]', length=296, freq='MS')

Зверніть увагу, що він починається з місяця після вказаної дати початку.


2

Це можна зробити за допомогою datetime.timedelta, де кількість днів для переходу до наступного місяця можна отримати за допомогою calender.monthrange. місячний діапазон повертає день тижня (0-6 ~ пн-нд) та кількість днів (28-31) для певного року та місяця.
Наприклад: місячний діапазон (2017, 1) повертає (6,31).

Ось сценарій, який використовує цю логіку для ітерації між двома місяцями.

from datetime import timedelta
import datetime as dt
from calendar import monthrange

def month_iterator(start_month, end_month):
    start_month = dt.datetime.strptime(start_month,
                                   '%Y-%m-%d').date().replace(day=1)
    end_month = dt.datetime.strptime(end_month,
                                 '%Y-%m-%d').date().replace(day=1)
    while start_month <= end_month:
        yield start_month
        start_month = start_month + timedelta(days=monthrange(start_month.year, 
                                                         start_month.month)[1])

`


Не могли б ви пояснити, як це вирішує проблему? Ми закликаємо людей додавати контекст до своїх відповідей; Дякую.
Gi0rgi0s

1
Додано пояснення
pankaj kumar

2

Багато людей вже дали вам хороші відповіді, щоб вирішити це, але я не прочитав жодного розуміння списку, тому я даю вам те, що я використовував для подібного випадку використання:


def compute_months(first_date, second_date):
    year1, month1, year2, month2 = map(
        int, 
        (first_date[:4], first_date[5:7], second_date[:4], second_date[5:7])
    )

    return [
        '{:0>4}-{:0>2}'.format(year, month)
        for year in range(year1, year2 + 1)
        for month in range(month1 if year == year1 else 1, month2 + 1 if year == year2 else 13)
    ]

>>> first_date = "2016-05"
>>> second_date = "2017-11"
>>> compute_months(first_date, second_date)
['2016-05',
 '2016-06',
 '2016-07',
 '2016-08',
 '2016-09',
 '2016-10',
 '2016-11',
 '2016-12',
 '2017-01',
 '2017-02',
 '2017-03',
 '2017-04',
 '2017-05',
 '2017-06',
 '2017-07',
 '2017-08',
 '2017-09',
 '2017-10',
 '2017-11']

1
#This definition gives an array of months between two dates.
import datetime
def MonthsBetweenDates(BeginDate, EndDate):
    firstyearmonths = [mn for mn in range(BeginDate.month, 13)]<p>
    lastyearmonths = [mn for mn in range(1, EndDate.month+1)]<p>
    months = [mn for mn in range(1, 13)]<p>
    numberofyearsbetween = EndDate.year - BeginDate.year - 1<p>
    return firstyearmonths + months * numberofyearsbetween + lastyearmonths<p>

#example
BD = datetime.datetime.strptime("2000-35", '%Y-%j')
ED = datetime.datetime.strptime("2004-200", '%Y-%j')
MonthsBetweenDates(BD, ED)

1

як і rangeфункція, коли місяцю 13 , переходьте до наступного року

def year_month_range(start_date, end_date):
    '''
    start_date: datetime.date(2015, 9, 1) or datetime.datetime
    end_date: datetime.date(2016, 3, 1) or datetime.datetime
    return: datetime.date list of 201509, 201510, 201511, 201512, 201601, 201602
    '''
    start, end = start_date.strftime('%Y%m'), end_date.strftime('%Y%m')
    assert len(start) == 6 and len(end) == 6
    start, end = int(start), int(end)

    year_month_list = []
    while start < end:
        year, month = divmod(start, 100)
        if month == 13:
            start += 88  # 201513 + 88 = 201601
            continue
        year_month_list.append(datetime.date(year, month, 1))

        start += 1
    return year_month_list

приклад у оболонці python

>>> import datetime
>>> s = datetime.date(2015,9,1)
>>> e = datetime.date(2016, 3, 1)
>>> year_month_set_range(s, e)
[datetime.date(2015, 11, 1), datetime.date(2015, 9, 1), datetime.date(2016, 1, 1), datetime.date(2016, 2, 1),
 datetime.date(2015, 12, 1), datetime.date(2015, 10, 1)]

1

Зазвичай 90 днів - це НЕ 3 місяці буквально, просто посилання.

Отже, нарешті, вам потрібно перевірити, чи дні більше 15, щоб додати +1 до місячного лічильника. або краще, додайте ще elif з півмісячним лічильником.

З цієї іншої відповіді на stackoverflow я нарешті закінчив з цим:

#/usr/bin/env python
# -*- coding: utf8 -*-

import datetime
from datetime import timedelta
from dateutil.relativedelta import relativedelta
import calendar

start_date = datetime.date.today()
end_date = start_date + timedelta(days=111)
start_month = calendar.month_abbr[int(start_date.strftime("%m"))]

print str(start_date) + " to " + str(end_date)

months = relativedelta(end_date, start_date).months
days = relativedelta(end_date, start_date).days

print months, "months", days, "days"

if days > 16:
    months += 1

print "around " + str(months) + " months", "(",

for i in range(0, months):
    print calendar.month_abbr[int(start_date.strftime("%m"))],
    start_date = start_date + relativedelta(months=1)

print ")"

Вихід:

2016-02-29 2016-06-14
3 months 16 days
around 4 months ( Feb Mar Apr May )

Я помітив, що це не працює, якщо ви додаєте більше днів, що залишились у поточному році, і це несподівано.


1

здається, що відповіді незадовільні, і з тих пір я використовую власний код, який легше зрозуміти

from datetime import datetime
from dateutil import relativedelta

date1 = datetime.strptime(str('2017-01-01'), '%Y-%m-%d')
date2 = datetime.strptime(str('2019-03-19'), '%Y-%m-%d')

difference = relativedelta.relativedelta(date2, date1)
months = difference.months
years = difference.years
# add in the number of months (12) for difference in years
months += 12 * difference.years
months

1
from datetime import datetime
from dateutil import relativedelta

def get_months(d1, d2):
    date1 = datetime.strptime(str(d1), '%Y-%m-%d')
    date2 = datetime.strptime(str(d2), '%Y-%m-%d')
    print (date2, date1)
    r = relativedelta.relativedelta(date2, date1)
    months = r.months +  12 * r.years
    if r.days > 0:
        months += 1
    print (months)
    return  months


assert  get_months('2018-08-13','2019-06-19') == 11
assert  get_months('2018-01-01','2019-06-19') == 18
assert  get_months('2018-07-20','2019-06-19') == 11
assert  get_months('2018-07-18','2019-06-19') == 12
assert  get_months('2019-03-01','2019-06-19') == 4
assert  get_months('2019-03-20','2019-06-19') == 3
assert  get_months('2019-01-01','2019-06-19') == 6
assert  get_months('2018-09-09','2019-06-19') == 10

0

Припускаючи, що upperDate завжди пізніше ніж lowerDate, і обидва - datetime.date:

if lowerDate.year == upperDate.year:
    monthsInBetween = range( lowerDate.month + 1, upperDate.month )
elif upperDate.year > lowerDate.year:
    monthsInBetween = range( lowerDate.month + 1, 12 )
    for year in range( lowerDate.year + 1, upperDate.year ):
        monthsInBetween.extend( range(1,13) )
    monthsInBetween.extend( range( 1, upperDate.month ) )

Я не перевірив це ретельно, але, схоже, це повинно зробити трюк.


0

Ось метод:

def months_between(start_dt, stop_dt):
    month_list = []
    total_months = 12*(stop_dt.year-start_dt.year)+(stop_dt.month-start_d.month)+1
    if total_months > 0:
        month_list=[ datetime.date(start_dt.year+int((start_dt+i-1)/12), 
                                   ((start_dt-1+i)%12)+1,
                                   1) for i in xrange(0,total_months) ]
    return month_list

Це спочатку обчислення загальної кількості місяців між двома датами включно. Потім він створює список, використовуючи першу дату як базу, і виконує арифметику модуля для створення об'єктів дати.


0

Мені насправді потрібно було зробити щось подібне зараз

Закінчилося написання функції, яка повертає список кортежів із зазначенням startіend кожного місяця між двома наборами дат, щоб я міг написати деякі SQL-запити із зворотного боку для щомісячних підсумків продажів тощо.

Я впевнений, що це може покращити той, хто знає, що вони роблять, але сподіваюся, що це допоможе ...

Повернене значення виглядає наступним чином (генерується на сьогодні - 365 днів до сьогодні як приклад)

[   (datetime.date(2013, 5, 1), datetime.date(2013, 5, 31)),
    (datetime.date(2013, 6, 1), datetime.date(2013, 6, 30)),
    (datetime.date(2013, 7, 1), datetime.date(2013, 7, 31)),
    (datetime.date(2013, 8, 1), datetime.date(2013, 8, 31)),
    (datetime.date(2013, 9, 1), datetime.date(2013, 9, 30)),
    (datetime.date(2013, 10, 1), datetime.date(2013, 10, 31)),
    (datetime.date(2013, 11, 1), datetime.date(2013, 11, 30)),
    (datetime.date(2013, 12, 1), datetime.date(2013, 12, 31)),
    (datetime.date(2014, 1, 1), datetime.date(2014, 1, 31)),
    (datetime.date(2014, 2, 1), datetime.date(2014, 2, 28)),
    (datetime.date(2014, 3, 1), datetime.date(2014, 3, 31)),
    (datetime.date(2014, 4, 1), datetime.date(2014, 4, 30)),
    (datetime.date(2014, 5, 1), datetime.date(2014, 5, 31))]

Код наступним чином (містить деякі налагоджувальні матеріали, які можна видалити):

#! /usr/env/python
import datetime

def gen_month_ranges(start_date=None, end_date=None, debug=False):
    today = datetime.date.today()
    if not start_date: start_date = datetime.datetime.strptime(
        "{0}/01/01".format(today.year),"%Y/%m/%d").date()  # start of this year
    if not end_date: end_date = today
    if debug: print("Start: {0} | End {1}".format(start_date, end_date))

    # sense-check
    if end_date < start_date:
        print("Error. Start Date of {0} is greater than End Date of {1}?!".format(start_date, end_date))
        return None

    date_ranges = []  # list of tuples (month_start, month_end)

    current_year = start_date.year
    current_month = start_date.month

    while current_year <= end_date.year:
        next_month = current_month + 1
        next_year = current_year
        if next_month > 12:
            next_month = 1
            next_year = current_year + 1

        month_start = datetime.datetime.strptime(
            "{0}/{1}/01".format(current_year,
                                current_month),"%Y/%m/%d").date()  # start of month
        month_end = datetime.datetime.strptime(
            "{0}/{1}/01".format(next_year,
                                next_month),"%Y/%m/%d").date()  # start of next month
        month_end  = month_end+datetime.timedelta(days=-1)  # start of next month less one day

        range_tuple = (month_start, month_end)
        if debug: print("Month runs from {0} --> {1}".format(
            range_tuple[0], range_tuple[1]))
        date_ranges.append(range_tuple)

        if current_month == 12:
            current_month = 1
            current_year += 1
            if debug: print("End of year encountered, resetting months")
        else:
            current_month += 1
            if debug: print("Next iteration for {0}-{1}".format(
                current_year, current_month))

        if current_year == end_date.year and current_month > end_date.month:
            if debug: print("Final month encountered. Terminating loop")
            break

    return date_ranges


if __name__ == '__main__':
    print("Running in standalone mode. Debug set to True")
    from pprint import pprint
    pprint(gen_month_ranges(debug=True), indent=4)
    pprint(gen_month_ranges(start_date=datetime.date.today()+datetime.timedelta(days=-365),
                            debug=True), indent=4)

0

Якщо припустити, що ви хотіли знати "частку" місяця, в якій були дати, що я і зробив, тоді вам потрібно зробити трохи більше роботи.

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 lest 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)

наводить приклад, який визначає кількість місяців між двома датами включно, включаючи частку кожного місяця, в яку входить дата. Це означає, що ви можете визначити, скільки місяців знаходиться між 20.01.2015 і 14.02.2015 , де частка дати в січні місяці визначається кількістю днів у січні; або також враховуючи, що кількість днів у лютому може змінюватися з року в рік.

Для довідки цей код також є на github - https://gist.github.com/andrewyager/6b9284a4f1cdb1779b10


0

Спробуйте це:

 dateRange = [datetime.strptime(dateRanges[0], "%Y-%m-%d"),
             datetime.strptime(dateRanges[1], "%Y-%m-%d")]
delta_time = max(dateRange) - min(dateRange)
#Need to use min(dateRange).month to account for different length month
#Note that timedelta returns a number of days
delta_datetime = (datetime(1, min(dateRange).month, 1) + delta_time -
                           timedelta(days=1)) #min y/m/d are 1
months = ((delta_datetime.year - 1) * 12 + delta_datetime.month -
          min(dateRange).month)
print months

Не має значення, в якому порядку ви вводите дати, і це враховує різницю в тривалості місяця.


Зауважте, що ваші дати не збігаються. Найпростіший спосіб був би, якщо delta_time.days = 0: місяці = 0 інакше решта рутини.
om_henners

0

Це працює ...

from datetime import datetime as dt
from dateutil.relativedelta import relativedelta
def number_of_months(d1, d2):
    months = 0
    r = relativedelta(d1,d2)
    if r.years==0:
        months = r.months
    if r.years>=1:
        months = 12*r.years+r.months
    return months
#example 
number_of_months(dt(2017,9,1),dt(2016,8,1))

0
from datetime import datetime

def diff_month(start_date,end_date):
    qty_month = ((end_date.year - start_date.year) * 12) + (end_date.month - start_date.month)

    d_days = end_date.day - start_date.day

    if d_days >= 0:
        adjust = 0
    else:
        adjust = -1
    qty_month += adjust

    return qty_month

diff_month(datetime.date.today(),datetime(2019,08,24))


#Examples:
#diff_month(datetime(2018,02,12),datetime(2019,08,24)) = 18
#diff_month(datetime(2018,02,12),datetime(2018,08,10)) = 5
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.