I have the following code to do this, but how can I do it better? Right now I think it's better than nested loops, but it starts to get Perl-one-linerish when you have a generator in a list comprehension.
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
I'm not actually using this to print. That's just for demo purposes.
The start_date and end_date variables are datetime.date objects because I don't need the timestamps. (They're going to be used to generate a report).
Sample Output
For a start date of 2009-05-30 and an end date of 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
Why are there two nested iterations? For me it produces the same list of data with only one iteration:
for single_date in (start_date + timedelta(n) for n in range(day_count)):
print ...
And no list gets stored, only one generator is iterated over. Also the "if" in the generator seems to be unnecessary.
After all, a linear sequence should only require one iterator, not two.
Update after discussion with John Machin:
Maybe the most elegant solution is using a generator function to completely hide/abstract the iteration over the range of 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: For consistency with the built-in range() function this iteration stops before reaching the end_date. So for inclusive iteration use the next day, as you would with range().
This might be more clear:
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
Use the dateutil library:
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")
This python library has many more advanced features, some very useful, like relative deltas—and is implemented as a single file (module) that's easily included into a project.
Pandas is great for time series in general, and has direct support for date ranges.
import pandas as pd
daterange = pd.date_range(start_date, end_date)
You can then loop over the daterange to print the date:
for single_date in daterange:
print (single_date.strftime("%Y-%m-%d"))
It also has lots of options to make life easier. For example if you only wanted weekdays, you would just swap in bdate_range. See http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps
The power of Pandas is really its dataframes, which support vectorized operations (much like numpy) that make operations across large quantities of data very fast and easy.
EDIT:
You could also completely skip the for loop and just print it directly, which is easier and more efficient:
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())
This function does more than you strictly require, by supporting negative step, etc. As long as you factor out your range logic, then you don't need the separate day_count and most importantly the code becomes easier to read as you call the function from multiple places.
This is the most human-readable solution I can think of.
import datetime
def daterange(start, end, step=datetime.timedelta(1)):
curr = start
while curr < end:
yield curr
curr += step
Numpy's arange function can be applied to 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)
The use of astype is to convert from numpy.datetime64 to an array of datetime.datetime objects.
Why not try:
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
Show the last n days from today:
import datetime
for i in range(0, 100):
print((datetime.date.today() + datetime.timedelta(i)).isoformat())
Output:
2016-06-29
2016-06-30
2016-07-01
2016-07-02
2016-07-03
2016-07-04
For completeness, Pandas also has a period_range function for timestamps that are out of bounds:
import pandas as pd
pd.period_range(start='1/1/1626', end='1/08/1627', freq='D')
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))
for i in range(16):
print datetime.date.today() + datetime.timedelta(days=i)
I have a similar problem, but I need to iterate monthly instead of daily.
This is my 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
Example #1
date_start = datetime(2016, 6, 1)
date_end = datetime(2017, 1, 1)
for p in monthly_range(date_start, date_end):
print(p)
Output
2016-06-01
2016-07-01
2016-08-01
2016-09-01
2016-10-01
2016-11-01
2016-12-01
2017-01-01
Example #2
date_start = datetime(2017, 1, 1)
date_end = datetime(2016, 6, 1)
for p in monthly_range(date_start, date_end):
print(p)
Output
2017-01-01
2016-12-01
2016-11-01
2016-10-01
2016-09-01
2016-08-01
2016-07-01
2016-06-01
You can generate a series of date between two dates using the pandas library simply and trustfully
import pandas as pd
print pd.date_range(start='1/1/2010', end='1/08/2018', freq='M')
You can change the frequency of generating dates by setting freq as D, M, Q, Y
(daily, monthly, quarterly, yearly
)
Using pendulum.period:
import pendulum
start = pendulum.from_format('2020-05-01', 'YYYY-MM-DD', formatter='alternative')
end = pendulum.from_format('2020-05-02', 'YYYY-MM-DD', formatter='alternative')
period = pendulum.period(start, end)
for dt in period:
print(dt.to_date_string())
> 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']
For those who are interested in Pythonic functional way:
from datetime import date, timedelta
from itertools import count, takewhile
for d in takewhile(lambda x: x<=date(2009,6,9), map(lambda x:date(2009,5,30)+timedelta(days=x), count())):
print(d)
What about the following for doing a range incremented by days:
for d in map( lambda x: startDate+datetime.timedelta(days=x), xrange( (stopDate-startDate).days ) ):
# Do stuff here
startDate and stopDate are datetime.date objects
For a generic version:
for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
# Do stuff here
startTime and stopTime are datetime.date or datetime.datetime object
(both should be the same type)
stepTime is a timedelta object
Note that .total_seconds() is only supported after python 2.7 If you are stuck with an earlier version you can write your own function:
def total_seconds( td ):
return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
This function has some extra features:
can pass a string matching the DATE_FORMAT for start or end and it is converted to a date object
can pass a date object for start or end
error checking in case the end is older than the start
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))
Here's code for a general date range function, similar to Ber's answer, but more 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'.")
import datetime
from dateutil.rrule import DAILY,rrule
date=datetime.datetime(2019,1,10)
date1=datetime.datetime(2019,2,2)
for i in rrule(DAILY , dtstart=date,until=date1):
print(i.strftime('%Y%b%d'),sep='\n')
OUTPUT:
2019Jan10
2019Jan11
2019Jan12
2019Jan13
2019Jan14
2019Jan15
2019Jan16
2019Jan17
2019Jan18
2019Jan19
2019Jan20
2019Jan21
2019Jan22
2019Jan23
2019Jan24
2019Jan25
2019Jan26
2019Jan27
2019Jan28
2019Jan29
2019Jan30
2019Jan31
2019Feb01
2019Feb02
from datetime import date,timedelta
delta = timedelta(days=1)
start = date(2020,1,1)
end=date(2020,9,1)
loop_date = start
while loop_date<=end:
print(loop_date)
loop_date+=delta
You can use Arrow:
This is example from the docs, iterating over hours:
from arrow import Arrow
>>> start = datetime(2013, 5, 5, 12, 30)
>>> end = datetime(2013, 5, 5, 17, 15)
>>> for r in Arrow.range('hour', start, end):
... print repr(r)
...
<Arrow [2013-05-05T12:30:00+00:00]>
<Arrow [2013-05-05T13:30:00+00:00]>
<Arrow [2013-05-05T14:30:00+00:00]>
<Arrow [2013-05-05T15:30:00+00:00]>
<Arrow [2013-05-05T16:30:00+00:00]>
To iterate over days, you can use like this:
>>> start = Arrow(2013, 5, 5)
>>> end = Arrow(2013, 5, 5)
>>> for r in Arrow.range('day', start, end):
... print repr(r)
(Didn't check if you can pass datetime.date objects, but anyways Arrow objects are easier in general)
If you are going to use dynamic timedelta then you can use:
1. With while loop
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
while start <= end:
yield start
start += delta
2. With for loop
from datetime import datetime, timedelta
from typing import Generator
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
delta_units = int((end - start) / delta)
for _ in range(delta_units + 1):
yield start
start += delta
3. If you are using async/await
async def datetime_range(start: datetime, end: datetime, delta: timedelta) -> AsyncGenerator[datetime, None]:
delta_units = int((end - start) / delta)
for _ in range(delta_units + 1):
yield start
start += delta
4. List comprehension
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> List[datetime]:
delta_units = int((end - start) / delta)
return [start + (delta * index) for index in range(delta_units + 1)]
Then 1 and 2 solutions simply can be used like this
start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)
result = [time_part for time_part in datetime_range(start, end, delta)]
# or
for time_part in datetime_range(start, end, delta):
print(time_part)
3-third solution can be used like this in async context. Because it retruns an async generator object, which can be used only in async context
start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)
result = [time_part async for time_part in datetime_range(start, end, delta)]
async for time_part in datetime_range(start, end, delta):
print(time_part)
The benefit of the solutions about is that all of them are using dynamic timedelta. This can be very usefull in cases when you do not know which time delta you will have.
Slightly different approach to reversible steps by storing range args in a 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)
Related
I am trying to modify this GitHub code for my own purposes in the title:
import random
from datetime import datetime, timedelta
min_year=1900
max_year=datetime.now().year
start = datetime(min_year, 1, 1, 00, 00, 00)
years = max_year - min_year+1
end = start + timedelta(days=365 * years)
for i in range(10):
random_date = start + (end - start) * random.random()
print(random_date)
My desired outcome specifically is, for all weekdays (Mon. - Fri.) April 1st, 2023 until July 31st, 2023 print two times (hh:mm:ss) which meet the following conditions:
Are an hour or more apart
Are within the hours of 8 AM - 6 PM (0800 - 1800)
I came up with this before remembering I need to add the two per weekday and hour-apart constraint in somewhere:
import random
from datetime import datetime, timedelta
start = datetime(2023, 4, 1, 00, 00, 00)
end = start + timedelta(days=160)
for i in range(10):
random_date = start + (end - start) * random.random()
no = random_date.weekday()
if no < 5:
print(random_date)
I'll be continuing to work on it, but if anyone has any advice I'd greatly appreciate it! Am fairly new to programming
You could try the following:
from random import uniform
from datetime import datetime, timedelta
start = datetime(2023, 4, 1) # April 1st, 2023
end = datetime(2023, 7, 31) # July 31st, 2023
one_day = timedelta(days=1)
while start <= end:
if start.weekday() < 5:
t1, t2 = uniform(8, 18), uniform(8, 18)
while abs(t1 - t2) < 1:
t2 = uniform(8, 18)
t1, t2 = (t1, t2) if t1 < t2 else (t2, t1)
t1 = start + one_day * (t1 / 24)
t2 = start + one_day * (t2 / 24)
print(t1, t2)
start += one_day
Loop over the date range of interest (from start to end) with a while-loop: at the end of each loop add a day to start, break out of the loop once end is processed.
If the day is a weekday, draw 2 random numbers uniformly distributed between 8 and 18 until they are at least 1 apart. Then divide them by 24 and add the corresponding part of a day to start.
If you are only interested in a seconds-resolution (which seems to be the case, but I have missed at first) then you can do that solely integer-based:
from random import randint
from datetime import datetime, timedelta
start = datetime(2023, 4, 1) # April 1st, 2023
end = datetime(2023, 7, 31) # July 31st, 2023
start_s, end_s = 3_600 * 8, 3_600 * 18
one_day = timedelta(days=1)
while start <= end:
if start.weekday() < 5:
t1, t2 = randint(start_s, end_s), randint(start_s, end_s)
while abs(t1 - t2) < 3_600:
t2 = randint(start_s, end_s)
t1, t2 = (t1, t2) if t1 < t2 else (t2, t1)
t1 = start + timedelta(seconds=t1)
t2 = start + timedelta(seconds=t2)
print(t1, t2)
start += one_day
Result looks like:
2023-04-03 12:10:39 2023-04-03 17:13:36
2023-04-04 09:11:52 2023-04-04 13:57:01
2023-04-05 09:57:52 2023-04-05 16:37:29
2023-04-06 09:36:55 2023-04-06 10:53:52
2023-04-07 13:57:20 2023-04-07 16:22:57
2023-04-10 10:01:00 2023-04-10 13:52:53
2023-04-11 14:15:35 2023-04-11 15:40:56
...
You can create the time ranges dynamically (guarantees a O(N) runtime where N is the number of days).
Based on your example, the start time must be from [8 AM, 5 PM]; the end time must be [start + 1h, 6 PM].
from collections import namedtuple
from datetime import date, timedelta, datetime
from random import randint
# Python built-in times are easy to mess up; work with "second tuples"
SimpleTime = namedtuple("SimpleTime", "hh mm ss")
def timetuple_to_seconds(time_tuple: SimpleTime) -> int:
return (time_tuple[0] * 60 + time_tuple[1]) * 60 + time_tuple[2]
def combine_timestamp(base_date: date, seconds_since_midnight: int) -> datetime:
return (
datetime.fromordinal(base_date.toordinal()) +
timedelta(seconds=seconds_since_midnight)
)
one_day = timedelta(days=1)
current_date = date(2023, 4, 1) - one_day # start on Apr 1
end_date = date(2023, 7, 31) # end on Jul 31
start_time = timetuple_to_seconds(SimpleTime(8, 0, 0)) # 8 AM
end_time = timetuple_to_seconds(SimpleTime(18, 0, 0)) # 6 PM
min_gap = timetuple_to_seconds(SimpleTime(1, 0, 0)) # 1h
while current_date < end_date:
current_date += one_day
if current_date.weekday() > 4: # weekend
continue
time_1 = randint(start_time, end_time - min_gap) # from start to end - gap
time_2 = randint(time_1 + min_gap, end_time) # from time_1 to end
print(
combine_timestamp(current_date, time_1),
'to',
combine_timestamp(current_date, time_2)
)
which should return something like
2023-04-03 08:14:14 to 2023-04-03 15:47:28
2023-04-04 08:23:13 to 2023-04-04 16:11:04
2023-04-05 16:08:53 to 2023-04-05 17:19:22
2023-04-06 09:47:17 to 2023-04-06 12:07:44
2023-04-07 11:27:45 to 2023-04-07 17:36:54
2023-04-10 08:43:15 to 2023-04-10 13:56:21
2023-04-11 09:06:26 to 2023-04-11 14:16:13
2023-04-12 13:44:43 to 2023-04-12 17:10:56
...
I'd like to find the most pythonic way to output a list of the week numbers between two dates.
For example:
input
start = datetime.date(2011, 12, 25)
end = datetime.date(2012, 1, 21)
output
find_weeks(start, end)
>> [201152, 201201, 201202, 201203]
I've been struggling using the datetime library with little success
Something in the lines of (update: removed less-readable option)
import datetime
def find_weeks(start,end):
l = []
for i in range((end-start).days + 1):
d = (start+datetime.timedelta(days=i)).isocalendar()[:2] # e.g. (2011, 52)
yearweek = '{}{:02}'.format(*d) # e.g. "201152"
l.append(yearweek)
return sorted(set(l))
start = datetime.date(2011, 12, 25)
end = datetime.date(2012, 1, 21)
print(find_weeks(start,end)[1:]) # [1:] to exclude first week.
Returns
['201152', '201201', '201202', '201203']
To include the first week (201151) simply remove [1:] after function call
.isocalendar() is your friend here - it returns a tuple of (year, week of year, day of week). We use that to reset the start date to the start of th eweek, and then add on a week each time until we pass the end date:
import datetime
def find_weeks(start_date, end_date):
subtract_days = start_date.isocalendar()[2] - 1
current_date = start_date + datetime.timedelta(days=7-subtract_days)
weeks_between = []
while current_date <= end_date:
weeks_between.append(
'{}{:02d}'.format(*current_date.isocalendar()[:2])
)
current_date += datetime.timedelta(days=7)
return weeks_between
start = datetime.date(2011, 12, 25)
end = datetime.date(2012, 1, 21)
print(find_weeks(start, end))
This prints
['201152', '201201', '201202', '201203']
Using Pandas
import pandas as pd
dates=pd.date_range(start=start,end=end,freq='W')
date_index=dates.year.astype(str)+dates.weekofyear.astype(str).str.zfill(2)
date_index.tolist()
I suggest you the following easy-to-read solution:
import datetime
start = datetime.date(2011, 12, 25)
end = datetime.date(2012, 1, 21)
def find_weeks(start, end):
l = []
while (start.isocalendar()[1] != end.isocalendar()[1]) or (start.year != end.year):
l.append(start.isocalendar()[1] + 100*start.year)
start += datetime.timedelta(7)
l.append(start.isocalendar()[1] + 100*start.year)
return (l[1:])
print(find_weeks(start, end))
>> [201252, 201201, 201202, 201203]
I prefer the arrow style solution here (might need pip install arrow):
import arrow
start = arrow.get('2011-12-25')
end = arrow.get('2012-01-21')
weeks = list(arrow.Arrow.span_range('week', start, end))
result looks like this:
>> from pprint import pprint
>> pprint(weeks[1:])
[(<Arrow [2011-12-19T00:00:00+00:00]>,
<Arrow [2011-12-25T23:59:59.999999+00:00]>),
(<Arrow [2011-12-26T00:00:00+00:00]>,
<Arrow [2012-01-01T23:59:59.999999+00:00]>),
(<Arrow [2012-01-02T00:00:00+00:00]>,
<Arrow [2012-01-08T23:59:59.999999+00:00]>),
(<Arrow [2012-01-09T00:00:00+00:00]>,
<Arrow [2012-01-15T23:59:59.999999+00:00]>),
(<Arrow [2012-01-16T00:00:00+00:00]>,
<Arrow [2012-01-22T23:59:59.999999+00:00]>)]
from there you can change the output to match the year and week number.
Is there any python function to deduce the number of Fridays or Thursdays from a date range? I searched google and I found many methods which usually use days divided by 7 concept but it does not give you the accurate days. For example from 1/Nov/2016 to 12/Nov/2016 there are two Fridays and two Thursdays so the result of subtraction should be 8.
You can do it with numpy:
import numpy as np
from datetime import datetime
start_date = datetime(2022, 10, 19).strftime('%Y-%m-%d')
end_date = datetime(2022, 12, 21).strftime('%Y-%m-%d')
weekend_days = np.busday_count(start_date, end_date, weekmask='0000110').item()
numpy busday_count doc
Keep in mind that end date is excluded from the count.
Using the date object from the datetime module.
from datetime import date, timedelta
curr = date(2016, 11, 1)
end = date(2016, 11, 12)
step = timedelta(1)
num_thur_fri = 0
while curr <= end:
if curr.weekday() in [3,2]: #Friday and thursday
num_thur_fri += 1
curr += step
print(num_thur_fri)
More reading here: https://docs.python.org/2/library/datetime.html#module-datetime
#brianpck is right, this is a really naive solution. Here's a better one
from datetime import date
begin = date(2016, 11, 1)
end = date(2016, 11, 12)
diff = (begin-end).days
day_of_week = begin.weekday()
num_thur_fri = 2*(diff//7)
for i in range(diff%7):
if day_of_week in [2,3]:
num_thur_fri += 1
day_of_week = (day_of_week +1) %7
Here is a simpler and faster approach that will calculate this figure for long periods of time.
First, you must calculate the amount of days between two datetime's. You can then floor divide by 7 to get the amount of entire weeks and multiply by 2 to get the number of Thursdays and Fridays. The final step is to modulo by seven to get the amount of days at the tail and then calculate how many of those are Thursdays or Fridays: this last step is the only one that actually requires knowing which weekday it is.
A full function would be:
from datetime import datetime, timedelta
def thursday_fridays_between(date1, date2):
days_between = abs((date2 - date1).days)
thursday_friday = days_between // 7 * 2
thursday_friday += sum((a + timedelta(i)).weekday() in (3, 2) for i in range(days_between % 7 + 1))
return thursday_friday
It can be used as follows:
>>> a = datetime(2016, 11, 1)
>>> b = datetime(2016, 11, 12)
>>> thursday_fridays_between(a, b)
4
i figure out a method, correct me if i am wrong.
here is my code
from datetime import date, timedelta, datetime
curr = "1-11-2016"
end = "30-11-2016"
format = "%d-%m-%Y"
start_date = datetime.strptime(curr, format)
end_date = datetime.strptime(end, format)
step = timedelta(1)
num_thur_fri = 0
off_days = ['Fri','Thu']
days = (end_date - start_date).days
for x in range(days):
day = start_date.strftime("%a")
print(day)
if day in off_days:
num_thur_fri += 1
start_date += step
print(num_thur_fri)
I have the following code to do this, but how can I do it better? Right now I think it's better than nested loops, but it starts to get Perl-one-linerish when you have a generator in a list comprehension.
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
I'm not actually using this to print. That's just for demo purposes.
The start_date and end_date variables are datetime.date objects because I don't need the timestamps. (They're going to be used to generate a report).
Sample Output
For a start date of 2009-05-30 and an end date of 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
Why are there two nested iterations? For me it produces the same list of data with only one iteration:
for single_date in (start_date + timedelta(n) for n in range(day_count)):
print ...
And no list gets stored, only one generator is iterated over. Also the "if" in the generator seems to be unnecessary.
After all, a linear sequence should only require one iterator, not two.
Update after discussion with John Machin:
Maybe the most elegant solution is using a generator function to completely hide/abstract the iteration over the range of 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: For consistency with the built-in range() function this iteration stops before reaching the end_date. So for inclusive iteration use the next day, as you would with range().
This might be more clear:
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
Use the dateutil library:
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")
This python library has many more advanced features, some very useful, like relative deltas—and is implemented as a single file (module) that's easily included into a project.
Pandas is great for time series in general, and has direct support for date ranges.
import pandas as pd
daterange = pd.date_range(start_date, end_date)
You can then loop over the daterange to print the date:
for single_date in daterange:
print (single_date.strftime("%Y-%m-%d"))
It also has lots of options to make life easier. For example if you only wanted weekdays, you would just swap in bdate_range. See http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps
The power of Pandas is really its dataframes, which support vectorized operations (much like numpy) that make operations across large quantities of data very fast and easy.
EDIT:
You could also completely skip the for loop and just print it directly, which is easier and more efficient:
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())
This function does more than you strictly require, by supporting negative step, etc. As long as you factor out your range logic, then you don't need the separate day_count and most importantly the code becomes easier to read as you call the function from multiple places.
This is the most human-readable solution I can think of.
import datetime
def daterange(start, end, step=datetime.timedelta(1)):
curr = start
while curr < end:
yield curr
curr += step
Numpy's arange function can be applied to 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)
The use of astype is to convert from numpy.datetime64 to an array of datetime.datetime objects.
Why not try:
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
Show the last n days from today:
import datetime
for i in range(0, 100):
print((datetime.date.today() + datetime.timedelta(i)).isoformat())
Output:
2016-06-29
2016-06-30
2016-07-01
2016-07-02
2016-07-03
2016-07-04
For completeness, Pandas also has a period_range function for timestamps that are out of bounds:
import pandas as pd
pd.period_range(start='1/1/1626', end='1/08/1627', freq='D')
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))
for i in range(16):
print datetime.date.today() + datetime.timedelta(days=i)
I have a similar problem, but I need to iterate monthly instead of daily.
This is my 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
Example #1
date_start = datetime(2016, 6, 1)
date_end = datetime(2017, 1, 1)
for p in monthly_range(date_start, date_end):
print(p)
Output
2016-06-01
2016-07-01
2016-08-01
2016-09-01
2016-10-01
2016-11-01
2016-12-01
2017-01-01
Example #2
date_start = datetime(2017, 1, 1)
date_end = datetime(2016, 6, 1)
for p in monthly_range(date_start, date_end):
print(p)
Output
2017-01-01
2016-12-01
2016-11-01
2016-10-01
2016-09-01
2016-08-01
2016-07-01
2016-06-01
You can generate a series of date between two dates using the pandas library simply and trustfully
import pandas as pd
print pd.date_range(start='1/1/2010', end='1/08/2018', freq='M')
You can change the frequency of generating dates by setting freq as D, M, Q, Y
(daily, monthly, quarterly, yearly
)
Using pendulum.period:
import pendulum
start = pendulum.from_format('2020-05-01', 'YYYY-MM-DD', formatter='alternative')
end = pendulum.from_format('2020-05-02', 'YYYY-MM-DD', formatter='alternative')
period = pendulum.period(start, end)
for dt in period:
print(dt.to_date_string())
> 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']
For those who are interested in Pythonic functional way:
from datetime import date, timedelta
from itertools import count, takewhile
for d in takewhile(lambda x: x<=date(2009,6,9), map(lambda x:date(2009,5,30)+timedelta(days=x), count())):
print(d)
What about the following for doing a range incremented by days:
for d in map( lambda x: startDate+datetime.timedelta(days=x), xrange( (stopDate-startDate).days ) ):
# Do stuff here
startDate and stopDate are datetime.date objects
For a generic version:
for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
# Do stuff here
startTime and stopTime are datetime.date or datetime.datetime object
(both should be the same type)
stepTime is a timedelta object
Note that .total_seconds() is only supported after python 2.7 If you are stuck with an earlier version you can write your own function:
def total_seconds( td ):
return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
This function has some extra features:
can pass a string matching the DATE_FORMAT for start or end and it is converted to a date object
can pass a date object for start or end
error checking in case the end is older than the start
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))
Here's code for a general date range function, similar to Ber's answer, but more 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'.")
import datetime
from dateutil.rrule import DAILY,rrule
date=datetime.datetime(2019,1,10)
date1=datetime.datetime(2019,2,2)
for i in rrule(DAILY , dtstart=date,until=date1):
print(i.strftime('%Y%b%d'),sep='\n')
OUTPUT:
2019Jan10
2019Jan11
2019Jan12
2019Jan13
2019Jan14
2019Jan15
2019Jan16
2019Jan17
2019Jan18
2019Jan19
2019Jan20
2019Jan21
2019Jan22
2019Jan23
2019Jan24
2019Jan25
2019Jan26
2019Jan27
2019Jan28
2019Jan29
2019Jan30
2019Jan31
2019Feb01
2019Feb02
from datetime import date,timedelta
delta = timedelta(days=1)
start = date(2020,1,1)
end=date(2020,9,1)
loop_date = start
while loop_date<=end:
print(loop_date)
loop_date+=delta
You can use Arrow:
This is example from the docs, iterating over hours:
from arrow import Arrow
>>> start = datetime(2013, 5, 5, 12, 30)
>>> end = datetime(2013, 5, 5, 17, 15)
>>> for r in Arrow.range('hour', start, end):
... print repr(r)
...
<Arrow [2013-05-05T12:30:00+00:00]>
<Arrow [2013-05-05T13:30:00+00:00]>
<Arrow [2013-05-05T14:30:00+00:00]>
<Arrow [2013-05-05T15:30:00+00:00]>
<Arrow [2013-05-05T16:30:00+00:00]>
To iterate over days, you can use like this:
>>> start = Arrow(2013, 5, 5)
>>> end = Arrow(2013, 5, 5)
>>> for r in Arrow.range('day', start, end):
... print repr(r)
(Didn't check if you can pass datetime.date objects, but anyways Arrow objects are easier in general)
If you are going to use dynamic timedelta then you can use:
1. With while loop
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
while start <= end:
yield start
start += delta
2. With for loop
from datetime import datetime, timedelta
from typing import Generator
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
delta_units = int((end - start) / delta)
for _ in range(delta_units + 1):
yield start
start += delta
3. If you are using async/await
async def datetime_range(start: datetime, end: datetime, delta: timedelta) -> AsyncGenerator[datetime, None]:
delta_units = int((end - start) / delta)
for _ in range(delta_units + 1):
yield start
start += delta
4. List comprehension
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> List[datetime]:
delta_units = int((end - start) / delta)
return [start + (delta * index) for index in range(delta_units + 1)]
Then 1 and 2 solutions simply can be used like this
start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)
result = [time_part for time_part in datetime_range(start, end, delta)]
# or
for time_part in datetime_range(start, end, delta):
print(time_part)
3-third solution can be used like this in async context. Because it retruns an async generator object, which can be used only in async context
start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)
result = [time_part async for time_part in datetime_range(start, end, delta)]
async for time_part in datetime_range(start, end, delta):
print(time_part)
The benefit of the solutions about is that all of them are using dynamic timedelta. This can be very usefull in cases when you do not know which time delta you will have.
Slightly different approach to reversible steps by storing range args in a 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)
I want to create a list of dates, starting with today, and going back an arbitrary number of days, say, in my example 100 days. Is there a better way to do it than this?
import datetime
a = datetime.datetime.today()
numdays = 100
dateList = []
for x in range (0, numdays):
dateList.append(a - datetime.timedelta(days = x))
print dateList
Marginally better...
base = datetime.datetime.today()
date_list = [base - datetime.timedelta(days=x) for x in range(numdays)]
Pandas is great for time series in general, and has direct support for date ranges.
For example pd.date_range():
import pandas as pd
from datetime import datetime
datelist = pd.date_range(datetime.today(), periods=100).tolist()
It also has lots of options to make life easier. For example if you only wanted weekdays, you would just swap in bdate_range.
See date range documentation
In addition it fully supports pytz timezones and can smoothly span spring/autumn DST shifts.
EDIT by OP:
If you need actual python datetimes, as opposed to Pandas timestamps:
import pandas as pd
from datetime import datetime
pd.date_range(end = datetime.today(), periods = 100).to_pydatetime().tolist()
#OR
pd.date_range(start="2018-09-09",end="2020-02-02")
This uses the "end" parameter to match the original question, but if you want descending dates:
pd.date_range(datetime.today(), periods=100).to_pydatetime().tolist()
Get range of dates between specified start and end date (Optimized for time & space complexity):
import datetime
start = datetime.datetime.strptime("21-06-2014", "%d-%m-%Y")
end = datetime.datetime.strptime("07-07-2014", "%d-%m-%Y")
date_generated = [start + datetime.timedelta(days=x) for x in range(0, (end-start).days)]
for date in date_generated:
print date.strftime("%d-%m-%Y")
You can write a generator function that returns date objects starting from today:
import datetime
def date_generator():
from_date = datetime.datetime.today()
while True:
yield from_date
from_date = from_date - datetime.timedelta(days=1)
This generator returns dates starting from today and going backwards one day at a time. Here is how to take the first 3 dates:
>>> import itertools
>>> dates = itertools.islice(date_generator(), 3)
>>> list(dates)
[datetime.datetime(2009, 6, 14, 19, 12, 21, 703890), datetime.datetime(2009, 6, 13, 19, 12, 21, 703890), datetime.datetime(2009, 6, 12, 19, 12, 21, 703890)]
The advantage of this approach over a loop or list comprehension is that you can go back as many times as you want.
Edit
A more compact version using a generator expression instead of a function:
date_generator = (datetime.datetime.today() - datetime.timedelta(days=i) for i in itertools.count())
Usage:
>>> dates = itertools.islice(date_generator, 3)
>>> list(dates)
[datetime.datetime(2009, 6, 15, 1, 32, 37, 286765), datetime.datetime(2009, 6, 14, 1, 32, 37, 286836), datetime.datetime(2009, 6, 13, 1, 32, 37, 286859)]
yeah, reinvent the wheel....
just search the forum and you'll get something like this:
from dateutil import rrule
from datetime import datetime
list(rrule.rrule(rrule.DAILY,count=100,dtstart=datetime.now()))
You can also use the day ordinal to make it simpler:
def date_range(start_date, end_date):
for ordinal in range(start_date.toordinal(), end_date.toordinal()):
yield datetime.date.fromordinal(ordinal)
Or as suggested in the comments you can create a list like this:
date_range = [
datetime.date.fromordinal(ordinal)
for ordinal in range(
start_date.toordinal(),
end_date.toordinal(),
)
]
From the title of this question I was expecting to find something like range(), that would let me specify two dates and create a list with all the dates in between. That way one does not need to calculate the number of days between those two dates, if one does not know it beforehand.
So with the risk of being slightly off-topic, this one-liner does the job:
import datetime
start_date = datetime.date(2011, 1, 1)
end_date = datetime.date(2014, 1, 1)
dates_2011_2013 = [ start_date + datetime.timedelta(n) for n in range(int ((end_date - start_date).days))]
All credits to this answer!
Here's a slightly different answer building off of S.Lott's answer that gives a list of dates between two dates start and end. In the example below, from the start of 2017 to today.
start = datetime.datetime(2017,1,1)
end = datetime.datetime.today()
daterange = [start + datetime.timedelta(days=x) for x in range(0, (end-start).days)]
If there are two dates and you need the range try
from dateutil import rrule, parser
date1 = '1995-01-01'
date2 = '1995-02-28'
datesx = list(rrule.rrule(rrule.DAILY, dtstart=parser.parse(date1), until=parser.parse(date2)))
Based on answers I wrote for myself this:
import datetime;
print [(datetime.date.today() - datetime.timedelta(days=x)).strftime('%Y-%m-%d') for x in range(-5, 0)]
Output:
['2017-12-11', '2017-12-10', '2017-12-09', '2017-12-08', '2017-12-07']
The difference is that I get the 'date' object, not the 'datetime.datetime' one.
A bit of a late answer I know, but I just had the same problem and decided that Python's internal range function was a bit lacking in this respect so I've overridden it in a util module of mine.
from __builtin__ import range as _range
from datetime import datetime, timedelta
def range(*args):
if len(args) != 3:
return _range(*args)
start, stop, step = args
if start < stop:
cmp = lambda a, b: a < b
inc = lambda a: a + step
else:
cmp = lambda a, b: a > b
inc = lambda a: a - step
output = [start]
while cmp(start, stop):
start = inc(start)
output.append(start)
return output
print range(datetime(2011, 5, 1), datetime(2011, 10, 1), timedelta(days=30))
Here is gist I created, from my own code, this might help. (I know the question is too old, but others can use it)
https://gist.github.com/2287345
(same thing below)
import datetime
from time import mktime
def convert_date_to_datetime(date_object):
date_tuple = date_object.timetuple()
date_timestamp = mktime(date_tuple)
return datetime.datetime.fromtimestamp(date_timestamp)
def date_range(how_many=7):
for x in range(0, how_many):
some_date = datetime.datetime.today() - datetime.timedelta(days=x)
some_datetime = convert_date_to_datetime(some_date.date())
yield some_datetime
def pick_two_dates(how_many=7):
a = b = convert_date_to_datetime(datetime.datetime.now().date())
for each_date in date_range(how_many):
b = a
a = each_date
if a == b:
continue
yield b, a
Here's a one liner for bash scripts to get a list of weekdays, this is python 3. Easily modified for whatever, the int at the end is the number of days in the past you want.
python -c "import sys,datetime; print('\n'.join([(datetime.datetime.today() - datetime.timedelta(days=x)).strftime(\"%Y/%m/%d\") for x in range(0,int(sys.argv[1])) if (datetime.datetime.today() - datetime.timedelta(days=x)).isoweekday()<6]))" 10
Here is a variant to provide a start (or rather, end) date
python -c "import sys,datetime; print('\n'.join([(datetime.datetime.strptime(sys.argv[1],\"%Y/%m/%d\") - datetime.timedelta(days=x)).strftime(\"%Y/%m/%d \") for x in range(0,int(sys.argv[2])) if (datetime.datetime.today() - datetime.timedelta(days=x)).isoweekday()<6]))" 2015/12/30 10
Here is a variant for arbitrary start and end dates. not that this isn't terribly efficient, but is good for putting in a for loop in a bash script:
python -c "import sys,datetime; print('\n'.join([(datetime.datetime.strptime(sys.argv[1],\"%Y/%m/%d\") + datetime.timedelta(days=x)).strftime(\"%Y/%m/%d\") for x in range(0,int((datetime.datetime.strptime(sys.argv[2], \"%Y/%m/%d\") - datetime.datetime.strptime(sys.argv[1], \"%Y/%m/%d\")).days)) if (datetime.datetime.strptime(sys.argv[1], \"%Y/%m/%d\") + datetime.timedelta(days=x)).isoweekday()<6]))" 2015/12/15 2015/12/30
A generic method that allows to create date ranges on parameterised window size(day, minute, hour, seconds):
from datetime import datetime, timedelta
def create_date_ranges(start, end, **interval):
start_ = start
while start_ < end:
end_ = start_ + timedelta(**interval)
yield (start_, min(end_, end))
start_ = end_
Tests:
def main():
tests = [
('2021-11-15:00:00:00', '2021-11-17:13:00:00', {'days': 1}),
('2021-11-15:00:00:00', '2021-11-16:13:00:00', {'hours': 12}),
('2021-11-15:00:00:00', '2021-11-15:01:45:00', {'minutes': 30}),
('2021-11-15:00:00:00', '2021-11-15:00:01:12', {'seconds': 30})
]
for t in tests:
print("\nInterval: %s, range(%s to %s)" % (t[2], t[0], t[1]))
start = datetime.strptime(t[0], '%Y-%m-%d:%H:%M:%S')
end = datetime.strptime(t[1], '%Y-%m-%d:%H:%M:%S')
ranges = list(create_date_ranges(start, end, **t[2]))
x = list(map(
lambda x: (x[0].strftime('%Y-%m-%d:%H:%M:%S'), x[1].strftime('%Y-%m-%d:%H:%M:%S')),
ranges
))
print(x)
main()
Test output:
Interval: {'days': 1}, range(2021-11-15:00:00:00 to 2021-11-17:13:00:00)
[('2021-11-15:00:00:00', '2021-11-16:00:00:00'), ('2021-11-16:00:00:00', '2021-11-17:00:00:00'), ('2021-11-17:00:00:00', '2021-11-17:13:00:00')]
Interval: {'hours': 12}, range(2021-11-15:00:00:00 to 2021-11-16:13:00:00)
[('2021-11-15:00:00:00', '2021-11-15:12:00:00'), ('2021-11-15:12:00:00', '2021-11-16:00:00:00'), ('2021-11-16:00:00:00', '2021-11-16:12:00:00'), ('2021-11-16:12:00:00', '2021-11-16:13:00:00')]
Interval: {'minutes': 30}, range(2021-11-15:00:00:00 to 2021-11-15:01:45:00)
[('2021-11-15:00:00:00', '2021-11-15:00:30:00'), ('2021-11-15:00:30:00', '2021-11-15:01:00:00'), ('2021-11-15:01:00:00', '2021-11-15:01:30:00'), ('2021-11-15:01:30:00', '2021-11-15:01:45:00')]
Interval: {'seconds': 30}, range(2021-11-15:00:00:00 to 2021-11-15:00:01:12)
[('2021-11-15:00:00:00', '2021-11-15:00:00:30'), ('2021-11-15:00:00:30', '2021-11-15:00:01:00'), ('2021-11-15:00:01:00', '2021-11-15:00:01:12')]
Matplotlib related
from matplotlib.dates import drange
import datetime
base = datetime.date.today()
end = base + datetime.timedelta(days=100)
delta = datetime.timedelta(days=1)
l = drange(base, end, delta)
I know this has been answered, but I'll put down my answer for historical purposes, and since I think it is straight forward.
import numpy as np
import datetime as dt
listOfDates=[date for date in np.arange(firstDate,lastDate,dt.timedelta(days=x))]
Sure it won't win anything like code-golf, but I think it is elegant.
Another example that counts forwards or backwards, starting from Sandeep's answer.
from datetime import date, datetime, timedelta
from typing import Sequence
def range_of_dates(start_of_range: date, end_of_range: date) -> Sequence[date]:
if start_of_range <= end_of_range:
return [
start_of_range + timedelta(days=x)
for x in range(0, (end_of_range - start_of_range).days + 1)
]
return [
start_of_range - timedelta(days=x)
for x in range(0, (start_of_range - end_of_range).days + 1)
]
start_of_range = datetime.today().date()
end_of_range = start_of_range + timedelta(days=3)
date_range = range_of_dates(start_of_range, end_of_range)
print(date_range)
gives
[datetime.date(2019, 12, 20), datetime.date(2019, 12, 21), datetime.date(2019, 12, 22), datetime.date(2019, 12, 23)]
and
start_of_range = datetime.today().date()
end_of_range = start_of_range - timedelta(days=3)
date_range = range_of_dates(start_of_range, end_of_range)
print(date_range)
gives
[datetime.date(2019, 12, 20), datetime.date(2019, 12, 19), datetime.date(2019, 12, 18), datetime.date(2019, 12, 17)]
Note that the start date is included in the return, so if you want four total dates, use timedelta(days=3)
from datetime import datetime , timedelta, timezone
start_date = '2022_01_25'
end_date = '2022_01_30'
start = datetime.strptime(start_date, "%Y_%m_%d")
print(type(start))
end = datetime.strptime(end_date, "%Y_%m_%d")
##pDate = str(pDate).replace('-', '_')
number_of_days = (end - start).days
print("number_of_days: ", number_of_days)
##
date_list = []
for day in range(number_of_days):
a_date = (start + timedelta(days = day)).astimezone(timezone.utc)
a_date = a_date.strftime('%Y-%m-%d')
date_list.append(a_date)
print(date_list)
A monthly date range generator with datetime and dateutil. Simple and easy to understand:
import datetime as dt
from dateutil.relativedelta import relativedelta
def month_range(start_date, n_months):
for m in range(n_months):
yield start_date + relativedelta(months=+m)
import datetime
def date_generator():
cur = base = datetime.date.today()
end = base + datetime.timedelta(days=100)
delta = datetime.timedelta(days=1)
while(end>base):
base = base+delta
print base
date_generator()
from datetime import datetime, timedelta
from dateutil import parser
def getDateRange(begin, end):
""" """
beginDate = parser.parse(begin)
endDate = parser.parse(end)
delta = endDate-beginDate
numdays = delta.days + 1
dayList = [datetime.strftime(beginDate + timedelta(days=x), '%Y%m%d') for x in range(0, numdays)]
return dayList
From above answers i created this example for date generator
import datetime
date = datetime.datetime.now()
time = date.time()
def date_generator(date, delta):
counter =0
date = date - datetime.timedelta(days=delta)
while counter <= delta:
yield date
date = date + datetime.timedelta(days=1)
counter +=1
for date in date_generator(date, 30):
if date.date() != datetime.datetime.now().date():
start_date = datetime.datetime.combine(date, datetime.time())
end_date = datetime.datetime.combine(date, datetime.time.max)
else:
start_date = datetime.datetime.combine(date, datetime.time())
end_date = datetime.datetime.combine(date, time)
print('start_date---->',start_date,'end_date---->',end_date)
I thought I'd throw in my two cents with a simple (and not complete) implementation of a date range:
from datetime import date, timedelta, datetime
class DateRange:
def __init__(self, start, end, step=timedelta(1)):
self.start = start
self.end = end
self.step = step
def __iter__(self):
start = self.start
step = self.step
end = self.end
n = int((end - start) / step)
d = start
for _ in range(n):
yield d
d += step
def __contains__(self, value):
return (
(self.start <= value < self.end) and
((value - self.start) % self.step == timedelta(0))
)