Python 查找两个日期之间月份的最​​佳方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4039879/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 13:57:15  来源:igfitidea点击:

Best way to find the months between two dates

pythondatetimemonthcalendardate-math

提问by Joshkunz

I have the need to be able to accurately find the months between two dates in python. I have a solution that works but its not very good (as in elegant) or fast.

我需要能够准确地找到 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

So just to explain, what I'm doing here is taking the two dates and converting them from iso format into python datetime objects. Then I loop through adding a week to the start datetime object and check if the numerical value of the month is greater (unless the month is December then it checks if the date is less), If the value is greater I append it to the list of months and keep looping through until I get to my end date.

所以只是为了解释一下,我在这里做的是获取两个日期并将它们从iso格式转换为python datetime对象。然后我循环添加一个星期到开始日期时间对象并检查月份的数值是否更大(除非月份是十二月然后它检查日期是否更小),如果值更大我将它附加到列表中几个月并不断循环,直到我到达结束日期。

It works perfectly it just doesn't seem like a good way of doing it...

它工作得很好,只是看起来不是一个好方法......

采纳答案by nonopolarity

Update 2018-04-20:it seems that OP @Joshkunz was asking for finding which monthsare between two dates, instead of "how many months" are between two dates. So I am not sure why @JohnLaRooy is upvoted for more than 100 times. @Joshkunz indicated in the comment under the original question he wanted the actual dates [or the months], instead of finding the total number of months.

2018 年 420 日更新:似乎 OP @Joshkunz 要求查找两个日期之间的月份,而不是两个日期之间的“多少个月”。所以我不确定为什么@JohnLaRooy 被投票超过 100 次。@Joshkunz 在原始问题下的评论中指出,他想要实际日期 [或月份],而不是找到总月份数

So it appeared the question wanted, for between two dates 2018-04-11to 2018-06-01

所以,就出现希望的问题,因为两个日期之间2018-04-11,以2018-06-01

Apr 2018, May 2018, June 2018 

And what if it is between 2014-04-11to 2018-06-01? Then the answer would be

如果介于2014-04-11to之间2018-06-01呢?那么答案就是

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

So that's why I had the following pseudo code many years ago. It merely suggested using the two months as end points and loop through them, incrementing by one month at a time. @Joshkunz mentioned he wanted the "months" and he also mentioned he wanted the "dates", without knowing exactly, it was difficult to write the exact code, but the idea is to use one simple loop to loop through the end points, and incrementing one month at a time.

所以这就是多年前我有以下伪代码的原因。它只是建议使用两个月作为终点并循环遍历它们,一次增加一个月。@Joshkunz 提到他想要“月份”,他还提到他想要“日期”,在不确切知道的情况下,很难编写确切的代码,但其想法是使用一个简单的循环来遍历端点,并且一次递增一个月。

The answer 8 years ago in 2010:

8年前的2010年的答案:

If adding by a week, then it will approximately do work 4.35 times the work as needed. Why not just:

如果增加一周,那么它将根据需要大约执行 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)

just pseduo code for now, haven't tested, but i think the idea along the same line will work.

现在只是伪代码,还没有测试,但我认为沿着同一条线的想法会奏效。

回答by Charles Hooper

Try something like this. It presently includes the month if both dates happen to be in the same month.

尝试这样的事情。如果两个日期碰巧在同一个月,则当前包括月份。

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

Output looks like:

输出看起来像:

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

回答by Scott

Assuming upperDate is always later than lowerDate and both are datetime.date objects:

假设 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 ) )

I haven't tested this thoroughly, but it looks like it should do the trick.

我还没有彻底测试过,但看起来它应该可以解决问题。

回答by Seth

Define a "month" as 1/12year, then do this:

将“月份”定义为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

You might try to define a month as "a period of either 29, 28, 30 or 31 days (depending on the year)". But you you do that, you have an additional problem to solve.

您可能会尝试将一个月定义为“29、28、30 或 31 天的时间段(取决于年份)”。但是你这样做了,你还有一个额外的问题需要解决。

While it's usually clear that June 15th+ 1 month should be July 15th, it's not usually not clear if January 30th+ 1 month is in February or March. In the latter case, you may be compelled to compute the date as February 30th, then "correct" it to March 2nd. But when you do that, you'll find that March 2nd- 1 month is clearly February 2nd. Ergo, reductio ad absurdum (this operation is not well defined).

虽然通常很清楚 6 月 15+ 1 个月应该是 7 月 15,但通常不清楚 1 月 30+ 1 个月是在 2 月还是 3 月。在后一种情况下,你可能被迫来计算日期为2月30日,那么“正确”它3月2日第二。但是,当你这样做,你会发现,3月2日第二- 1个月显然是2月2日第二。Ergo,reductio ad absurdum(这个操作没有很好的定义)。

回答by om_henners

Try this:

尝试这个:

 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

Shouldn't matter what order you input the dates, and it takes into account the difference in month lengths.

输入日期的顺序无关紧要,它会考虑月份长度的差异。

回答by Vin-G

Get the ending month (relative to the year and month of the start month ex: 2011 January = 13 if your start date starts on 2010 Oct) and then generate the datetimes beginning the start month and that end month like so:

获取结束月份(相对于开始月份的年份和月份,例如:2011 年 1 月 = 13,如果您的开始日期从 2010 年 10 月开始),然后生成开始月份和结束月份的日期时间,如下所示:

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

if both dates are on the same year, it could also be simply written as:

如果两个日期都在同一年,也可以简单地写成:

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

回答by Dan

Here is a method:

这是一个方法:

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

This is first computing the total number of months between the two dates, inclusive. Then it creates a list using the first date as the base and performs modula arithmetic to create the date objects.

这是首先计算两个日期之间的总月数,包括在内。然后它使用第一个日期作为基础创建一个列表,并执行模运算来创建日期对象。

回答by John La Rooy

Start by defining some test cases, then you will see that the function is very simple and needs no loops

先定义一些测试用例,然后你会看到函数很简单,不需要循环

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

You should add some test cases to your question, as there are lots of potential corner cases to cover - there is more than one way to define the number of months between two dates.

您应该在问题中添加一些测试用例,因为有很多潜在的极端情况需要涵盖 - 定义两个日期之间的月数的方法不止一种。

回答by Daniel Reis

There is a simple solution based on 360 day years, where all months have 30 days. It fits most use cases where, given two dates, you need to calculate the number of full months plus the remaining days.

有一个基于 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))