Python'da çeşitli tarihler arasında yineleme


368

Bunu yapmak için aşağıdaki kod var, ama nasıl daha iyi yapabilirim? Şu anda iç içe döngülerden daha iyi olduğunu düşünüyorum, ancak liste kavrayışında bir jeneratör olduğunda Perl-one-linerish almaya başlar.

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

notlar

  • Aslında bunu yazdırmak için kullanmıyorum. Bu sadece demo amaçlıdır.
  • Zaman değişkenlerine ihtiyaç duymadığım için start_dateve end_datedeğişkenleri datetime.datenesnelerdir. (Rapor oluşturmak için kullanılacaklar).

Örnek Çıktı

Şunun başlangıç ​​tarihi 2009-05-30ve bitiş tarihi için 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
Sadece belirtmek gerekirse: 'time.strftime ("% Y-% m-% d", single_date.timetuple ())' ile daha kısa 'single_date.strftime ("% Y-%) arasında herhangi bir fark olduğunu düşünmüyorum. m% D ").' Cevapların çoğu daha uzun stili kopyalıyor gibi görünüyor.
Mu Zihin

8
Vay, bu cevaplar çok karmaşık. Bunu deneyin: stackoverflow.com/questions/7274267/…
Gringo Suave

@GringoSuave: Sean Cavanagh'ın cevabı hakkında karmaşık olan nedir?
jfs


1
Çoğaltın ya da etmeyin, diğer sayfada daha basit bir cevap alırsınız.
Gringo Suave

Yanıtlar:


553

Neden iki iç içe yineleme var? Benim için sadece tek bir yinelemeyle aynı veri listesini üretir:

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

Ve hiçbir liste depolanmaz, sadece bir jeneratör tekrarlanır. Ayrıca jeneratördeki "if" gereksiz görünüyor.

Sonuçta, doğrusal bir dizi iki değil, sadece bir yineleyici gerektirmelidir.

John Machin ile görüştükten sonra güncelleme:

Belki de en şık çözüm, tarih aralığını yinelemeyi tamamen gizlemek / soyutlamak için bir jeneratör işlevi kullanmaktır:

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

Not: Yerleşik ile tutarlı olması açısından range()işlevi bu yineleme durur önce ulaşan end_date. Kapsayıcı yineleme için ertesi gün, olduğu gibi kullanın range().


4
-1 ... day_count'un ön hesaplamasını yapmak ve range'i kullanmak, basit bir while döngüsü yeterli olduğunda harika değildir.
John Machin

7
@John Machin: Tamam. Bununla birlikte, bazı sayaç veya değerin açıkça artmasıyla döngüler yaparken yinelemeyi önlerim. Entegrasyon modeli, daha fazla pitoniktir (en azından kişisel görüşüme göre) ve aynı zamanda daha geneldir, çünkü bu yinelemenin nasıl yapıldığının ayrıntılarını gizlerken bir yinelemeyi ifade etmeyi sağlar.
Ber

10
@Ber: Hiç hoşuma gitmedi; ÇOK KADAR kötü. Zaten bir yineleme yaptınız! Şikâyet konusu yapıları bir jeneratöre sararak, daha fazla yürütme yükü eklediniz ve kullanıcının dikkatini 3-liner'in kodunu ve / veya dokümanlarını okumak için başka bir yere yönlendirdiniz. -2
John Machin

8
@John Machin: Kabul etmiyorum. Mesele, satır sayısını mutlak minimuma indirmekle ilgili değildir. Sonuçta, burada Perl hakkında konuşmuyoruz. Ayrıca, kodum sadece bir yineleme yapar (jeneratör bu şekilde çalışır, ancak sanırım bunu biliyorsunuz). *** Demek istediğim, yeniden kullanım ve kendi kendini açıklayan kod için kavramları soyutlamak. Bu mümkün olan en kısa kodu var çok daha değerli olduğunu korumak.
Ber

9
Terslik için gidiyorsanız bir jeneratör ifadesi kullanabilirsiniz:(start_date + datetime.timedelta(n) for n in range((end_date - start_date).days))
Mark Ransom

219

Bu daha açık olabilir:

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
Çok açık ve kısa, ancak devam etmek istiyorsanız iyi çalışmıyor
rslite

benim durumum için güzel çalışıyor
doomdaam

169

dateutilKütüphaneyi kullanın :

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

Bu python kütüphanesi, bazıları relative deltas gibi çok kullanışlı birçok gelişmiş özelliğe sahiptir ve bir projeye kolayca dahil edilebilen tek bir dosya (modül) olarak uygulanır.


3
Burada için döngü içinde nihai tarih olduğunu unutmayın dahil ait untilson tarih ise daterangeyöntemin BER cevabı olan seçkin bir end_date.
Ninjakannon


77

Pandalar genel olarak zaman serileri için mükemmeldir ve tarih aralıkları için doğrudan desteğe sahiptir.

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

Daha sonra tarihi yazdırmak için tarih aralığı üzerinde döngü yapabilirsiniz:

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

Ayrıca hayatı kolaylaştırmak için birçok seçeneğe sahiptir. Örneğin, yalnızca hafta içi günler istiyorsanız, sadece bdate_range ile değiştirebilirsiniz. Bkz. Http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps

Pandaların gücü, büyük miktarlarda veri üzerinde işlemleri çok hızlı ve kolay hale getiren vektörlenmiş işlemleri (numpy gibi) destekleyen veri çerçeveleridir.

EDIT: Ayrıca for döngüsünü tamamen atlayabilir ve doğrudan doğrudan yazdırabilirsiniz, bu da daha kolay ve daha verimli:

print(daterange)

"numpy gibi" - Pandalar numpy üzerine inşa edilmiştir: 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())

Bu fonksiyon negatif adımı destekleyerek sen kesinlikle gerektiren daha yapıyor, vb Sürece menzilli mantığı çarpanlarına olarak, o zaman ayrı gerekmez day_countve en önemlisi kod birden işlevini çağırın olarak okunması daha kolay olur yerler.


Aralık'ın parametrelerini daha yakından eşleştirmek için yeniden adlandırılan teşekkürler, vücutta değişmeyi unuttu.

+1 ... ancak adımın bir zaman çizelgesi olmasına izin verirken, (a) dateTIMErange () olarak adlandırmalı ve örneğin timedelta (saat = 12) ve timedelta (saat = 36) adımlarının düzgün çalışmasını veya ( b) ayrılmaz sayıda gün olmayan tuzak adımları veya (c) arayanı güçlükten kurtarın ve adımı zaman çizelgesi yerine birkaç gün olarak ifade edin.
John Machin

Herhangi bir timedelta zaten çalışmalıdır, ancak (a) nedeniyle bunu yazdıktan sonra kişisel not koleksiyonuma datetime_range ve date_range ekledim. Başka bir işlevin (c) için faydalı olduğundan emin değilim, en yaygın gün = 1 durumu zaten halledilir ve açık bir zaman çizelgesini geçmek zorunda kalmak karışıklığı önler. Belki bir yere yüklemek en iyisidir: bitbucket.org/kniht/scraps/src/tip/python/gen_range.py

bu çalışmayı günler dışındaki artışlarla yapmak için step.days yerine step.total_seconds () yöntemine karşı kontrol etmelisiniz
16:37

12

Bu, aklıma gelen en insan tarafından okunabilir çözüm.

import datetime

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

11

Neden denemiyorsunuz:

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 arangeişlevi tarihlere uygulanabilir:

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)

Kullanımı bir nesneler dizisine astypedönüştürmektir .numpy.datetime64datetime.datetime


Süper yalın yapı! Son satır benim için çalışıyordates = np.arange(d0, d1, dt).astype(datetime.datetime)
pyano

Saatlik / dakikası /… gibi sabit yuvarlak bir adım yerine herhangi bir zaman çizelgesine izin veren genel bir tek katmanlı çözüm göndermek için +1.
F.Raab

7

Bugünden itibaren son n günü göster:

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

Çıktı:

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

Lütfen yuvarlak parantez ekleyin, gibiprint((datetime.date.today() + datetime.timedelta(i)).isoformat())
TitanFighter

@TitanFighter lütfen düzenlemeleri yapmaktan çekinmeyin, onları kabul edeceğim.
user1767754

2
Denedim. Düzenleme için en az 6 karakter gerekir, ancak bu durumda yalnızca 2 karakter eklemek gerekir "(" ve ")"
TitanFighter

print((datetime.date.today() + datetime.timedelta(i))).isoformat () olmadan aynı çıktıyı verir. YYMMDD'yi yazdırmak için betiğime ihtiyacım var. Bunu nasıl yapacağını bilen var mı?
mr.zog

Bunu print deyimi yerine for döngüsünde yapınd = 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
for i in range(16):
    print datetime.date.today() + datetime.timedelta(days=i)

4

Tamlık için, Pandaların ayrıca period_rangesınırların dışındaki zaman damgaları için bir işlevi vardır:

import pandas as pd

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

3

Benzer bir sorunum var, ancak günlük yerine aylık olarak yinelemem gerekiyor.

Bu benim çözümüm

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

Örnek 1

date_start = datetime(2016, 6, 1)
date_end = datetime(2017, 1, 1)

for p in monthly_range(date_start, date_end):
    print(p)

Çıktı

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

Örnek 2

date_start = datetime(2017, 1, 1)
date_end = datetime(2016, 6, 1)

for p in monthly_range(date_start, date_end):
    print(p)

Çıktı

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

Can 't * Basit özyinelemeli fonksiyon düşündüren kimse olmadan bu soru 9 yıldır duruyordu inanıyoruz:

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)

Çıktı:

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

Düzenleme: * Şimdi buna inanabilirim - bkz. Python kuyruk özyinelemesini optimize ediyor mu? . Teşekkürler Tim .


3
Neden basit bir döngüyü özyineleme ile değiştiresiniz? Bu, kabaca iki buçuk yıldan daha uzun aralıklar için kırılır.
Tim-Erwin

@ Tim-Erwin Dürüst olmak gerekirse hiçbir fikrim yoktu CPython yorumunuzu değerli kuyruk özyineleme optimize etmez.
Pocketsand

2

Panda kütüphanesini basit ve güvenli bir şekilde kullanarak iki tarih arasında bir tarih serisi oluşturabilirsiniz

import pandas as pd

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

Frekleri D, M, Q, Y (günlük, aylık, üç aylık, yıllık) olarak ayarlayarak tarih oluşturma sıklığını değiştirebilirsiniz


2014 yılında bu konuya cevap
verdim

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

Bu fonksiyonun bazı ekstra özellikleri vardır:

  • başlangıç ​​veya bitiş için DATE_FORMAT ile eşleşen bir dize iletebilir ve bir tarih nesnesine dönüştürülür
  • başlangıç ​​veya bitiş için bir tarih nesnesi iletebilir
  • sonun başlangıçtan daha eski olması durumunda hata kontrolü

    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'in cevabına benzer, ancak daha esnek bir genel tarih aralığı işlevi için kod:

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

Günlere göre artırılmış bir aralık yapmak için aşağıdakilere ne dersiniz:

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

Genel bir sürüm için:

for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
  # Do stuff here
  • startTime ve stopTime datetime.date veya datetime.datetime nesnesidir (her ikisi de aynı tür olmalıdır)
  • stepTime bir timedelta nesnesidir

.Total_seconds () işlevinin yalnızca python 2.7'den sonra desteklendiğini unutmayın. Daha eski bir sürüme sahipseniz kendi işlevinizi yazabilirsiniz:

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

0

Argleri rangebir demet içinde saklayarak geri dönüşümlü adımlara biraz farklı bir yaklaşım .

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

ÇIKTI:

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'a hoş geldiniz! Bu kod soruyu çözebilir, ancak bunun sorunun nasıl ve neden çözüldüğüne ilişkin bir açıklama da dahil olmak üzere , özellikle çok iyi cevapları olan sorularda, yayınınızın kalitesini artırmaya ve muhtemelen daha fazla oylamaya neden olur. Sadece şimdi soran kişi için değil, gelecekte okuyucular için soruyu cevapladığınızı unutmayın. Lütfen açıklama eklemek için yanıtınızı düzenleyin ve hangi sınırlamaların ve varsayımların geçerli olduğunu belirtin. Yorumdan
double-beep
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.