Лучший способ найти месяцы между двумя датами
Мне нужно иметь возможность точно найти месяцы между двумя датами в 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 и проверяю, больше ли числовое значение месяца (если месяц не декабрь, тогда он проверяет, меньше ли дата), Если значение больше, я добавляю его в список. месяцев и продолжайте покачиваться, пока я не доберусь до своей конечной даты.
Это работает отлично, просто не похоже на хороший способ сделать это...
42 ответа
Обновление 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)
просто псевдо-код на данный момент, не проверял, но я думаю, что идея в том же духе будет работать.
Начните с определения некоторых тестовых случаев, затем вы увидите, что функция очень проста и не требует циклов
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
Вы должны добавить несколько тестовых случаев к вашему вопросу, так как существует множество потенциальных угловых случаев, которые можно охватить - существует несколько способов определения количества месяцев между двумя датами.
Один вкладыш для поиска списка дат, с шагом в месяц, между двумя датами.
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)]
Это сработало для меня -
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 * (r.years+1)
from dateutil import relativedelta
relativedelta.relativedelta(date1, date2)
months_difference = (r.years * 12) + r.months
Вы можете легко рассчитать это, используя 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)]
Получите конечный месяц (относительно года и месяца начального месяца, например: январь 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)]
Мое простое решение:
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))
Этот пост прибивает это! использование 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
Многие люди уже дали вам хорошие ответы для решения этой проблемы, но я не читал ни одного использования понимания списков, поэтому я даю вам то, что использовал для аналогичного варианта использования:
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/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 (эта операция четко не определена).
Вот как это сделать с помощью 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')
Обратите внимание, что он начинается с месяца после указанной даты начала.
Несколько претенциозное решение от @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)
Вы также можете использовать библиотеку стрелок. Это простой пример:
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
Надеюсь это поможет!
Узнайте разницу в количестве дней, месяцев и лет между двумя датами.
import datetime
from dateutil.relativedelta import relativedelta
iphead_proc_dt = datetime.datetime.now()
new_date = iphead_proc_dt + relativedelta(months=+25, days=+23)
# Get Number of Days difference bewtween two dates
print((new_date - iphead_proc_dt).days)
difference = relativedelta(new_date, iphead_proc_dt)
# Get Number of Months difference bewtween two dates
print(difference.months + 12 * difference.years)
# Get Number of Years difference bewtween two dates
print(difference.years)
Существует простое решение, основанное на 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))
Вы можете использовать http://labix.org/python-dateutil. См. Python: разница между двумя датами в месяцах
Попробуйте что-то вроде этого. В настоящее время он включает месяц, если обе даты совпадают.
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]
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
Как 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)]
Это можно сделать с помощью 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])
`
Кажется , что ответы неудовлетворительные, и с тех пор я использую свой собственный код, который легче понять
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
Чтобы получить количество полных месяцев между двумя датами:
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
Обычно 90 дней НЕ 3 месяца в буквальном смысле, просто ссылка.
Итак, наконец, вам нужно проверить, больше ли дней 15, чтобы добавить +1 к счетчику месяцев. или лучше, добавьте еще один elif с полугодовым счетчиком.
Из этого другого ответа stackru я наконец-то закончил с этим:
#/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 )
Я заметил, что это не сработает, если вы добавите больше дней, оставшихся в текущем году, и это неожиданно.
#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)
Вот мое решение для этого:
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 года будет равна нулю (поскольку разница составляет всего один день).
Это мой способ сделать это:
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
Я просто использую месяц, чтобы создать диапазон дат и рассчитать длину.
Предполагая, что вы хотите знать "долю" месяца, в котором были даты, что я и сделал, вам нужно проделать немного больше работы.
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
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
Вы можете использовать приведенный ниже код, чтобы получить месяц между двумя датами:
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
.