wake-up-neo.net

Durchlaufen einer Reihe von Datumsangaben in Python

Ich habe dazu den folgenden Code, aber wie kann ich das besser machen? Im Moment denke ich, dass es besser ist als verschachtelte Schleifen, aber es beginnt, Perl-one-linerish zu bekommen, 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 verwende das eigentlich nicht zum Drucken. Das ist nur zu Demonstrationszwecken. 
  • Die Variablen start_date und end_date sind datetime.date-Objekte, da ich keine Zeitstempel benötige. (Sie werden verwendet, um einen Bericht zu erstellen).

Beispielausgabe

Für ein Startdatum von 2009-05-30 und 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
278
ShawnMilo

Warum gibt es zwei verschachtelte Iterationen? Für mich erzeugt es dieselbe 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 wiederholt. 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 eleganteste Lösung ist die Verwendung einer Generatorfunktion, um die Iteration über den gesamten Datumsbereich hinweg zu verbergen/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")

Hinweis: Zur Konsistenz mit der integrierten Funktion range() stoppt diese Iteration vor Erreichen des end_date. Verwenden Sie also die Iteration inklusiv am nächsten Tag, wie Sie es mit range() tun würden.

434
Ber

Das könnte klarer sein:

d = start_date
delta = datetime.timedelta(days=1)
while d <= end_date:
    print d.strftime("%Y-%m-%d")
    d += delta
155
Sean Cavanagh

Verwenden Sie die Bibliothek 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")

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

139
nosklo

Pandas eignet sich hervorragend für Zeitreihen im Allgemeinen und unterstützt Datumsbereiche direkt.

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

Sie können dann die Datumsauswahl übergehen, um das Datum zu drucken:

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

Es hat auch viele Optionen, um das Leben leichter zu machen. Wenn Sie beispielsweise nur Wochentage haben 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 sind in der Tat seine Datenrahmen, die vektorisierte Operationen (ähnlich wie Numpy) unterstützen, die Operationen mit großen Datenmengen sehr schnell und einfach machen.

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

print(daterange)
47
fantabolous
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 bietet mehr, als Sie unbedingt benötigen, indem Sie negative Schritte usw. unterstützen. Solange Sie Ihre Bereichslogik ausfeilen, benötigen Sie keinen separaten day_count und vor allem lässt sich der Code leichter lesen, wenn Sie die Funktion aufrufen mehrere Orte.

13
Roger Pate

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
11
john

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
9
Patrick

Zeige die letzten n Tage ab 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
6
user1767754

Die arange-Funktion von Numpy kann auf Datumsangaben 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 astype ist die Konvertierung von numpy.datetime64 in ein Array von datetime.datetime-Objekten.

5
Tor
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 __== "__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
John Machin

Ich habe ein ähnliches Problem, aber ich muss monatlich anstelle von täglich durchlaufen.

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
3
juanmhidalgo

Ich kann nicht glauben, dass diese Frage seit 9 Jahren existiert, 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

Edit: * Jetzt kann ich es glauben - siehe Optimiert Python die Rekursion des Schwanzes? . Vielen Dank Tim .

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

Mit der Pandabibliothek können Sie einfach und vertrauenswürdig eine Datumsreihe zwischen zwei Datumsangaben 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 für die Frequenz D, M, Q, Y (Täglich, monatlich, vierteljährlich, jährlich ) Einstellen.

1
Shinto Joseph
> 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
LetzerWille

Eine etwas andere Herangehensweise an reversible Schritte durch Speichern von range args in einem Tuple. 

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
GollyJer

Wie sieht es mit Folgendem aus, wenn Sie einen Bereich um Tage erhöhen:

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-Objekt (beide sollten vom gleichen Typ sein)
  • stepTime ist ein Timedelta-Objekt

Beachten Sie, dass .total_seconds () nur nach Python 2.7 unterstützt wird. Wenn Sie bei einer früheren Version stecken, 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
0
teambob

Diese Funktion hat einige zusätzliche Funktionen:

  • kann eine Zeichenfolge übergeben, die mit DATE_FORMAT für Anfang oder Ende übereinstimmt und in ein Datumsobjekt umgewandelt wird
  • kann ein Datumsobjekt für den Start oder das Ende übergeben
  • fehlerprüfung, falls das Ende älter ist als der Anfang

    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))
    
0
DMfll

Hier der Code für eine allgemeine Datumsbereichsfunktion, ähnlich der Antwort von Ber, jedoch 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'.")
0