【问题标题】:Iterating through a range of dates in Python在 Python 中遍历一系列日期
【发布时间】:2017-09-25 11:47:32
【问题描述】:

我有以下代码可以做到这一点,但我怎样才能做得更好?现在我认为它比嵌套循环更好,但是当你在列表理解中有一个生成器时,它开始变得 Perl-one-linerish。

day_count = (end_date - start_date).days + 1
for single_date in [d for d in (start_date + timedelta(n) for n in range(day_count)) if d <= end_date]:
    print strftime("%Y-%m-%d", single_date.timetuple())

注意事项

  • 我实际上并没有使用它来打印。这仅用于演示目的。
  • start_dateend_date 变量是datetime.date 对象,因为我不需要时间戳。 (它们将用于生成报告)。

样本输出

对于2009-05-30 的开始日期和2009-06-09 的结束日期:

2009-05-30
2009-05-31
2009-06-01
2009-06-02
2009-06-03
2009-06-04
2009-06-05
2009-06-06
2009-06-07
2009-06-08
2009-06-09

【问题讨论】:

标签: python date datetime iteration date-range


【解决方案1】:

为什么有两个嵌套迭代?对我来说,它只需要一次迭代就可以生成相同的数据列表:

for single_date in (start_date + timedelta(n) for n in range(day_count)):
    print ...

并且没有列表被存储,只有一个生成器被迭代。生成器中的“if”似乎也是不必要的。

毕竟,一个线性序列应该只需要一个迭代器,而不是两个。

与 John Machin 讨论后更新:

也许最优雅的解决方案是使用生成器函数来完全隐藏/抽象日期范围内的迭代:

from datetime import date, timedelta

def daterange(start_date, end_date):
    for n in range(int((end_date - start_date).days)):
        yield start_date + timedelta(n)

start_date = date(2013, 1, 1)
end_date = date(2015, 6, 2)
for single_date in daterange(start_date, end_date):
    print(single_date.strftime("%Y-%m-%d"))

注意:为了与内置的range() 函数保持一致,此迭代在到达end_date 之前停止。因此,对于包容性迭代,请使用第二天,就像使用 range() 一样。

【讨论】:

  • @John Machin:好的。然而,我确实阻止了对 while 循环的迭代,其中显式地增加了一些计数器或值。交互模式更 Pythonic(至少在我个人看来)也更通用,因为它允许表达迭代,同时隐藏迭代如何完成的细节。
  • @Ber:我一点也不喜欢;这是双重的糟糕。你已经有一个迭代!通过将抱怨的构造包装在生成器中,您增加了更多的执行开销,并将用户的注意力转移到其他地方来阅读您的 3-liner 的代码和/或文档。 -2
  • @John Machin:我不同意。重点不是将行数减少到绝对最小值。毕竟,我们在这里不是在谈论 Perl。此外,我的代码只进行一次迭代(这就是生成器的工作方式,但我想你知道这一点)。 *** 我的观点是关于为重用和自我解释代码抽象概念。我坚持认为这比拥有尽可能短的代码更有价值。
  • 如果你想要简洁,你可以使用生成器表达式:(start_date + datetime.timedelta(n) for n in range((end_date - start_date).days))
  • for n in range(int ((end_date - start_date).days+1)): 要包含 end_date
【解决方案2】:

这可能更清楚:

from datetime import date, timedelta

start_date = date(2019, 1, 1)
end_date = date(2020, 1, 1)
delta = timedelta(days=1)
while start_date <= end_date:
    print(start_date.strftime("%Y-%m-%d"))
    start_date += delta

【讨论】:

  • 很清楚很简短,但是如果你想用 continue 就不好用了
【解决方案3】:

使用dateutil 库:

from datetime import date
from dateutil.rrule import rrule, DAILY

a = date(2009, 5, 30)
b = date(2009, 6, 9)

for dt in rrule(DAILY, dtstart=a, until=b):
    print dt.strftime("%Y-%m-%d")

这个 python 库有许多更高级的特性,其中一些非常有用,比如relative deltas——并且被实现为单个文件(模块),可以很容易地包含在项目中。

【讨论】:

【解决方案4】:

Pandas 通常非常适合时间序列,并且直接支持日期范围。

import pandas as pd
daterange = pd.date_range(start_date, end_date)

然后您可以遍历日期范围以打印日期:

for single_date in daterange:
    print (single_date.strftime("%Y-%m-%d"))

它还有很多选项可以让生活更轻松。例如,如果您只想要工作日,您只需交换 bdate_range。见http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps

Pandas 的强大之处在于它的数据帧,它支持矢量化操作(很像 numpy),这使得对大量数据的操作变得非常快速和容易。

编辑: 您也可以完全跳过 for 循环,直接打印,这样更简单、更高效:

print(daterange)

【讨论】:

  • "much like numpy" - Pandas 是基于 numpy 构建的:P
  • 这很优雅!
【解决方案5】:
import datetime

def daterange(start, stop, step=datetime.timedelta(days=1), inclusive=False):
  # inclusive=False to behave like range by default
  if step.days > 0:
    while start < stop:
      yield start
      start = start + step
      # not +=! don't modify object passed in if it's mutable
      # since this function is not restricted to
      # only types from datetime module
  elif step.days < 0:
    while start > stop:
      yield start
      start = start + step
  if inclusive and start == stop:
    yield start

# ...

for date in daterange(start_date, end_date, inclusive=True):
  print strftime("%Y-%m-%d", date.timetuple())

通过支持负步骤等,此函数的功能超出了您的严格要求。只要您排除了范围逻辑,那么您就不需要单独的day_count,最重要的是,代码变得更容易阅读您从多个地方调用该函数。

【讨论】:

  • 谢谢,重命名为更接近范围的参数,忘记在正文中更改。
  • +1 ...但由于您允许该步骤为时间增量,您应该(a)将其称为 dateTIMErange() 并进行例如步骤timedelta(hours=12) 和 timedelta(hours=36) 正常工作或 (b) 捕获不是整数天的步骤或 (c) 为调用者省去麻烦并将步骤表示为天数而不是一个时间增量。
  • 任何 timedelta 都应该已经可以工作了,但是在写完这篇文章后,我确实将 datetime_range 和 date_range 添加到了我的个人剪贴簿中,因为 (a)。不确定另一个函数是否值得 (c),days=1 的最常见情况已经处理,并且必须通过显式 timedelta 避免混淆。也许最好把它上传到某个地方:bitbucket.org/kniht/scraps/src/tip/python/gen_range.py
  • 要使这项工作在除天数之外的增量上工作,您应该检查 step.total_seconds(),而不是 step.days
【解决方案6】:

Numpy 的arange 函数可以应用于日期:

import numpy as np
from datetime import datetime, timedelta
d0 = datetime(2009, 1,1)
d1 = datetime(2010, 1,1)
dt = timedelta(days = 1)
dates = np.arange(d0, d1, dt).astype(datetime)

astype 的用途是将numpy.datetime64 转换为datetime.datetime 对象的数组。

【讨论】:

  • 超级精益建设!最后一行对我有用dates = np.arange(d0, d1, dt).astype(datetime.datetime)
  • +1 用于发布允许任何时间增量的通用单线解决方案,而不是固定的舍入步骤,例如每小时/每分钟/…。
【解决方案7】:

这是我能想到的最易读的解决方案。

import datetime

def daterange(start, end, step=datetime.timedelta(1)):
    curr = start
    while curr < end:
        yield curr
        curr += step

【讨论】:

    【解决方案8】:

    为什么不试试:

    import datetime as dt
    
    start_date = dt.datetime(2012, 12,1)
    end_date = dt.datetime(2012, 12,5)
    
    total_days = (end_date - start_date).days + 1 #inclusive 5 days
    
    for day_number in range(total_days):
        current_date = (start_date + dt.timedelta(days = day_number)).date()
        print current_date
    

    【讨论】:

      【解决方案9】:

      显示从今天开始的最后 n 天:

      import datetime
      for i in range(0, 100):
          print((datetime.date.today() + datetime.timedelta(i)).isoformat())
      

      输出:

      2016-06-29
      2016-06-30
      2016-07-01
      2016-07-02
      2016-07-03
      2016-07-04
      

      【讨论】:

      • 请加圆括号,如print((datetime.date.today() + datetime.timedelta(i)).isoformat())
      • @TitanFighter 请随意编辑,我会接受的。
      • 我试过了。编辑至少需要 6 个字符,但在这种情况下,只需添加 2 个字符,“(”和“)”
      • print((datetime.date.today() + datetime.timedelta(i))) 没有 .isoformat() 给出完全相同的输出。我需要我的脚本来打印 YYMMDD。有人知道怎么做吗?
      • 只需在for循环中执行此操作,而不是在打印语句d = datetime.date.today() + datetime.timedelta(i); d.strftime("%Y%m%d")
      【解决方案10】:

      为了完整起见,Pandas 还有一个period_range 函数用于处理越界的时间戳:

      import pandas as pd
      
      pd.period_range(start='1/1/1626', end='1/08/1627', freq='D')
      

      【讨论】:

        【解决方案11】:
        import datetime
        
        def daterange(start, stop, step_days=1):
            current = start
            step = datetime.timedelta(step_days)
            if step_days > 0:
                while current < stop:
                    yield current
                    current += step
            elif step_days < 0:
                while current > stop:
                    yield current
                    current += step
            else:
                raise ValueError("daterange() step_days argument must not be zero")
        
        if __name__ == "__main__":
            from pprint import pprint as pp
            lo = datetime.date(2008, 12, 27)
            hi = datetime.date(2009, 1, 5)
            pp(list(daterange(lo, hi)))
            pp(list(daterange(hi, lo, -1)))
            pp(list(daterange(lo, hi, 7)))
            pp(list(daterange(hi, lo, -7))) 
            assert not list(daterange(lo, hi, -1))
            assert not list(daterange(hi, lo))
            assert not list(daterange(lo, hi, -7))
            assert not list(daterange(hi, lo, 7)) 
        

        【讨论】:

          【解决方案12】:
          for i in range(16):
              print datetime.date.today() + datetime.timedelta(days=i)
          

          【讨论】:

            【解决方案13】:

            我有类似的问题,但我需要每月而不是每天迭代。

            这是我的解决方案

            import calendar
            from datetime import datetime, timedelta
            
            def days_in_month(dt):
                return calendar.monthrange(dt.year, dt.month)[1]
            
            def monthly_range(dt_start, dt_end):
                forward = dt_end >= dt_start
                finish = False
                dt = dt_start
            
                while not finish:
                    yield dt.date()
                    if forward:
                        days = days_in_month(dt)
                        dt = dt + timedelta(days=days)            
                        finish = dt > dt_end
                    else:
                        _tmp_dt = dt.replace(day=1) - timedelta(days=1)
                        dt = (_tmp_dt.replace(day=dt.day))
                        finish = dt < dt_end
            

            示例 #1

            date_start = datetime(2016, 6, 1)
            date_end = datetime(2017, 1, 1)
            
            for p in monthly_range(date_start, date_end):
                print(p)
            

            输出

            2016-06-01
            2016-07-01
            2016-08-01
            2016-09-01
            2016-10-01
            2016-11-01
            2016-12-01
            2017-01-01
            

            示例 #2

            date_start = datetime(2017, 1, 1)
            date_end = datetime(2016, 6, 1)
            
            for p in monthly_range(date_start, date_end):
                print(p)
            

            输出

            2017-01-01
            2016-12-01
            2016-11-01
            2016-10-01
            2016-09-01
            2016-08-01
            2016-07-01
            2016-06-01
            

            【讨论】:

              【解决方案14】:

              您可以使用 pandas 库简单而可靠地生成两个日期之间的一系列日期

              import pandas as pd
              
              print pd.date_range(start='1/1/2010', end='1/08/2018', freq='M')
              

              您可以通过将freq设置为D、M、Q、Y来更改生成日期的频率 (每天、每月、每季度、每年 )

              【讨论】:

                【解决方案15】:
                > pip install DateTimeRange
                
                from datetimerange import DateTimeRange
                
                def dateRange(start, end, step):
                        rangeList = []
                        time_range = DateTimeRange(start, end)
                        for value in time_range.range(datetime.timedelta(days=step)):
                            rangeList.append(value.strftime('%m/%d/%Y'))
                        return rangeList
                
                    dateRange("2018-09-07", "2018-12-25", 7)  
                
                    Out[92]: 
                    ['09/07/2018',
                     '09/14/2018',
                     '09/21/2018',
                     '09/28/2018',
                     '10/05/2018',
                     '10/12/2018',
                     '10/19/2018',
                     '10/26/2018',
                     '11/02/2018',
                     '11/09/2018',
                     '11/16/2018',
                     '11/23/2018',
                     '11/30/2018',
                     '12/07/2018',
                     '12/14/2018',
                     '12/21/2018']
                

                【讨论】:

                  【解决方案16】:

                  使用 pendulum.period:

                  import pendulum
                  
                  start = pendulum.from_format('2020-05-01', 'YYYY-MM-DD', formatter='alternative')
                  end = pendulum.from_format('2020-05-02', 'YYYY-MM-DD', formatter='alternative')
                  
                  period = pendulum.period(start, end)
                  
                  for dt in period:
                      print(dt.to_date_string())
                  

                  【讨论】:

                    【解决方案17】:

                    对于那些对 Pythonic 函数方式感兴趣的人:

                    from datetime import date, timedelta
                    from itertools import count, takewhile
                    
                    for d in takewhile(lambda x: x<=date(2009,6,9), map(lambda x:date(2009,5,30)+timedelta(days=x), count())):
                        print(d)
                    

                    【讨论】:

                      【解决方案18】:

                      对于按天递增的范围,以下情况如何:

                      for d in map( lambda x: startDate+datetime.timedelta(days=x), xrange( (stopDate-startDate).days ) ):
                        # Do stuff here
                      
                      • startDate 和 stopDate 是 datetime.date 对象

                      对于通用版本:

                      for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
                        # Do stuff here
                      
                      • startTime 和 stopTime 是 datetime.date 或 datetime.datetime 对象 (两者应该是同一类型)
                      • stepTime 是一个 timedelta 对象

                      请注意,.total_seconds() 仅在 python 2.7 之后才支持如果您坚持使用早期版本,您可以编写自己的函数:

                      def total_seconds( td ):
                        return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
                      

                      【讨论】:

                        【解决方案19】:

                        这个函数有一些额外的功能:

                        • 可以传递与 DATE_FORMAT 匹配的字符串作为开始或结束,并将其转换为日期对象
                        • 可以传递一个日期对象作为开始或结束
                        • 错误检查,以防结束比开始更早

                          import datetime
                          from datetime import timedelta
                          
                          
                          DATE_FORMAT = '%Y/%m/%d'
                          
                          def daterange(start, end):
                                def convert(date):
                                      try:
                                            date = datetime.datetime.strptime(date, DATE_FORMAT)
                                            return date.date()
                                      except TypeError:
                                            return date
                          
                                def get_date(n):
                                      return datetime.datetime.strftime(convert(start) + timedelta(days=n), DATE_FORMAT)
                          
                                days = (convert(end) - convert(start)).days
                                if days <= 0:
                                      raise ValueError('The start date must be before the end date.')
                                for n in range(0, days):
                                      yield get_date(n)
                          
                          
                          start = '2014/12/1'
                          end = '2014/12/31'
                          print list(daterange(start, end))
                          
                          start_ = datetime.date.today()
                          end = '2015/12/1'
                          print list(daterange(start, end))
                          

                        【讨论】:

                          【解决方案20】:

                          这是通用日期范围函数的代码,类似于 Ber 的答案,但更灵活:

                          def count_timedelta(delta, step, seconds_in_interval):
                              """Helper function for iterate.  Finds the number of intervals in the timedelta."""
                              return int(delta.total_seconds() / (seconds_in_interval * step))
                          
                          
                          def range_dt(start, end, step=1, interval='day'):
                              """Iterate over datetimes or dates, similar to builtin range."""
                              intervals = functools.partial(count_timedelta, (end - start), step)
                          
                              if interval == 'week':
                                  for i in range(intervals(3600 * 24 * 7)):
                                      yield start + datetime.timedelta(weeks=i) * step
                          
                              elif interval == 'day':
                                  for i in range(intervals(3600 * 24)):
                                      yield start + datetime.timedelta(days=i) * step
                          
                              elif interval == 'hour':
                                  for i in range(intervals(3600)):
                                      yield start + datetime.timedelta(hours=i) * step
                          
                              elif interval == 'minute':
                                  for i in range(intervals(60)):
                                      yield start + datetime.timedelta(minutes=i) * step
                          
                              elif interval == 'second':
                                  for i in range(intervals(1)):
                                      yield start + datetime.timedelta(seconds=i) * step
                          
                              elif interval == 'millisecond':
                                  for i in range(intervals(1 / 1000)):
                                      yield start + datetime.timedelta(milliseconds=i) * step
                          
                              elif interval == 'microsecond':
                                  for i in range(intervals(1e-6)):
                                      yield start + datetime.timedelta(microseconds=i) * step
                          
                              else:
                                  raise AttributeError("Interval must be 'week', 'day', 'hour' 'second', \
                                      'microsecond' or 'millisecond'.")
                          

                          【讨论】:

                            【解决方案21】:
                            from datetime import date,timedelta
                            delta = timedelta(days=1)
                            start = date(2020,1,1)
                            end=date(2020,9,1)
                            loop_date = start
                            while loop_date<=end:
                                print(loop_date)
                                loop_date+=delta
                            

                            【讨论】:

                              【解决方案22】:

                              你可以使用Arrow:

                              这是来自文档的示例,迭代数小时:

                              from arrow import Arrow
                              
                              >>> start = datetime(2013, 5, 5, 12, 30)
                              >>> end = datetime(2013, 5, 5, 17, 15)
                              >>> for r in Arrow.range('hour', start, end):
                              ...     print repr(r)
                              ...
                              <Arrow [2013-05-05T12:30:00+00:00]>
                              <Arrow [2013-05-05T13:30:00+00:00]>
                              <Arrow [2013-05-05T14:30:00+00:00]>
                              <Arrow [2013-05-05T15:30:00+00:00]>
                              <Arrow [2013-05-05T16:30:00+00:00]>
                              

                              要迭代几天,您可以这样使用:

                              >>> start = Arrow(2013, 5, 5)
                              >>> end = Arrow(2013, 5, 5)
                              >>> for r in Arrow.range('day', start, end):
                              ...     print repr(r)
                              

                              (没有检查是否可以传递datetime.date 对象,但无论如何Arrow 对象通常更容易)

                              【讨论】:

                                【解决方案23】:

                                通过将range args 存储在元组中,实现可逆步骤的方法略有不同。

                                def date_range(start, stop, step=1, inclusive=False):
                                    day_count = (stop - start).days
                                    if inclusive:
                                        day_count += 1
                                
                                    if step > 0:
                                        range_args = (0, day_count, step)
                                    elif step < 0:
                                        range_args = (day_count - 1, -1, step)
                                    else:
                                        raise ValueError("date_range(): step arg must be non-zero")
                                
                                    for i in range(*range_args):
                                        yield start + timedelta(days=i)
                                

                                【讨论】:

                                  【解决方案24】:
                                  import datetime
                                  from dateutil.rrule import DAILY,rrule
                                  
                                  date=datetime.datetime(2019,1,10)
                                  
                                  date1=datetime.datetime(2019,2,2)
                                  
                                  for i in rrule(DAILY , dtstart=date,until=date1):
                                       print(i.strftime('%Y%b%d'),sep='\n')
                                  

                                  输出:

                                  2019Jan10
                                  2019Jan11
                                  2019Jan12
                                  2019Jan13
                                  2019Jan14
                                  2019Jan15
                                  2019Jan16
                                  2019Jan17
                                  2019Jan18
                                  2019Jan19
                                  2019Jan20
                                  2019Jan21
                                  2019Jan22
                                  2019Jan23
                                  2019Jan24
                                  2019Jan25
                                  2019Jan26
                                  2019Jan27
                                  2019Jan28
                                  2019Jan29
                                  2019Jan30
                                  2019Jan31
                                  2019Feb01
                                  2019Feb02
                                  

                                  【讨论】:

                                  • 欢迎来到 Stack Overflow!虽然这段代码可能会解决问题,但including an explanation 说明如何以及为什么会解决问题,特别是对于有太多好答案的问题,确实有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请edit您的答案以添加解释并说明适用的限制和假设。 From Review
                                  猜你喜欢
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2022-06-28
                                  • 2014-10-04
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2020-08-20
                                  相关资源
                                  最近更新 更多