Itération à travers une gamme de dates en Python

j'ai le code suivant pour ce faire, mais comment puis-je faire mieux? En ce moment je pense que c'est mieux que les boucles imbriquées, mais ça commence à devenir Perl-un-linerish quand vous avez un générateur dans une compréhension de liste.

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

Notes

  • Je ne l'utilise pas pour imprimer. C'est juste pour des fins de démonstration.
  • les variables start_date et end_date sont des objets datetime.date parce que je ne besoin d'horodateurs. (Ils vont être utilisés pour générer un rapport).

Sortie D'Échantillon

Pour une date de début de 2009-05-30 et une date de fin de 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
252
demandé sur Peter O. 2009-06-30 00:16:02

19 réponses

Pourquoi y a-t-il deux itérations imbriquées? Pour moi, il produit la même liste de données avec une seule itération:

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

Et aucune liste n'obtient stockées, un seul générateur est itéré. Aussi le "si" dans le générateur semble être inutile.

après tout, une séquence linéaire ne devrait exiger qu'un itérateur, pas deux.

mise à jour après discussion avec John Machin:

peut-être le plus élégant solution utilise une fonction de générateur pour masquer complètement/abstraire l'itération sur la gamme de dates:

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: pour la cohérence avec la fonction intégrée range() cette itération arrête avant pour atteindre le end_date . Ainsi, pour l'itération inclusive utiliser le lendemain, comme vous le feriez avec range() .

392
répondu Ber 2015-06-03 10:35:18

cela pourrait être plus clair:

d = start_date
delta = datetime.timedelta(days=1)
while d <= end_date:
    print d.strftime("%Y-%m-%d")
    d += delta
151
répondu Sean Cavanagh 2011-02-23 21:12:50

utiliser le dateutil bibliothèque:

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

cette bibliothèque python a beaucoup plus de fonctionnalités avancées, certaines très utiles, comme relative delta s-et est implémentée comme un seul fichier (module) qui est facilement inclus dans un projet.

134
répondu nosklo 2012-09-10 18:15:36

Pandas est grand pour les séries chronologiques en général, et a un soutien direct pour les gammes de date.

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

vous pouvez ensuite faire une boucle sur le changement de date pour imprimer la date:

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

Il a aussi beaucoup d'options pour rendre la vie plus facile. Par exemple, si vous ne voulez que les jours de semaine, vous pouvez simplement échanger dans bdate_range. Voir http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps

la puissance de Pandas est vraiment ses dataframes, qui prennent en charge les opérations vectorisées (un peu comme numpy) qui rendent les opérations à travers de grandes quantités de données très rapides et faciles.

EDIT: Vous pouvez également sauter complètement la boucle for et juste l'imprimer directement, ce qui est plus facile et plus efficace:

print(daterange)
42
répondu fantabolous 2014-07-23 07:17:20
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())

cette fonction fait plus que ce que vous demandez strictement, en supportant l'étape négative, etc. Aussi longtemps que vous tenez compte de votre logique de portée, alors vous n'avez pas besoin du day_count séparé et le plus important le code devient plus facile à lire que vous appelez la fonction de plusieurs endroits.

13
répondu 2009-06-29 21:32:12

Pourquoi ne pas essayer:

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
répondu john 2014-05-23 13:35:24

C'est la plus lisible solution je pense.

import datetime

def daterange(start, end, step=datetime.timedelta(1)):
    curr = start
    while curr < end:
        yield curr
        curr += step
7
répondu Patrick 2016-10-13 14:28:08

la fonction arange de Numpy peut être appliquée aux dates:

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)

l'utilisation de astype est de passer de numpy.datetime64 à un tableau d'objets datetime.datetime .

5
répondu Tor 2016-05-29 10:53:27

Afficher les n derniers jours à partir d'aujourd'hui:

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

sortie:

2016-06-29
2016-06-30
2016-07-01
2016-07-02
2016-07-03
2016-07-04
5
répondu user1767754 2018-01-21 23:27:07
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
répondu John Machin 2009-06-30 11:49:11

j'ai un problème similaire, mais je dois itérer mensuel au lieu de quotidien.

C'est ma solution

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

exemple #1

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

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

Sortie

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

exemple #2

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

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

Sortie

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
répondu juanmhidalgo 2017-01-24 07:34:17
for i in range(16):
    print datetime.date.today() + datetime.timedelta(days=i)
2
répondu user368996 2012-09-10 18:04:10

ne Peut pas croire que* cette question a existé pendant 9 ans sans que quiconque ce qui suggère une simple fonction récursive:

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)

sortie:

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: *Maintenant, je peux le croire -- voir Python optimiser la queue de la récursivité? . Merci Tim .

2
répondu Pocketsand 2018-08-31 12:29:27

vous pouvez générer une série de date entre deux dates en utilisant la bibliothèque pandas simplement et avec confiance

import pandas as pd

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

vous pouvez changer la fréquence de génération des dates en mettant freq comme D, M, Q, Y (quotidienne, mensuelle, trimestrielle, annuelle )

1
répondu Shinto Joseph 2018-08-09 05:41:50

Qu'en est-il de ce qui suit pour faire une gamme incrémentée de jours:

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

pour une version générique:

for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
  # Do stuff here
  • startTime et stopTime sont datetime.date ou datetime.objet datetime (les deux doivent être du même type)
  • stepTime est un timedelta objet

Notons que .total_secondes () n'est supporté qu'après python 2.7 si vous êtes bloqué avec une version antérieure, vous pouvez écrire votre propre fonction:

def total_seconds( td ):
  return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
0
répondu teambob 2011-07-13 03:42:20

cette fonction a quelques caractéristiques supplémentaires:

  • peut passer une chaîne de caractères correspondant à DATE_FORMAT pour start ou end et il est converti en un objet date
  • peut passer un objet date pour début ou fin
  • vérification des erreurs dans le cas où la fin est plus ancienne que le début

    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
répondu DMfll 2014-07-27 04:01:52

voici le code d'une fonction générale d'intervalle de date, similaire à la réponse de Ber, mais plus flexible:

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
répondu Turtles Are Cute 2016-07-15 17:59:27
> 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']
0
répondu LetzerWille 2018-09-11 16:04:44

approche légèrement différente des marches réversibles en stockant range args dans un 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
répondu GollyJer 2018-10-03 16:13:41