找到两个日期之间的月份的最佳方法

Jos*_*unz 81 python datetime monthcalendar date-math

我需要能够准确地找到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
Run Code Online (Sandbox Code Playgroud)

所以只是为了解释一下,我在这里做的是将两个日期转换为iso格式转换为python datetime对象.然后我循环通过在开始日期时间对象中添加一周,并检查月份的数值是否更大(除非月份是12月,然后检查日期是否更少),如果值更大,我将其附加到列表几个月,并一直循环,直到我到达我的结束日期.

它完美地工作它似乎不是一个好方法...

Joh*_*ooy 175

首先定义一些测试用例,然后你会发现函数非常简单,不需要循环

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
Run Code Online (Sandbox Code Playgroud)

您应该在问题中添加一些测试用例,因为有很多潜在的案例要涵盖 - 有两种方法可以定义两个日期之间的月数.

  • @Rao.这就是为什么我说"有两种方法可以定义两个日期之间的月数".这个问题仍然没有正式的定义.这也是为什么我建议应该在定义旁边提供测试用例的原因. (19认同)
  • 我建议在减法周围添加 abs() 以允许 d1 小于 d2: return abs(d1.year - d2.year)*12 + abs(d1.month - d2.month) (3认同)
  • 它给出了错误的结果.它在"2015-04-30"和"2015-05-01"之间产生了1个月,实际上只有1天. (2认同)
  • 使用 pandas 可能更容易: pandas.date_range(start_date, end_date,freq='MS').strftime('%Y-%m').tolist() (2认同)

N1B*_*1B4 36

一个班轮,用于查找两个日期之间按月递增的日期时间列表.

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)]
Run Code Online (Sandbox Code Playgroud)

  • 请注意这一点。他们实际上在文档中有一条注释,指出 rrule 可能会出现“令人惊讶的行为,例如,当开始日期发生在月底时”(请参阅​​ https://dateutil.readthedocs.io/en/stable/rrule 中的注释) .html)。避免这种情况的一种方法是将日期替换为每月的第一天: `start_date_first = start_date.replace(day=1), end_date_first = end_date.replace(day=1)` 然后 rrule 正确计算月份。 (3认同)
  • OP要求提供开始日期和结束日期之间的月份列表.在我的示例中使用您的方法错过了二月.当然,例如,仍然可以通过将开始日期调整到本月的第一天来挽救您的解决方案. (2认同)
  • 这是一个很好的解决方案,它为我节省了很多时间。我们可以通过指定开始日期```[[dt for dt in rrule(MONTHLY,bymonthday = 10,dtstart = strt_dt,until = end_dt)]`)来使它更好。 (2认同)

aam*_*r23 24

这对我有用 -

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)
Run Code Online (Sandbox Code Playgroud)

  • @triunenature看起来不对,肯定应该像r.months +(r.years*12) (9认同)
  • 值得一提的是,如果delta超过1年,`r.months`将从0开始 (7认同)
  • 通常我会做r.months*(r.years + 1),因为这会调整@jbkkd所说的内容 (2认同)
  • 是的,如果日期相隔一年以上,这将不起作用。 (2认同)

Vin*_*n-G 10

获取结束月份(相对于开始月份的年份和月份,例如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)
      )]
Run Code Online (Sandbox Code Playgroud)

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

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


小智 10

您可以使用dateutil模块中的rrule轻松计算:

from dateutil import rrule
from datetime import date

print(list(rrule.rrule(rrule.MONTHLY, dtstart=date(2013, 11, 1), until=date(2014, 2, 1))))
Run Code Online (Sandbox Code Playgroud)

会给你:

 [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)]
Run Code Online (Sandbox Code Playgroud)


小智 8

我的简单解决方案:

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))
Run Code Online (Sandbox Code Playgroud)

  • 尽管操作顺序不同,但它与已接受的答案完全相同。 (2认同)

yat*_*ani 8

from dateutil import relativedelta

r = relativedelta.relativedelta(date1, date2)

months_difference = (r.years * 12) + r.months
Run Code Online (Sandbox Code Playgroud)


Set*_*eth 6

将“月份”定义为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
Run Code Online (Sandbox Code Playgroud)

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

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


sro*_*uex 6

这个帖子钉了它!使用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
Run Code Online (Sandbox Code Playgroud)

  • 如果日期覆盖一年以上,则此方法不起作用,您需要添加`relativedelta.relativedelta(date2,date1).years * 12` (2认同)

H4k*_*kim 6

很多人已经给了你很好的答案来解决这个问题,但我还没有阅读任何使用列表理解的内容,所以我给你我用于类似用例的内容:

def compute_months(first_date: str, second_date: str) -> list:
    """List months between two dates given with the format YYYY-MM"""
    year_start = int(first_date[:4])
    month_start = int(first_date[5:7])

    year_end = int(second_date[:4])
    month_end = int(second_date[5:7])

    return [
        f"{year:0>4}-{month:0>2}"
        for year in range(year_start, year_end + 1)
        for month in range(
            month_start if year == year_start else 1,
            month_end + 1 if year == year_end else 13,
        )
    ]
Run Code Online (Sandbox Code Playgroud)
>>> 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']
Run Code Online (Sandbox Code Playgroud)


nop*_*ole 5

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

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

Apr 2018, May 2018, June 2018 
Run Code Online (Sandbox Code Playgroud)

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

Apr 2014, May 2014, ..., Dec 2014, Jan 2015, ..., Jan 2018, ..., June 2018
Run Code Online (Sandbox Code Playgroud)

所以这就是多年前我有以下伪代码的原因。它只是建议使用两个月作为终点并循环遍历它们,一次增加一个月。@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)
Run Code Online (Sandbox Code Playgroud)

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


con*_*xyz 5

以下是如何使用 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')
Run Code Online (Sandbox Code Playgroud)

请注意,它从给定开始日期之后的月份开始。