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
etend_date
sont des objetsdatetime.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
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()
.
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
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.
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)
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.
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
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
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
.
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
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))
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
for i in range(16):
print datetime.date.today() + datetime.timedelta(days=i)
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 .
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 )
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
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))
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'.")
> 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']
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)