Лучший способ найти месяцы между двумя датами

Мне нужно иметь возможность точно находить месяцы между двумя датами в 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 в объекты datetime python. Затем я добавляю неделю к объекту start datetime и проверяю, больше ли числовое значение месяца (если месяц не декабрь, тогда он проверяет, меньше ли дата). Если значение больше, я добавляю его в список месяцев и продолжайте повторять, пока я не дойду до даты окончания.

Он работает отлично, просто не похоже на хороший способ сделать это ...


person Joshkunz    schedule 28.10.2010    source источник
comment
Вы спрашиваете, ЧИСЛО месяцев между двумя датами, или сколько месяцев на самом деле?   -  person Charles Hooper    schedule 28.10.2010
comment
в моем решении: я не увеличиваю количество секунд в месяц. Я просто увеличиваю число с 1 до 2, а затем с 2 до 3 позже.   -  person nonopolarity    schedule 28.10.2010
comment
Я просто хотел, чтобы вы знали, что даже если вам не понравился мой ответ, потому что в нем был цикл, вы выбрали ответ, содержащий ДВА цикла. Понимание списков по-прежнему остается циклическим.   -  person Charles Hooper    schedule 31.10.2010


Ответы (32)


Обновление 2018-04-20: похоже, что OP @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)

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

person nonopolarity    schedule 28.10.2010
comment
Хорошо, я вижу некоторые проблемы с месяцами, такими как февраль, если увеличение выполняется по месяцам, а не по неделям. - person Joshkunz; 28.10.2010
comment
Я не увеличиваю количество секунд на месяц. Я просто увеличиваю число 1 до 2, а потом с 2 до 3. - person nonopolarity; 28.10.2010

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

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

Вы должны добавить несколько тестовых примеров к вашему вопросу, так как есть много потенциальных угловых случаев, которые нужно охватить - существует более одного способа определить количество месяцев между двумя датами.

person John La Rooy    schedule 28.10.2010
comment
это дает неправильный результат. получается 1 месяц между 30 апреля 2015 и 01 мая 2015, что на самом деле составляет всего 1 день. - person Rao; 13.04.2015
comment
@Rao. вот почему я сказал, что существует более одного способа определить количество месяцев между двумя датами. Формального определения этому вопросу пока нет. Вот почему я предлагаю предоставить тестовые наборы вместе с определением. - person John La Rooy; 13.04.2015
comment
Я рекомендую добавить abs () вокруг вычитаний, чтобы позволить d1 быть меньше d2: return abs (d1.year - d2.year) * 12 + abs (d1.month - d2.month) - person lszrh; 10.09.2016
comment
Ты уверен, @LukasSchulze? Если d1 меньше d2, вам все равно придется вычесть это число из первого, верно? - person Lilith-Elina; 12.05.2017

Один вкладыш, чтобы найти список дат и времени с приращением по месяцам между двумя датами.

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)]
person N1B4    schedule 03.02.2015
comment
Этот! Это приспосабливается к множеству необычных особых ситуаций благодаря использованию rrule. Обратите внимание, что выходные значения - это даты и время, поэтому вы можете преобразовать их во все, что вам нравится (включая строки), чтобы соответствовать тому, что показывают другие. - person Ezekiel Kruglick; 22.01.2016
comment
Это не удается, когда strt_dt - 2001-1-29, из-за отсутствия високосного дня в этом году. - person C S; 22.11.2016
comment
@ C-S Поскольку 2001-2-29 не существует, rrule соответствующим образом исключает его при увеличении по месяцам 29-го числа каждого месяца. Включение этой функции было бы ошибкой, но здесь это не так. - person N1B4; 22.11.2016
comment
OP попросил список месяцев между датами начала и окончания. По вашему методу на моем примере пропущен февраль. Конечно, ваше решение можно спасти, например, установив дату начала на первое число месяца. - person C S; 22.11.2016
comment
Это хорошее решение, которое экономит мне много времени. Мы можем улучшить его, указав дату начала [dt for dt in rrule(MONTHLY, bymonthday=10,dtstart=strt_dt, until=end_dt)] - person Pengju Zhao; 25.07.2017
comment
Мне понравился комментарий Пэнджу Чжао. Я сделал одно небольшое изменение, чтобы получить последний день месяца. dates = [dt for dt in rrule(MONTHLY, bymonthday=-1, dtstart=strt_dt, until=end_dt)] - person Russ W; 28.06.2019
comment
Вернул неверный результат, когда дата начала была 30 мая 2020 года, а дата окончания - 29 июня 2020 года. Он вернул только май 2020 года. - person Alla Sorokina; 30.06.2020
comment
Помните об этом. На самом деле у них есть примечание в документации, в котором говорится, что rrule может иметь неожиданное поведение, когда, например, дата начала приходится на конец месяца (см. Примечание в dateutil.readthedocs.io/en/stable/rrule.html). Один из способов избежать этого - заменить даты первым днем ​​месяца: start_date_first = start_date.replace(day=1), end_date_first = end_date.replace(day=1) Тогда rrule правильно считает месяцы. - person Alla Sorokina; 30.06.2020

Это сработало для меня -

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)
person aamir23    schedule 18.09.2015
comment
удалите ненужные str() вокруг строковых литералов. - person jfs; 16.12.2015
comment
Ничего не стоит, если дельта больше 1 года, r.months будет начинаться с 0 - person jbkkd; 26.04.2017
comment
обычно я использую r.months * (r.years + 1), так как это соответствует тому, о чем говорил @jbkkd - person triunenature; 26.08.2017
comment
@triunenature это выглядит неправильно, наверняка это должно быть что-то вроде r.months + (r.years * 12) - person Moataz Elmasry; 04.04.2018
comment
Да, это не сработает, если разница в датах больше года. - person HansG600; 11.08.2018
comment
Покупатель будьте осторожны - если вы хотите показать разницу, например, 1 между некоторыми произвольными датами в ноябре и декабре, это не работает. Т.е. вы получите 0 для date1 = date (2019, 11, 30) и date2 = date (2019,12,29), когда вы можете захотеть 1 (как и я). ИМО, это важный крайний случай. - person Sibs; 02.11.2020

Вы можете легко рассчитать это, используя rrule из модуля 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)]
person Nazarii Gudzovatyi    schedule 08.02.2014

Получите конечный месяц (относительно года и месяца начального месяца, например: 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)]
person Vin-G    schedule 28.10.2010

Этот пост делает это! Используйте 1_.

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
person srodriguex    schedule 04.06.2016
comment
@edouard Как вы можете видеть в приведенном мной примере, даты указаны в разные годы. Тем не менее, примененный мной str() слепок совершенно не нужен. - person srodriguex; 12.09.2016
comment
это не работает, если даты охватывают более года, вам нужно добавить relativedelta.relativedelta(date2, date1).years * 12 - person muon; 23.05.2018

Мое простое решение:

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))
person Hottabov    schedule 29.04.2018
comment
недооцененное решение - person reabow; 27.05.2020

Определите «месяц» как 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 th + 1 месяц должно быть 15 th июля, обычно неясно, будет ли 30 th января + 1 месяц в феврале или марте. В последнем случае вам может потребоваться вычислить дату как 30 февраля th, а затем "скорректировать" ее на 2 марта nd. Но когда вы это сделаете, вы обнаружите, что 2 марта nd - 1 месяц явно соответствует 2 февраля nd. Ergo, reductio ad absurdum (эта операция плохо определена).

person Seth    schedule 28.10.2010

Существует простое решение, основанное на 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))
person Daniel Reis    schedule 27.01.2012

Немного приукрашенное решение от @ 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)
person saaj    schedule 28.05.2013

Вы также можете использовать библиотеку arrow. Это простой пример:

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

Надеюсь это поможет!

person Community    schedule 20.06.2014

Попробуйте что-нибудь подобное. В настоящее время он включает месяц, если обе даты совпадают с одним и тем же месяцем.

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]
person Charles Hooper    schedule 28.10.2010
comment
Тем не менее, здесь используется тот же подход с использованием цикла, поэтому я не обязательно вижу выгоду ... - person Joshkunz; 28.10.2010
comment
Я не понимаю, почему это проблема. Петли вам не враг. - person Charles Hooper; 28.10.2010
comment
Что ж, это нужно делать каждый раз, когда это запрос ajax. Я знаю, что петли - не враг, но похоже, что они являются медленным решением проблемы, которую следует решать гораздо проще. - person Joshkunz; 28.10.2010

Вы можете использовать python-dateutil. См. Python: разница двух дат в месяцах

person sandinmyjoints    schedule 10.08.2011

Вот как это сделать с помощью 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')

Обратите внимание, что он начинается с месяца после заданной даты начала.

person conner.xyz    schedule 26.04.2017

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


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']

person H4kim    schedule 18.09.2019

Это можно сделать с помощью datetime.timedelta, где количество дней для перехода к следующему месяцу можно получить с помощью calender.monthrange. monthrange возвращает день недели (0-6 ~ пн-вс) и количество дней (28-31) для данного года и месяца.
Например: monthrange (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])

`

person pankaj kumar    schedule 24.07.2018
comment
Не могли бы вы объяснить, как это решает проблему? Мы призываем людей добавлять контекст к своим ответам; Благодарю. - person Gi0rgi0s; 24.07.2018
comment
Добавлено объяснение - person pankaj kumar; 30.07.2018

точно так же, как функция 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)]
person WeizhongTu    schedule 16.12.2015

Обычно 90 дней - это НЕ 3 месяца в буквальном смысле, это просто справка.

Итак, наконец, вам нужно проверить, больше ли дней 15, чтобы добавить +1 к счетчику месяцев. или лучше добавить еще один эльф с полумесячным счетчиком.

Из этого другого ответа 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 )

Я заметил, что это не сработает, если вы добавите больше дней, оставшихся в текущем году, и это неожиданно.

person m3nda    schedule 29.02.2016

кажется, что ответы неудовлетворительны, и с тех пор я использую свой собственный код, который легче понять

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
person grandia    schedule 14.03.2019

Вот мое решение для этого:

def calc_age_months(from_date, to_date):
    from_date = time.strptime(from_date, "%Y-%m-%d")
    to_date = time.strptime(to_date, "%Y-%m-%d")

    age_in_months = (to_date.tm_year - from_date.tm_year)*12 + (to_date.tm_mon - from_date.tm_mon)

    if to_date.tm_mday < from_date.tm_mday:
        return age_in_months -1
    else
        return age_in_months

Это также будет обрабатывать некоторые крайние случаи, когда разница в месяцах между 31 декабря 2018 года и 1 января 2019 года будет равна нулю (поскольку разница составляет всего один день).

person Gaurav Agarwal    schedule 22.11.2019

Предполагая, что 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 ) )

Я не тестировал это полностью, но похоже, что это должно помочь.

person Scott    schedule 28.10.2010

Вот способ:

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

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

person Dan    schedule 28.10.2010

Мне прямо сейчас нужно было сделать что-то похожее

Закончил написанием функции, которая возвращает список кортежей, указывающих 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)
person James    schedule 08.05.2014

Предполагая, что вы хотите узнать «долю» месяца, в котором были даты, что я и сделал, вам нужно проделать немного больше работы.

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)

предоставляет пример, который вычисляет количество месяцев между двумя датами включительно, включая долю каждого месяца, в котором указана дата. Это означает, что вы можете вычислить, сколько месяцев между 2015-01-20 и 2015-02-14 , где доля числа в январе месяце определяется количеством дней в январе; или в равной степени с учетом того, что количество дней в феврале может меняться из года в год.

Для справки, этот код также есть на github - https://gist.github.com/andrewyager/6b9284a4f1cdb1779b10 < / а>

person Andrew Yager    schedule 15.11.2015

Попробуй это:

 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

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

person om_henners    schedule 28.10.2010
comment
Обратите внимание, что это не учитывает совпадения ваших дат. Проще всего было бы, если delta_time.days = 0: months = 0, иначе остальная часть рутины. - person om_henners; 28.10.2010

Это работает...

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))
person George Pamfilis    schedule 08.08.2016

Вот мой способ сделать это:

Start_date = "2000-06-01"
End_date   = "2001-05-01"

month_num  = len(pd.date_range(start = Start_date[:7], end = End_date[:7] ,freq='M'))+1

Я просто использую месяц для создания диапазона дат и вычисления длины.

person Arthur    schedule 26.04.2019

Чтобы получить количество полных месяцев между двумя датами:

import datetime

def difference_in_months(start, end):
    if start.year == end.year:
        months = end.month - start.month
    else:
        months = (12 - start.month) + (end.month)

    if start.day > end.day:
        months = months - 1

    return months
person pieguy    schedule 02.10.2019
comment
Это работает, только если разница составляет не более года. - person maximus ツ; 12.05.2020

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

OrderedDict(((start_date + timedelta(_)).strftime(date_format), None) for _ in xrange((end_date - start_date).days)).keys()

где start_date и end_date должны быть правильной датой, а date_format - это формат, в котором вы хотите получить результат даты ..

В вашем случае date_format будет %b %Y.

person akshaypjoshi    schedule 07.01.2020

Вы можете использовать что-то вроде:

import datetime
days_in_month = 365.25 / 12  # represent the average of days in a month by year
month_diff = lambda end_date, start_date, precision=0: round((end_date - start_date).days / days_in_month, precision)
start_date = datetime.date(1978, 12, 15)
end_date = datetime.date(2012, 7, 9)
month_diff(end_date, start_date)  # should show 403.0 months
person Dubas    schedule 10.07.2012

person    schedule
comment
Предоставьте минимальное объяснение кода, который вы публикуете. - person Pedro Henrique; 17.05.2019