sumar - Iterando a través de un rango de fechas en Python
restar tiempo python (19)
¿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 ninguna lista se almacena, solo se itera un generador. También el "si" en el generador parece ser innecesario.
Después de todo, una secuencia lineal solo debe requerir un iterador, no dos.
Actualización después de la discusión con John Machin:
Quizás la solución más elegante es usar una función de generador para ocultar / abstraer completamente la iteración en 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 mantener la coherencia con la función de range()
incorporada, esta iteración se detiene antes de llegar a la fecha end_date
. Entonces, para la iteración inclusiva, use el día siguiente, como lo haría con range()
.
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 tener Perl-one-linerish cuando tienes un generador en una lista de comprensión.
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. (Se van a utilizar 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
¿Por qué no intentarlo?
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
¿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 objetos datetime.date
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 objetos datetime.date o datetime.datetime (ambos deben ser del mismo tipo)
- stepTime es un objeto timedelta
Tenga en cuenta que .total_seconds () solo se admite 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
Aquí está el código para una función de rango de fechas general, 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''.")
Esta es la solución más legible por humanos que se me ocurre.
import datetime
def daterange(start, end, step=datetime.timedelta(1)):
curr = start
while curr < end:
yield curr
curr += step
Esta función tiene algunas características adicionales:
- puede pasar una cadena que coincida con DATE_FORMAT para el inicio o el final y se convierte en un objeto de fecha
- Puede pasar un objeto de fecha para inicio o fin
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))
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
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 de numpy.datetime64
a una matriz de objetos datetime.datetime
.
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
Edición: * Ahora puedo creerlo. Ver ¿Python optimiza la recursión de la cola? . Gracias Tim
Pandas es ideal para series de tiempo en general y tiene soporte directo para rangos de fechas.
import pandas as pd
daterange = pd.date_range(start_date, end_date)
A continuación, puede recorrer el daterange 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 deseaba entre semana, solo intercambiaría bdate_range. Consulte http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps
El poder de Pandas es realmente sus marcos de datos, que admiten operaciones vectorizadas (muy parecidas a las de números) 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 y simplemente imprimirlo directamente, lo que es más fácil y más eficiente:
print(daterange)
Puede generar una serie de fechas entre dos fechas utilizando la biblioteca de pandas de manera simple 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 generación de fechas configurando las frecuencias como D, M, Q, Y (diaria, mensual, trimestral, anual)
Tengo un problema similar, pero necesito iterar mensualmente en lugar de diario.
Esta es mi solucion
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
Un enfoque ligeramente diferente de los pasos reversibles al almacenar los argumentos de range
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)
Usa la librería 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")
Esta biblioteca de Python tiene muchas más características avanzadas, algunas muy útiles, como relative delta
, y se implementa como un solo archivo (módulo) que se incluye fácilmente en un proyecto.
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
for i in range(16):
print datetime.date.today() + datetime.timedelta(days=i)
> 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'']
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 que estrictamente requiere, apoyando el paso negativo, etc. Mientras que day_count
su lógica de rango, entonces no necesita el day_count
separado y, lo que es más importante, el código se vuelve más fácil de leer al llamar a la función desde múltiples lugares.
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))