Pythonで日付の範囲を反復する


369

これを行うための次のコードがありますが、どうすればより適切に実行できますか?現時点では、ネストされたループよりも優れていると思いますが、リスト内包にジェネレーターがあると、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_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

3
指摘しておくだけ: 'time.strftime( "%Y-%m-%d"、single_date.timetuple())'と短い 'single_date.strftime( "%Y-%)の間に違いはないと思いますm-%d ") '。ほとんどの答えは、長いスタイルをコピーしているようです。
ムーマインド

8
うわー、これらの答えはあまりにも複雑です。これを試してください:stackoverflow.com/questions/7274267/...
グリンゴサーブ

@GringoSuave:Sean Cavanaghの回答の複雑さは何ですか?
jfs 2015


1
重複しているかどうかに関係なく、別のページでより簡単な回答が得られます。
Gringo Suave

回答:


553

なぜ2つのネストされた反復があるのですか?私にとっては、同じデータリストが1回の反復で生成されます。

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

また、リストは保存されず、反復されるジェネレータは1つだけです。また、ジェネレータの「if」は不要のようです。

結局のところ、線形シーケンスに必要なイテレータは2つではなく1つだけです。

John Machinと話し合った後の更新:

おそらく最もエレガントな解決策は、ジェネレータ関数を使用して、日付の範囲全体で反復を完全に非表示/抽象化することです:

from datetime import timedelta, date

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


4
-1 ...単純なwhileループで十分な場合、day_countの予備計算と範囲の使用は素晴らしいものではありません。
John Machin、

7
@ジョン・マチン:わかりました。ただし、いくつかのカウンターまたは値の明示的なインクリメントを使用して、whileループよりも反復を事前に行います。反復パターンは(少なくとも私の個人的な見方では)よりパイソン的であり、反復がどのように行われるかの詳細を隠しながら反復を表現できるため、より一般的です。
Ber

10
@Ber:私はまったく好きではありません。それは二重に悪いです。あなたはすでにイテレーションを持っていました!不満のある構成をジェネレーターでラップすることにより、実行オーバーヘッドがさらに追加され、ユーザーの注意がどこか他の場所に転送されて、3ライナーのコードやドキュメントが読み取られます。-2
ジョンマチン

8
@ジョン・マチン:同意しない。ポイントは、ライン数を絶対最小数に減らすことではありません。結局のところ、ここではPerlについて話しているのではありません。また、私のコードは1回の反復のみを実行します(これがジェネレーターの動作方法ですが、あなたはそれを知っていると思います)。***私のポイントは、再利用のための概念を抽象化し、自己説明的なコードについてです。これは、可能な限り短いコードよりもはるかに価値があると私は主張します。
Ber

9
あなたは簡潔さのためつもりなら、あなたはジェネレータ式を使用することができます(start_date + datetime.timedelta(n) for n in range((end_date - start_date).days))
マーク身代金

219

これはもっと明確かもしれません:

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
非常に明確で短いが、継続を使用する場合はうまく機能しない
rslite

私の使用例では
うまく機能してい

169

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 などの非常に便利な高度な機能が多数あり、プロジェクトに簡単に含めることができる単一のファイル(モジュール)として実装されています。


3
ここのためのループの最後の日であることに注意してください込みuntil最終的な日付一方daterangeで法のBERの答えがある排他的なend_date
Ninjakannon、2015年

より最新のドキュメントdateutil.readthedocs.io/en/stable/rrule.html
qwr

77

パンダは一般的に時系列に最適で、日付範囲を直接サポートしています。

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をご覧ください

パンダの威力は、実際にはそのデータフレームであり、大量のデータにわたる操作を非常に高速かつ簡単にするベクトル化された操作(numpyのようなもの)をサポートしています。

編集:forループを完全にスキップして直接印刷することもできますが、これはより簡単で効率的です。

print(daterange)

"numpy like numpy"-Pandasはnumpy:Pに基づいて構築されています
Zach Saucier

15
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ではなく日数として表現します。
John Machin

任意のtimedeltaは既に動作するはずですが、(a)のため、これを書いた後、個人のスクラップコレクションにdatetime_rangeとdate_rangeを追加しました。(c)に別の関数が価値があるかどうかは不明です。最も一般的なdays = 1のケースはすでに対処されており、明示的なtimedeltaを渡すことで混乱を回避できます。多分それをどこかにアップロードするのが最善です:bitbucket.org/kniht/scraps/src/tip/python/gen_range.py

これを日数以外の増分で機能させるには、step.daysではなくstep.total_seconds()に対してチェックする必要があります
amohr

12

これは私が考えることができる最も人間が読めるソリューションです。

import datetime

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

11

試してみませんか:

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

7

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

毎時/毎分/…などの固定された丸められたステップの代わりに、任意のtimedeltaを許可する一般的なワンライナーソリューションを投稿するための+1。
F.Raab

7

今日から過去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 2018年

@TitanFighterは自由に編集してください、私はそれらを受け入れます。
user1767754

2
私は試した。編集には最低6文字が必要ですが、この場合は「(」と「)」の2文字だけを追加する必要があります
TitanFighter

print((datetime.date.today() + datetime.timedelta(i))).isoformat()がない場合は、まったく同じ出力が得られます。YYMMDDを印刷するにはスクリプトが必要です。誰でもそれを行う方法を知っていますか?
mr.zog

これを、printステートメントの代わりにforループで実行してくださいd = datetime.date.today() + datetime.timedelta(i); d.strftime("%Y%m%d")
user1767754

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


4

完全を期すため、Pandasにperiod_rangeは範囲外のタイムスタンプの機能もあります。

import pandas as pd

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

3

同様の問題がありますが、毎日ではなく毎月繰り返す必要があります。

これは私の解決策です

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

3

することができます「tは *簡単な再帰関数を示唆誰もせずに、この質問は、9年前から存在していると考えています:

from datetime import datetime, timedelta

def walk_days(start_date, end_date):
    if start_date <= end_date:
        print(start_date.strftime("%Y-%m-%d"))
        next_date = start_date + timedelta(days=1)
        walk_days(next_date, end_date)

#demo
start_date = datetime(2009, 5, 30)
end_date   = datetime(2009, 6, 9)

walk_days(start_date, end_date)

出力:

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は末尾再帰を最適化しますか?ティムありがとう。


3
単純なループを再帰で置き換えるのはなぜですか?これは、約2年半より長い範囲では機能しません。
Tim-Erwin

@ Tim-Erwin正直に言って、CPythonが末尾再帰を最適化しないとは思わなかったので、あなたのコメントは貴重です。
Pocketsand 2018

2

あなたはパンダライブラリを使用して2つの日付の間の一連の日付を簡単かつ信頼して生成できます

import pandas as pd

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

日付を生成する頻度は、freqをD、M、Q、Y(日次、月次、四半期、年次)に設定することで変更できます。


2014年にこのスレッドで既に回答済み
Alexey Vazhnov

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

1

この関数には、いくつかの追加機能があります。

  • 開始または終了の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))

1

以下は、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'.")

0

日によって増分される範囲を実行するための次のことについてはどうでしょうか。

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

0

rangeargsをタプルに格納することによる、可逆ステップへのわずかに異なるアプローチ。

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)

0
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へようこそ!このコードは問題を解決する可能性がありますが、特にこれが問題を解決する方法と理由の説明含め、特に良い回答が多すぎる質問では、投稿の品質を向上させるのに役立ち、おそらくより多くの賛成票が得られます。あなたが今尋ねている人だけでなく、あなたが将来の読者のための質問に答えていることを忘れないでください。回答を編集して説明を追加し、適用される制限と前提を示してください。口コミから
ダブルビープ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.