Iterar a través de un rango de fechas en Python
Tengo el siguiente código para hacer esto, pero ¿cómo puedo hacerlo mejor? En este momento creo que es mejor que los bucles anidados, pero comienza a ponerse Perl-one-linerish cuando tienes un generador en una comprensión de lista.
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())
Notas
- En realidad no estoy usando esto para imprimir. Eso es sólo para fines de demostración.
- Las variables
start_date
yend_date
son objetosdatetime.date
porque no necesito las marcas de tiempo. (Van a ser utilizados para generar un informe).
Salida de muestra
Para una fecha de inicio de 2009-05-30
y una fecha de finalización 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 answers
¿por Qué hay dos iteraciones anidadas? Para mí produce la misma lista de datos con una sola iteración:
for single_date in (start_date + timedelta(n) for n in range(day_count)):
print ...
Y no se almacena ninguna lista, solo se itera un generador. También el "si" en el generador parece ser innecesario.
Después de todo, una secuencia lineal solo debería requerir un iterador, no dos.
Actualización después de la discusión con John Machin:
Tal vez la solución más elegante es usar una función de generador para ocultar/abstraer completamente la iteración sobre el rango de fechas:
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: Para la consistencia con la función incorporada range()
, esta iteración se detiene antes de que llegue a end_date
. Así que para la iteración inclusiva use el día siguiente, como lo haría con range()
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-03 10:35:18
Esto podría ser más claro:
d = start_date
delta = datetime.timedelta(days=1)
while d <= end_date:
print d.strftime("%Y-%m-%d")
d += delta
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-23 21:12:50
Utilice el dateutil
biblioteca:
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")
Esta biblioteca de python tiene muchas características más avanzadas, algunas muy útiles, como relative delta
s-y se implementa como un solo archivo (módulo) que se incluye fácilmente en un proyecto.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-10 18:15:36
Pandas es ideal para series temporales en general, y tiene soporte directo para rangos de fechas.
import pandas as pd
daterange = pd.date_range(start_date, end_date)
Luego puede hacer un bucle sobre el intervalo de datos para imprimir la fecha:
for single_date in daterange:
print (single_date.strftime("%Y-%m-%d"))
También tiene muchas opciones para hacer la vida más fácil. Por ejemplo, si solo quisieras los días de la semana, simplemente cambiarías en bdate_range. Véase http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps
El poder de los Pandas es realmente sus dataframes, que soportan vectorizados operaciones (como numpy) que hacen que las operaciones en grandes cantidades de datos sean muy rápidas y fáciles.
EDITAR: También puede omitir completamente el bucle for e imprimirlo directamente, lo cual es más fácil y más eficiente:
print(daterange)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
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())
Esta función hace más de lo estrictamente necesario, apoyando el paso negativo, etc. Siempre y cuando factorice su lógica de rango, entonces no necesita el day_count
separado y lo más importante es que el código se vuelve más fácil de leer cuando llama a la función desde varios lugares.
¿Por qué no intentar:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-23 13:35:24
Esta es la solución más legible que se me ocurre.
import datetime
def daterange(start, end, step=datetime.timedelta(1)):
curr = start
while curr < end:
yield curr
curr += step
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-13 14:28:08
La función arange
de Numpy se puede aplicar a las fechas:
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)
El uso de astype
es convertir numpy.datetime64
a una matriz de datetime.datetime
objetos.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-29 10:53:27
Mostrar los últimos n días a partir de hoy:
import datetime
for i in range(0, 100):
print((datetime.date.today() + datetime.timedelta(i)).isoformat())
Salida:
2016-06-29
2016-06-30
2016-07-01
2016-07-02
2016-07-03
2016-07-04
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
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))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-30 11:49:11
Tengo un problema similar, pero necesito iterar mensualmente en lugar de diariamente.
Esta es mi solución
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
Ejemplo #1
date_start = datetime(2016, 6, 1)
date_end = datetime(2017, 1, 1)
for p in monthly_range(date_start, date_end):
print(p)
Salida
2016-06-01
2016-07-01
2016-08-01
2016-09-01
2016-10-01
2016-11-01
2016-12-01
2017-01-01
Ejemplo #2
date_start = datetime(2017, 1, 1)
date_end = datetime(2016, 6, 1)
for p in monthly_range(date_start, date_end):
print(p)
Salida
2017-01-01
2016-12-01
2016-11-01
2016-10-01
2016-09-01
2016-08-01
2016-07-01
2016-06-01
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-24 07:34:17
for i in range(16):
print datetime.date.today() + datetime.timedelta(days=i)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-10 18:04:10
No puedo creer * esta pregunta ha existido durante 9 años sin que nadie sugiera una función recursiva 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)
Salida:
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
Editar: *Ahora puedo creerlo see ver ¿Python optimiza la recursión de cola? . Gracias Tim.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-31 12:29:27
Puede generar una serie de fechas entre dos fechas usando la biblioteca de pandas de manera sencilla y confiable
import pandas as pd
print pd.date_range(start='1/1/2010', end='1/08/2018', freq='M')
Puede cambiar la frecuencia de las fechas de generación estableciendo freq como D, M, Q, Y (diario, mensual, trimestral, anual )
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-09 05:41:50
¿Qué pasa con lo siguiente para hacer un rango incrementado por días:
for d in map( lambda x: startDate+datetime.timedelta(days=x), xrange( (stopDate-startDate).days ) ):
# Do stuff here
- startDate y stopDate son datetime.objetos de fecha
Para una versión genérica:
for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
# Do stuff here
- startTime y stopTime son datetime.fecha o fecha y hora.objeto datetime (ambos deben ser del mismo tipo)
- stepTime es un objeto timedelta
Tenga en cuenta que .total_seconds() solo es compatible después de python 2.7 Si está atascado con una versión anterior, puede escribir su propia función:
def total_seconds( td ):
return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-13 03:42:20
Esta función tiene algunas características adicionales:
- puede pasar una cadena que coincida con el DATE_FORMAT para start o end y se convierte en un objeto date
- puede pasar un objeto date para start o end
-
Comprobación de errores en caso de que el final sea anterior al inicio
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))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-27 04:01:52
Aquí está el código para una función general de intervalo de fechas, similar a la respuesta de Ber, pero más 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'.")
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
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']
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-11 16:04:44
Un enfoque ligeramente diferente para los pasos reversibles almacenando range
args en una tupla.
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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-03 16:13:41