在 Python 中迭代一系列日期

2024-12-11 08:47:00
admin
原创
140
摘要:问题描述:我有以下代码可以做到这一点,但我怎样才能做得更好?现在我认为它比嵌套循环更好,但是当你在列表理解中有一个生成器时,它开始变得像 Perl-one-liner 一样。day_count = (end_date - start_date).days + 1 for single_date in [d f...

问题描述:

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

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_date和变量end_datedatetime.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

解决方案 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: date, end_date: date):
    days = int((end_date - start_date).days)
    for n in range(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()`

解决方案 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

解决方案 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)

解决方案 5:

这是我能想到的最人性化的解决方案。

import datetime

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

解决方案 6:

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,最重要的是,当您从多个地方调用该函数时,代码变得更容易阅读。

解决方案 7:

为什么不尝试一下:

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

解决方案 8:

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

解决方案 9:

为了完整起见,Pandas 还具有period_range针对超出范围的时间戳的函数:

import pandas as pd

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

解决方案 10:

显示从今天起的最后 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

解决方案 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:

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

import pandas as pd

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

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

解决方案 13:

for i in range(16):
    print datetime.date.today() + datetime.timedelta(days=i)

解决方案 14:

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

这是我的解决方案

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

解决方案 15:

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='
')

输出:

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

解决方案 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:

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

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:

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

解决方案 19:

您可以使用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物体通常更容易)

解决方案 20:

如果您要使用动态 timedelta,那么您可以使用:

1. 使用 while 循环

def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
    while start <= end:
        yield start
        start += delta

2. 使用 for 循环

from datetime import datetime, timedelta
from typing import Generator


def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
    delta_units = int((end - start) / delta)

    for _ in range(delta_units + 1):
        yield start
        start += delta

3. 如果你使用 async/await

async def datetime_range(start: datetime, end: datetime, delta: timedelta) -> AsyncGenerator[datetime, None]:
    delta_units = int((end - start) / delta)

    for _ in range(delta_units + 1):
        yield start
        start += delta

4. 列表推导

def datetime_range(start: datetime, end: datetime, delta: timedelta) -> List[datetime]:
    delta_units = int((end - start) / delta)
    return [start + (delta * index) for index in range(delta_units + 1)]

然后 1 和 2 的解决方案可以像这样使用

start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)

result = [time_part for time_part in datetime_range(start, end, delta)]
# or 
for time_part in datetime_range(start, end, delta):
    print(time_part)

第三种解决方案可以在异步上下文中像这样使用。因为它会重新运行一个异步生成器对象,该对象只能在异步上下文中使用

start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)

result = [time_part async for time_part in datetime_range(start, end, delta)]

async for time_part in datetime_range(start, end, delta):
    print(time_part)

关于解决方案的好处是它们都使用动态 timedelta。当您不知道将拥有哪个时间增量时,这非常有用。

解决方案 21:

那么对于按天增加的范围,以下内容如何:

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

解决方案 22:

该函数有一些额外的特性:

  • 可以传递与 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))

解决方案 23:

以下是通用日期范围函数的代码,类似于 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', \n            'microsecond' or 'millisecond'.")

解决方案 24:

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

解决方案 25:

在极地中也可以这样做,如果eager设置为True

start = datetime.date(year=2009, month=5, day=30)
end = datetime.date(year=2009, month=6, day=9)
dates = pl.date_range(start, end, eager=True)

for date in dates:
    print(date)

解决方案 26:

这是基于 leftjoin 解决方案的替代解决方案。

对于 3.0.0 以下的摆锤

def test_iteration():
    start = pendulum.from_format('2018-01', 'YYYY-MM')
    end = pendulum.from_format('2020-01', 'YYYY-MM')

    interval = pendulum.period(start, end)

    for dt in interval.range('months'):
        print(dt.format('YYYY-MM'))

对于 pendulum 3.0.0 及以上版本(它们已将周期重命名为间隔)
https://github.com/sdispater/pendulum/pull/676

def test_iteration():
    start = pendulum.from_format('2018-01', 'YYYY-MM')
    end = pendulum.from_format('2020-01', 'YYYY-MM')

    interval = pendulum.interval(start, end)

    for dt in interval.range('months'):
        print(dt.format('YYYY-MM'))

您可以从此处文档的列表中更改范围值:https ://pendulum.eustace.io/docs/#range

Supported units for range() are: years, months, weeks, days, hours, minutes, seconds and microseconds

解决方案 27:

range通过将参数存储在元组中来实现可逆步骤的略微不同的方法。

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)

解决方案 28:

Python API 调用循环的答案

使用datetime模块中的datetime和timedelta

from datetime import datetime, timedelta

import requests


headers = {"accept": "application/json", "x-api-key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}

delta = timedelta(days=30)
start = datetime(year=2022, month=1, day=1)
num_ranges = 10

for _ in range(num_ranges):
    start_str = start.strftime("%Y-%m-%d")
    end = start + delta
    end_str = end.strftime("%Y-%m-%d")
    url = f"https://apiendpoint.com/v2/dapps/xxxx/history/uaw?dateFrom={start_str}&dateTo={end_str}"
    response = requests.request("GET", url, headers=headers)
    # TODO: append response json to result
    start = end
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用