Durchlaufen einer Reihe von Daten in Python

368

Ich habe den folgenden Code, um dies zu tun, aber wie kann ich es besser machen? Im Moment denke ich, dass es besser ist als verschachtelte Schleifen, aber es fängt an, Perl-One-Linerish zu werden, wenn Sie einen Generator in einem Listenverständnis haben.

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

Anmerkungen

  • Ich benutze dies nicht zum Drucken. Das ist nur für Demozwecke.
  • Die Variablen start_dateund end_datesind datetime.dateObjekte, da ich die Zeitstempel nicht benötige. (Sie werden verwendet, um einen Bericht zu erstellen).

Beispielausgabe

Für ein Startdatum 2009-05-30und ein Enddatum von 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
ShawnMilo
quelle
3
Nur um darauf hinzuweisen: Ich glaube, es gibt keinen Unterschied zwischen 'time.strftime ("% Y-% m-% d", single_date.timetuple ())' und der kürzeren 'single_date.strftime ("% Y-%"). m-% d ") '. Die meisten Antworten scheinen den längeren Stil zu kopieren.
Mu Mind
8
Wow, diese Antworten sind viel zu kompliziert. Versuchen Sie dies: stackoverflow.com/questions/7274267/…
Gringo Suave
@GringoSuave: Was ist kompliziert an Sean Cavanaghs Antwort ?
JFS
Anwendung: Cheat auf GitHub-Streifen: stackoverflow.com/questions/20099235/…
Ciro Santilli 法轮功 冠状 病. 事件 法轮功
1
Doppelt oder nicht, auf der anderen Seite erhalten Sie eine einfachere Antwort.
Gringo Suave

Antworten:

553

Warum gibt es zwei verschachtelte Iterationen? Für mich erzeugt es die gleiche Datenliste mit nur einer Iteration:

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

Und es wird keine Liste gespeichert, nur ein Generator wird durchlaufen. Auch das "Wenn" im Generator scheint unnötig zu sein.

Schließlich sollte eine lineare Sequenz nur einen Iterator erfordern, nicht zwei.

Update nach Diskussion mit John Machin:

Die vielleicht eleganteste Lösung ist die Verwendung einer Generatorfunktion, um die Iteration über den Datumsbereich hinweg vollständig auszublenden / zu abstrahieren:

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

NB: Aus Gründen der Konsistenz mit der integrierten range()Funktion stoppt diese Iteration, bevor die erreicht wird end_date. Verwenden Sie für eine inklusive Iteration den nächsten Tag wie bei range().

Ber
quelle
4
-1 ... eine vorläufige Berechnung von day_count und die Verwendung von range ist nicht fantastisch, wenn eine einfache while-Schleife ausreicht.
John Machin
7
@ John Machin: Okay. Ich bevorzuge jedoch eine Iteration über while-Schleifen mit expliziter Inkrementierung eines Zählers oder Werts. Das Interationsmuster ist pythonischer (zumindest aus meiner persönlichen Sicht) und auch allgemeiner, da es das Ausdrücken einer Iteration ermöglicht, während die Details dieser Iteration ausgeblendet werden.
Ber
10
@Ber: Ich mag es überhaupt nicht; Es ist doppelt schlecht. Sie hatten bereits eine Iteration! Indem Sie die beanstandeten Konstrukte in einen Generator einbinden, haben Sie noch mehr Ausführungsaufwand hinzugefügt und die Aufmerksamkeit des Benutzers auf einen anderen Ort gelenkt, um den Code und / oder die Dokumente Ihres 3-Liners zu lesen. -2
John Machin
8
@ John Machin: Ich bin anderer Meinung. Es geht nicht darum, die Anzahl der Zeilen auf das absolute Minimum zu reduzieren. Schließlich reden wir hier nicht über Perl. Außerdem führt mein Code nur eine Iteration durch (so funktioniert der Generator, aber das wissen Sie wohl). *** Mein Punkt ist die Zusammenfassung von Konzepten für die Wiederverwendung und selbsterklärenden Code. Ich behaupte, dass dies weitaus lohnender ist, als den kürzestmöglichen Code zu haben.
Ber
9
Wenn Sie knapp werden möchten, können Sie einen Generatorausdruck verwenden:(start_date + datetime.timedelta(n) for n in range((end_date - start_date).days))
Mark Ransom
219

Dies könnte klarer sein:

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
Sean Cavanagh
quelle
3
Sehr klar und kurz, funktioniert aber nicht gut, wenn Sie continue
rslite
funktioniert
wunderbar
169

Verwenden Sie die dateutilBibliothek:

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

Diese Python-Bibliothek verfügt über viele erweiterte Funktionen, von denen einige sehr nützlich sind, wie z. B. relative deltas. Sie wird als einzelne Datei (Modul) implementiert, die problemlos in ein Projekt aufgenommen werden kann.

nosklo
quelle
3
Beachten Sie, dass der letzte Tag hier in der for - Schleife ist inklusive der untilErwägung , dass der letzte Tag des daterangeVerfahrens in dem Ber Antwort ist exklusiv von end_date.
Ninjakannon
77

Pandas eignet sich hervorragend für Zeitreihen im Allgemeinen und bietet direkte Unterstützung für Datumsbereiche.

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

Sie können dann den Datenbereich durchlaufen, um das Datum auszudrucken:

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

Es hat auch viele Möglichkeiten, um das Leben leichter zu machen. Wenn Sie beispielsweise nur Wochentage möchten, tauschen Sie einfach bdate_range aus. Siehe http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps

Die Stärke von Pandas liegt in seinen Datenrahmen, die vektorisierte Operationen (ähnlich wie Numpy) unterstützen, die Operationen über große Datenmengen sehr schnell und einfach machen.

BEARBEITEN: Sie können die for-Schleife auch vollständig überspringen und direkt drucken, was einfacher und effizienter ist:

print(daterange)
fantastisch
quelle
"Ähnlich wie Numpy" - Pandas basiert auf 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())

Diese Funktion leistet mehr, als Sie unbedingt benötigen, indem sie einen negativen Schritt usw. unterstützt. Solange Sie Ihre Bereichslogik herausrechnen, benötigen Sie keine separate day_countund vor allem ist der Code leichter lesbar, wenn Sie die Funktion von mehreren aufrufen setzt.


quelle
Dank, umbenannt, um den Parametern des Bereichs besser zu entsprechen, habe ich vergessen, mich im Körper zu ändern.
+1 ... aber da Sie zulassen, dass der Schritt ein Zeitdelta ist, sollten Sie entweder (a) ihn dateTIMErange () nennen und Schritte wie z. B. Zeitdelta (Stunden = 12) und Zeitdelta (Stunden = 36) ordnungsgemäß ausführen oder b) Schritte abfangen, die keine ganzzahlige Anzahl von Tagen sind, oder (c) dem Anrufer den Ärger ersparen und den Schritt als Anzahl von Tagen anstelle eines Zeitdeltas ausdrücken.
John Machin
Jedes Timedelta sollte bereits funktionieren, aber ich habe datetime_range und date_range zu meiner persönlichen Schrottsammlung hinzugefügt, nachdem ich dies geschrieben habe, aufgrund von (a). Nicht sicher, ob sich eine andere Funktion für (c) lohnt, der häufigste Fall von Tagen = 1 ist bereits erledigt, und das Übergeben eines expliziten Zeitdeltas vermeidet Verwirrung. Vielleicht ist es am besten, es irgendwo hochzuladen: bitbucket.org/kniht/scraps/src/tip/python/gen_range.py
Damit dies in anderen Schritten als Tagen funktioniert, sollten Sie mit step.total_seconds () und nicht mit step.days
amohr
12

Dies ist die am besten lesbare Lösung, die ich mir vorstellen kann.

import datetime

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

Warum nicht versuchen:

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
John
quelle
7

Die arangeFunktion von Numpy kann auf Daten angewendet werden:

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)

Die Verwendung von astypeist das Konvertieren von numpy.datetime64in ein Array von datetime.datetimeObjekten.

Tor
quelle
Super schlanke Konstruktion! Die letzte Zeile funktioniert für mich mitdates = np.arange(d0, d1, dt).astype(datetime.datetime)
Pyano
+1 für die Veröffentlichung einer generischen Einzeilerlösung, die ein beliebiges Zeitdelta anstelle eines festen gerundeten Schritts wie stündlich / minutiös /… zulässt.
F. Raab
7

Zeigen Sie die letzten n Tage von heute:

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

Ausgabe:

2016-06-29
2016-06-30
2016-07-01
2016-07-02
2016-07-03
2016-07-04
user1767754
quelle
Bitte fügen Sie runde Klammern hinzu, wieprint((datetime.date.today() + datetime.timedelta(i)).isoformat())
TitanFighter
@TitanFighter Bitte zögern Sie nicht, Änderungen vorzunehmen, ich werde sie akzeptieren.
user1767754
2
Ich habe es versucht. Für die Bearbeitung sind mindestens 6 Zeichen erforderlich. In diesem Fall müssen jedoch nur 2 Zeichen "(" und ")"
hinzugefügt werden
print((datetime.date.today() + datetime.timedelta(i)))ohne .isoformat () gibt es genau die gleiche Ausgabe. Ich brauche mein Skript, um YYMMDD zu drucken. Weiß jemand, wie man das macht?
mr.zog
Tun Sie dies einfach in der for-Schleife anstelle der print-Anweisungd = 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)) 
John Machin
quelle
4
for i in range(16):
    print datetime.date.today() + datetime.timedelta(days=i)
user368996
quelle
4

Der Vollständigkeit halber hat Pandas auch eine period_rangeFunktion für Zeitstempel, die außerhalb der Grenzen liegen:

import pandas as pd

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

Ich habe ein ähnliches Problem, aber ich muss monatlich statt täglich iterieren.

Das ist meine Lösung

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

Beispiel 1

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

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

Ausgabe

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

Beispiel 2

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

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

Ausgabe

2017-01-01
2016-12-01
2016-11-01
2016-10-01
2016-09-01
2016-08-01
2016-07-01
2016-06-01
juanmhidalgo
quelle
3

Kann nicht glauben, dass diese Frage seit 9 Jahren besteht, ohne dass jemand eine einfache rekursive Funktion vorschlägt:

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)

Ausgabe:

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

Bearbeiten: * Jetzt kann ich es glauben - siehe Optimiert Python die Schwanzrekursion? . Danke Tim .

Taschen und
quelle
3
Warum sollten Sie eine einfache Schleife durch Rekursion ersetzen? Dies bricht für Bereiche, die länger als ungefähr zweieinhalb Jahre sind.
Tim-Erwin
@ Tim-Erwin Ehrlich gesagt hatte ich keine Ahnung, dass CPython die Schwanzrekursion nicht optimiert, daher ist Ihr Kommentar wertvoll.
Taschen und
2

Mit der Pandas-Bibliothek können Sie einfach und vertrauensvoll eine Reihe von Daten zwischen zwei Daten generieren

import pandas as pd

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

Sie können die Häufigkeit der Datumsgenerierung ändern, indem Sie die Frequenz auf D, M, Q, Y einstellen (täglich, monatlich, vierteljährlich, jährlich).

Shinto Joseph
quelle
Bereits in diesem Thread im Jahr 2014 beantwortet
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']
LetzerWille
quelle
1

Diese Funktion hat einige zusätzliche Funktionen:

  • kann eine Zeichenfolge übergeben, die mit DATE_FORMAT für Start oder Ende übereinstimmt, und sie wird in ein Datumsobjekt konvertiert
  • kann ein Datumsobjekt für Start oder Ende übergeben
  • Fehlerprüfung für den Fall, dass das Ende älter als der Anfang ist

    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))
dmmfll
quelle
1

Hier ist Code für eine allgemeine Datumsbereichsfunktion, ähnlich der Antwort von Ber, aber flexibler:

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'.")
Schildkröten sind süß
quelle
0

Was ist mit den folgenden Schritten, um einen um Tage erhöhten Bereich auszuführen:

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

Für eine generische Version:

for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
  # Do stuff here
  • startTime und stopTime sind datetime.date- oder datetime.datetime-Objekte (beide sollten vom gleichen Typ sein).
  • stepTime ist ein Zeitdelta-Objekt

Beachten Sie, dass .total_seconds () erst nach Python 2.7 unterstützt wird. Wenn Sie mit einer früheren Version nicht weiterkommen, können Sie Ihre eigene Funktion schreiben:

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

Etwas anderer Ansatz für reversible Schritte durch Speichern von rangeArgumenten in einem Tupel.

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

AUSGABE:

2019Jan10
2019Jan11
2019Jan12
2019Jan13
2019Jan14
2019Jan15
2019Jan16
2019Jan17
2019Jan18
2019Jan19
2019Jan20
2019Jan21
2019Jan22
2019Jan23
2019Jan24
2019Jan25
2019Jan26
2019Jan27
2019Jan28
2019Jan29
2019Jan30
2019Jan31
2019Feb01
2019Feb02
HANNAN SHAIK
quelle
Willkommen bei Stack Overflow! Während dieser Code die Frage lösen kann, einschließlich einer Erklärung, wie und warum dies das Problem löst, insbesondere bei Fragen mit zu vielen guten Antworten, würde dies wirklich dazu beitragen, die Qualität Ihres Beitrags zu verbessern, und wahrscheinlich zu mehr Upvotes führen. Denken Sie daran, dass Sie in Zukunft die Frage für die Leser beantworten, nicht nur für die Person, die jetzt fragt. Bitte bearbeiten Sie Ihre Antwort, um Erklärungen hinzuzufügen und anzugeben, welche Einschränkungen und Annahmen gelten. Aus der Überprüfung
Doppel-Piepton