J'ai le code suivant pour le faire, mais comment puis-je le faire mieux? À l'heure actuelle, je pense que c'est mieux que les boucles imbriquées, mais cela commence à devenir Perl-one-linerish lorsque vous avez un générateur dans une liste de compréhension.
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())
start_date
et end_date
sont des objets datetime.date
car je n'ai pas besoin des horodatages. (Ils vont être utilisés pour générer un rapport).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
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'est stockée, un seul générateur est itéré. De plus, le "si" dans le générateur semble être inutile.
Après tout, une séquence linéaire ne devrait nécessiter qu'un seul itérateur, pas deux.
La solution la plus élégante est peut-être d'utiliser une fonction de générateur pour masquer/résumer complètement l'itération sur la plage 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: Par souci de cohérence avec la fonction range()
intégrée, cette itération est arrêtée avant pour atteindre le end_date
. Donc, pour une itération inclusive, utilisez 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
Utilisez la bibliothèque 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")
Cette bibliothèque python possède de nombreuses fonctionnalités plus avancées, dont certaines très utiles, telles que relative delta
s - et est implémentée sous la forme d'un fichier unique (module) facilement incluse dans un projet.
Les pandas sont parfaits pour les séries chronologiques en général et prennent en charge directement les plages de dates.
import pandas as pd
daterange = pd.date_range(start_date, end_date)
Vous pouvez ensuite faire une boucle sur la plage pour imprimer la date:
for single_date in daterange:
print (single_date.strftime("%Y-%m-%d"))
Il a également beaucoup d'options pour vous rendre la vie plus facile. Par exemple, si vous ne voulez que les jours de semaine, vous devez simplement échanger bdate_range. Voir http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps
La puissance des pandas réside en réalité dans ses images, qui prennent en charge les opérations vectorisées (un peu comme Numpy), qui rendent les opérations sur de grandes quantités de données très simples et rapides.
EDIT: Vous pouvez également ignorer complètement la boucle for et l’imprimer directement, ce qui est plus simple 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 dont vous avez strictement besoin, en prenant en charge les pas négatifs, etc. Tant que vous factorisez votre logique de distance, vous n'avez pas besoin du day_count
séparé et, surtout, le code devient plus facile à lire lorsque vous appelez la fonction 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 solution la plus lisible par l’homme à laquelle je puisse penser.
import datetime
def daterange(start, end, step=datetime.timedelta(1)):
curr = start
while curr < end:
yield curr
curr += step
Afficher les n derniers jours à compter de ce jour:
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
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
consiste à convertir de numpy.datetime64
en un tableau d'objets datetime.datetime
.
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))
J'ai un problème similaire, mais je dois effectuer une itération mensuelle plutôt que quotidienne.
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
Je ne peux pas croire * que cette question existe depuis 9 ans sans que personne ne suggère une fonction récursive simple:
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: * Je peux maintenant le croire - voir Python optimise-t-il la récursion des queues? . Merci Tim .
for i in range(16):
print datetime.date.today() + datetime.timedelta(days=i)
Vous pouvez générer une série de dates entre deux dates en utilisant la bibliothèque pandas simplement et en toute confiance.
import pandas as pd
print pd.date_range(start='1/1/2010', end='1/08/2018', freq='M')
Vous pouvez modifier la fréquence de génération des dates en définissant fréq sur D, M, Q, Y (Quotidien, mensuel, trimestriel, annuel )
> 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 étapes réversibles en stockant des arguments range
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)
Qu'en est-il des éléments suivants pour effectuer une plage incrémentée de jours:
for d in map( lambda x: startDate+datetime.timedelta(days=x), xrange( (stopDate-startDate).days ) ):
# Do stuff here
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
Notez que .total_seconds () 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 fonctionnalités supplémentaires:
vérification d'erreur si 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 pour une fonction de plage de dates générale, 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'.")