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

93

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

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

Йошкунц
источник
Вы спрашиваете, ЧИСЛО месяцев между двумя датами или сколько месяцев на самом деле?
Чарльз Хупер,
в моем решении: я не увеличиваю «количество секунд в месяц». Я просто увеличиваю число с 1 до 2, а потом с 2 до 3.
неполярность
Я просто хотел, чтобы вы знали, что даже если вам не понравился мой ответ, потому что он «имел цикл», вы выбрали ответ, в котором есть ДВА цикла. Понимание списков по-прежнему остается циклическим.
Чарльз Хупер,

Ответы:

1

Обновление 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)

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

неполярность
источник
1
Хорошо, я вижу некоторые проблемы с месяцами, такими как февраль, если увеличение выполняется по месяцам, а не по неделям.
Joshkunz
Я не увеличиваю «количество секунд в месяц». Я просто увеличиваю число 1до 2, а затем от 2до 3.
неполярность
199

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

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
это дает неправильный результат. между «2015-04-30» и «2015-05-01» получается 1 месяц, что на самом деле составляет всего 1 день.
Rao
21
@Rao. вот почему я сказал: «Существует более одного способа определить количество месяцев между двумя датами». Формального определения этому вопросу пока нет. Вот почему я предлагаю указать тестовые примеры вместе с определением.
Джон Ла Рой,
2
Я рекомендую добавить abs () вокруг вычитаний, чтобы позволить d1 быть меньше d2: return abs (d1.year - d2.year) * 12 + abs (d1.month - d2.month)
lszrh
Вы уверены, @LukasSchulze? Если d1 меньше d2, вам все равно придется вычесть это число из первого, верно?
Лилит-Элина
41

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

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)]
N1B4
источник
Этот! Это приспосабливается к множеству необычных особых ситуаций благодаря использованию rrule. Обратите внимание, что выходные значения - это даты и время, поэтому вы можете преобразовать их во все, что вам нравится (включая строки), чтобы соответствовать тому, что показывают другие.
Иезекииль Круглик
Это не удается, когда 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)тогда rrule правильно считает месяцы.
Алла Сорокина
38

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

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)
aamir23
источник
удалите ненужные str()строковые литералы.
jfs
7
Ничего не стоит, если дельта больше 1 года, r.monthsначнется с 0
jbkkd
2
обычно я использую r.months * (r.years + 1), так как это соответствует тому, о чем говорил
@jbkkd
9
@triunenature, это выглядит не так, наверняка это должно быть что-то вроде r.months + (r.years * 12)
Моатаз Элмасри
2
Да, это не работает, если разница в датах больше года.
HansG600
11

Вы можете легко рассчитать это, используя 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)]
Назарий Гудзоватый
источник
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
srodriguex
источник
1
@edouard Как вы можете видеть в приведенном мной примере, даты указаны в разные годы. Тем не менее, str()слепок, который я сделал, совершенно не нужен.
srodriguex
6
это не работает, если даты охватывают более года, нужно добавитьrelativedelta.relativedelta(date2, date1).years * 12
мюон
delta.years*12 + delta.months
user2682863 06
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))
Хоттабов
источник
недооцененное решение
reabow
6
from dateutil import relativedelta

relativedelta.relativedelta(date1, date2)

months_difference = (r.years * 12) + r.months
Ятабани
источник
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)
Saaj
источник
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

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

Луис Альберто Сантана
источник
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]
Чарльз Хупер
источник
Тем не менее, здесь используется тот же подход к циклу, так что я не обязательно вижу выгоду ...
Джошкунц
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')

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

conner.xyz
источник
2

Это можно сделать с помощью 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])

`

панкадж кумар
источник
Не могли бы вы объяснить, как это решает проблему? Мы призываем людей добавлять контекст к своим ответам; Спасибо.
Gi0rgi0s
1
Добавил объяснение
панкадж кумар
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']

H4kim
источник
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)]
WeizhongTu
источник
1

Обычно 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 )

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

m3nda
источник
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
Grandia
источник
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
Йогеш Кушваха
источник
1

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

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 года будет равна нулю (поскольку разница составляет всего один день).

Гаурав Агарвал
источник
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

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

om_henners
источник
Обратите внимание, что это не учитывает совпадения ваших дат. Проще всего было бы, если delta_time.days = 0: months = 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))
Джордж Памфилис
источник