【问题标题】:How do I divide a date range into months in Python?如何在 Python 中将日期范围划分为月份?
【发布时间】:2018-07-11 20:20:27
【问题描述】:

我有以下日期范围:

begin: 2018-02-15
end: 2018-04-23

我想实现以下目标:

["2018-02-15 - 2018-02-28", "2018-03-01 - 2018-03-31", "2018-04-01 - 2018-04-23"]

本质上,我想将给定的日期范围划分为月份。我想不出在 Python 中实现这一点的方法。

我已经考虑了解决方案here,但是,这会根据指定的时间间隔拆分日期范围。我希望能够动态拆分日期范围。

因此,给定从 2018 年 2 月 15 日到 2018 年 4 月 23 日的日期范围,我希望能够获得该范围内的各个月份,如下所示:

  • 2018 年 2 月 15 日至 2018 年 2 月 28 日
  • 2018 年 3 月 1 日至 2018 年 3 月 31 日
  • 2018 年 4 月 1 日至 2018 年 4 月 23 日

【问题讨论】:

  • 你已经尝试了什么?它产生了什么?这与您所寻找的有什么不同?
  • @Harley 感谢您的建议。我已经查看了最佳答案,但我不确定如何使其适应我的需求。答案将日期范围拆分为指定的时间间隔。我不知道特定月份可能有多少天,因此,使用答案并以 30 的间隔进行拆分可能并不总是正确的
  • 有类似的需求。最终使用arrowarrow.Arrow.span_range('month', start, end) 成功了。

标签: python python-3.x datetime


【解决方案1】:

在一个循环中;从第一天开始,不断增加一天,直到您到达结束日期;每当月份更改时保存日期。

import datetime
begin = '2018-02-15'
end = '2018-04-23'

dt_start = datetime.datetime.strptime(begin, '%Y-%m-%d')
dt_end = datetime.datetime.strptime(end, '%Y-%m-%d')
one_day = datetime.timedelta(1)
start_dates = [dt_start]
end_dates = []
today = dt_start
while today <= dt_end:
    #print(today)
    tomorrow = today + one_day
    if tomorrow.month != today.month:
        start_dates.append(tomorrow)
        end_dates.append(today)
    today = tomorrow

end_dates.append(dt_end)


out_fmt = '%d %B %Y'
for start, end in zip(start_dates,end_dates):
    print('{} to {}'.format(start.strftime(out_fmt), end.strftime(out_fmt)))

结果:

>>>
15 February 2018 to 28 February 2018
01 March 2018 to 31 March 2018
01 April 2018 to 23 April 2018
>>>

您可能会想出一种方法来获取开始日期和结束日期之间的月份范围;为每个月份的第一天创建一个日期时间对象,存储它们以及它们之前的几天。不过,跨越年份的日期可能会有问题。

【讨论】:

  • 赞成提到跨年是一个问题!为此,我为我的函数添加了一个快速修复。
  • 我认为这对于开始和结束与一个月的第一天和最后一天重合的情况是不正确的?
  • @mcansado,我还没有测试过,但是您看到有什么错误?最后一个额外的开始日期?我写的时候没有想到那种情况。你认为它可以用一些额外的逻辑来解决吗? usr2564301 的答案更好吗?
  • 错误似乎是,当日期完全匹配一个月的开始和结束时,会添加另一对,其中开始日期是下个月的第一天,最后一天是上个月的最后一天。有一些方法可以肯定地解决它,我还没有为它编写代码,但只是想我会在这里为最终来到这里的人提到它:)
【解决方案2】:

要使用方便的日期对象,请始终使用标准模块datetime。这会包装您的字符串格式日期,并允许更轻松的计算以及定制的输出格式。

不幸的是,它似乎遗漏了一个重要信息:每个月的最后天,给定一年(这对于二月是必要的)。还有一个额外的模块calendar,它返回the last day for a month,但是因为这就是你所需要的,并且有一个简单的基于datetime的函数可以做同样的事情,所以我选择了后者。

这样,您可以设置任何begin 日期并将其连同该月的最后一天一起附加到您的列表中,然后将begin 设置为下一个 月的第一天并继续直到你通过end

警告/微调:我意识到如果 beginend 都在相同月份内,它将不起作用。这需要临时检查,所以我将最初的 while begin &lt; end 更改为 while True 并将结束日期的检查移到单独的行中。

另外,跨越一个需要再次单独测试,因为否则语句month+1将在12月失败。

import datetime

# borrowed from https://stackoverflow.com/a/13565185
# as noted there, the calendar module has a function of its own
def last_day_of_month(any_day):
    next_month = any_day.replace(day=28) + datetime.timedelta(days=4)  # this will never fail
    return next_month - datetime.timedelta(days=next_month.day)

begin = "2018-02-15"
end = "2018-04-23"

def monthlist(begin,end):
    begin = datetime.datetime.strptime(begin, "%Y-%m-%d")
    end = datetime.datetime.strptime(end, "%Y-%m-%d")

    result = []
    while True:
        if begin.month == 12:
            next_month = begin.replace(year=begin.year+1,month=1, day=1)
        else:
            next_month = begin.replace(month=begin.month+1, day=1)
        if next_month > end:
            break
        result.append ([begin.strftime("%Y-%m-%d"),last_day_of_month(begin).strftime("%Y-%m-%d")])
        begin = next_month
    result.append ([begin.strftime("%Y-%m-%d"),end.strftime("%Y-%m-%d")])
    return result


date_list = monthlist(begin,end)
print (date_list)

结果

[ ['2018-02-15', '2018-02-28'],
  ['2018-03-01', '2018-03-31'],
  ['2018-04-01', '2018-04-23'] ]

(为了便于阅读,略作格式化)

【讨论】:

  • 我喜欢这个,不知道为什么 OP 选择了我的。!
【解决方案3】:

如果你不介意使用pandas,有一个很好的助手date_range 可以实现你想要的:

import pandas as pd
start = pd.Timestamp('20180215')
end = pd.Timestamp('20180423')

parts = list(pd.date_range(start, end, freq='M')) 
# parts = [Timestamp('2018-02-28 00:00:00', freq='M'), Timestamp('2018-03-31 00:00:00', freq='M')]

if start != parts[0]:
  parts.insert(0, start)
if end != parts[-1]:
  parts.append(end)
parts[0] -= pd.Timedelta('1d')  # we add back one day later

pairs = zip(map(lambda d: d + pd.Timedelta('1d'), parts[:-1]), parts[1:])

pairs_str = list(map(lambda t: t[0].strftime('%Y-%m-%d') + ' - ' + t[1].strftime('%Y-%m-%d'), pairs))

# pairs_str = ['2018-02-15 - 2018-02-28', '2018-03-01 - 2018-03-31', '2018-04-01 - 2018-04-23']

【讨论】:

    【解决方案4】:

    使用python日历和会计年度变化

    import calendar
    from datetime import datetime
    begin = '2018-02-15'
    end= '2018-04-23'
    
    begin_year, begin_month, begin_date = [int(i) for i in begin.split("-")]
    end_year, end_month, end_date = [int(i) for i in end.split("-")]
    
    years = end_year - begin_year
    # if date range contains more than single year, we calculate total months
    if years:
        months = (12 - begin_month) + end_month + (12 * (years - 1))
    else:
        months = end_month - begin_month
    dates = []
    month = begin_month
    year = begin_year
    
    def create_datetime_object(y, m, d):
        return datetime.strptime('{}-{}-{}'.format(y, m, d), '%Y-%m-%d')
    # append the first date
    dates.append(create_datetime_object(begin_year, begin_month, begin_date))
    
    for i in range(months+1):
        days_in_month = calendar.monthrange(year, month)[-1]
        if month == begin_month and year == begin_year:
            dates.append(create_datetime_object(begin_year, begin_month, days_in_month))
        elif month == end_month and year == end_year:
            dates.append(create_datetime_object(end_year, end_month, 1))
        else:
            dates.append(create_datetime_object(year, month, 1))
            dates.append(create_datetime_object(year, month, days_in_month))
        if month == 12:
            month = 0
            year += 1
        month += 1
    # append the last date
    dates.append(create_datetime_object(end_year, end_month, end_date))
    

    要获得问题列表,我们可以执行以下操作 -

    dates = [datetime.strftime(dt, '%Y-%m-%d') for dt in dates]

    【讨论】:

      【解决方案5】:

      我不得不进行类似的操作并最终构建了这个函数。我在不同的用例(不同年份、同月...)上对其进行了测试,效果很好。

      这里的灵感来自 S.Lott 的回答 Creating a range of dates in Python

      import datetime
      
      def get_segments(start_date, end_date):
          """
          Divides input date range into associated months periods
      
          Example:
              Input: start_date = 2018-02-15
                     end_date   = 2018-04-23
              Output:
                  ["2018-02-15 - 2018-02-28", 
                   "2018-03-01 - 2018-03-31", 
                   "2018-04-01 - 2018-04-23"]
          """
          curr_date = start_date
          curr_month = start_date.strftime("%m")
          segments = []
      
          loop = (curr_date!=end_date) 
          days_increment = 1
      
          while loop:
              # Get incremented date with 1 day
              curr_date = start_date + datetime.timedelta(days=days_increment)
              # Get associated month
              prev_month = curr_month
              curr_month = curr_date.strftime("%m")
              # Add to segments if new month
              if prev_month!=curr_month:
                  # get start of segment
                  if not segments:
                      start_segment = start_date
                  else:
                      start_segment = segments[-1][1] + datetime.timedelta(days=1)
                  # get end of segment
                  end_segment = curr_date - datetime.timedelta(days=1)
                  # define and add segment
                  segment = [start_segment, end_segment]
                  segments.append(segment)
              # stop if last day reached
              loop = (curr_date!=end_date) 
              # increment added days
              days_increment += 1
      
          if not segments or segments[-1][1]!=end_date:
              if not segments:
                  start_last_segment = start_date
              else:
                  start_last_segment = segments[-1][1] + datetime.timedelta(days=1)
              last_segment = [start_last_segment, end_date]
              segments.append(last_segment)
      
          for i in range(len(segments)):
              segments[i][0] = segments[i][0].strftime("%Y-%m-%d")
              segments[i][1] = segments[i][1].strftime("%Y-%m-%d")
      
          return segments
      

      这是一个例子:

      start_date = datetime.datetime(2020, 5, 27)
      end_date = datetime.datetime(2021, 3, 1)
      
      segments = get_segments(start_date, end_date)
      
      for seg in segments:
          print(seg)
      

      输出:

      ['2020-05-27', '2020-05-31']
      ['2020-06-01', '2020-06-30']
      ['2020-07-01', '2020-07-31']
      ['2020-08-01', '2020-08-31']
      ['2020-09-01', '2020-09-30']
      ['2020-10-01', '2020-10-31']
      ['2020-11-01', '2020-11-30']
      ['2020-12-01', '2020-12-31']
      ['2021-01-01', '2021-01-31']
      ['2021-02-01', '2021-02-28']
      ['2021-03-01', '2021-03-01']
      

      【讨论】:

        【解决方案6】:

        我通过@wwii 扩展了解决方案

        现在您不会有重复的开始和/或结束日期

        def date_range_split_monthly(begin, end):
        
            dt_start = datetime.strptime(begin, '%Y-%m-%d')
            dt_end = datetime.strptime(end, '%Y-%m-%d')
            one_day = timedelta(1)
            start_dates = [dt_start]
            end_dates = []
            today = dt_start
            while today <= dt_end:
                # print(today)
                tomorrow = today + one_day
                if tomorrow.month != today.month:
                    if tomorrow <= dt_end:
                        start_dates.append(tomorrow)
                        end_dates.append(today)
                today = tomorrow
        
            end_dates.append(dt_end)
        
            return start_dates, end_dates
        

        【讨论】:

          【解决方案7】:

          对于使用Pendulum的人:

          import pendulum
          start = pendulum.now().subtract(months=6)
          end = pendulum.today()
          period = pendulum.period(start, end)
          
          time_ranges = list(period.range("months"))
          
          arr = []
          
          for index, dt in enumerate(time_ranges):
              if index < len(time_ranges) - 1:
                  start_range = time_ranges[index].format("YYYY-MM-D")
                  end_range = time_ranges[index + 1].format("YYYY-MM-D")
                  litt = F"{start_range} - {end_range}"
                  print(litt)
                  arr.append(litt)
          
          print(arr)
          

          More about period here

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-02-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-07-29
            相关资源
            最近更新 更多