452 votes

Itération dans une plage de dates en Python

J'ai le code suivant pour le faire, mais comment puis-je l'améliorer ? Pour l'instant, je pense que c'est mieux que des boucles imbriquées, mais ça commence à devenir Perl-one-linerish quand on a 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 vraiment pour imprimer. C'est juste pour la démonstration.
  • El start_date y end_date Les variables sont datetime.date car je n'ai pas besoin de l'horodatage. (Ils vont être utilisés pour générer un rapport).

Exemple de sortie

Pour une date de début de 2009-05-30 et une date de fin de validité 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

3 votes

Juste pour préciser : Je ne pense pas qu'il y ait de différence entre 'time.strftime("%Y-%m-%d", single_date.timetuple())' et le plus court 'single_date.strftime("%Y-%m-%d")'. La plupart des réponses semblent copier le style le plus long.

11 votes

Wow, ces réponses sont beaucoup trop compliquées. Essaie ça : stackoverflow.com/questions/7274267/

0 votes

@GringoSuave : qu'est-ce qui est compliqué dans La réponse de Sean Cavanagh ?

662voto

Ber Points 10364

Pourquoi y a-t-il deux itérations imbriquées ? Pour moi, cela 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'est stockée, seul un générateur est itéré. De plus, le "if" dans le générateur semble inutile.

Après tout, une séquence linéaire ne devrait nécessiter qu'un seul itérateur, et non deux.

Mise à jour après discussion avec John Machin :

La solution la plus élégante consiste peut-être à utiliser une fonction de générateur pour masquer/abstraire complètement l'itération sur la plage de dates :

from datetime import date, timedelta

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 des raisons de cohérence avec le système intégré range() cette itération s'arrête avant atteindre le end_date . Donc pour l'itération inclusive, utilisez le jour suivant, comme vous le feriez avec range() .

0 votes

Merci, bon point. Cette instruction if est un reliquat d'une version précédente, avant que je ne soustraie la date de début à la date de fin.

0 votes

Merci Ber, c'est génial. Ça fonctionne parfaitement et c'est bien mieux que mon vieux bordel.

4 votes

-1 ... avoir un calcul préliminaire de day_count et utiliser la plage n'est pas génial quand une simple boucle while suffira.

273voto

Sean Cavanagh Points 1156

Cela pourrait être plus clair :

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

6 votes

Très clair et court, mais ne fonctionne pas bien si vous voulez utiliser la fonction "continuer".

179voto

nosklo Points 75862

Utilisez 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 possède de nombreuses fonctionnalités plus avancées, dont certaines très utiles, telles que relative delta et est implémenté comme un seul fichier (module) qui est

0 votes

Des documents plus modernes dateutil.readthedocs.io/fr/stable/rrule.html

4 votes

Notez que la date finale dans la boucle for ici est inclusivement de until alors que la date finale de la daterange méthode dans La réponse de Ber es exclusif de end_date .

99voto

fantabolous Points 431

Pandas est excellent pour les séries temporelles en général, et a un support direct pour les plages de dates.

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

Vous pouvez ensuite boucler sur la plage de dates pour imprimer la date :

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

Il dispose également de nombreuses options pour vous faciliter la vie. Par exemple, si vous ne voulez que les jours de la semaine, il vous suffit de remplacer bdate_range. Voir http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps

La puissance de Pandas réside dans ses cadres de données, qui prennent en charge les opérations vectorielles (un peu comme numpy), ce qui rend les opérations sur de grandes quantités de données très rapides et faciles.

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

print(daterange)

1 votes

"much like numpy" - Pandas est construit sur numpy :P

17voto

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 dont vous avez strictement besoin, en prenant en charge les pas négatifs, etc. Tant que vous tenez compte de votre logique de plage, vous n'avez pas besoin de la fonction séparée day_count et surtout, le code devient plus facile à lire car vous appelez la fonction à partir de plusieurs endroits.

0 votes

Merci, renommé pour mieux correspondre aux paramètres de la gamme, j'ai oublié de changer dans le corps.

0 votes

+1 ... mais comme vous permettez à l'étape d'être un timedelta, vous devriez soit (a) l'appeler dateTIMErange() et faire fonctionner correctement les étapes de par exemple timedelta(hours=12) et timedelta(hours=36) ou (b) piéger les étapes qui ne sont pas un nombre entier de jours ou (c) épargner à l'appelant les tracas et exprimer l'étape comme un nombre de jours au lieu d'un timedelta.

0 votes

Tout timedelta devrait déjà fonctionner, mais j'ai ajouté datetime_range et date_range à ma collection personnelle de déchets après avoir écrit ceci, à cause de (a). Je ne suis pas sûr qu'une autre fonction soit utile pour (c), le cas le plus courant de days=1 est déjà pris en charge, et le fait de devoir passer un timedelta explicite évite toute confusion. Peut-être que le mieux est de le télécharger quelque part : bitbucket.org/kniht/scraps/src/tip/python/gen_range.py

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X