Get the week numbers between two dates with python - python

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.

Related

Get Dates for Last Week in Python

Ok I'm trying to get the dates for the previous week in Python assuming the week starts on Sunday and Ends on Saturday.
Through google and trial and error, I have this:
def get_week_days(year, week):
d = date(year,1,1)
if(d.weekday()>3):
d = d+timedelta(7-d.weekday())
else:
d = d - timedelta(d.weekday())
dlt = timedelta(days = (week-1)*7)
FH_start = d + dlt
FH_end = d + dlt + timedelta(days=6)
return FH_start, FH_end
print get_week_days(2018, this_week_int)
and that outputs:
(datetime.date(2018, 3, 5), datetime.date(2018, 3, 11))
How do I offset it to have the week start on Sunday, or is there a better way to write this?
Here's one simple version:
from datetime import date, timedelta
def get_week_days(year, week):
year_start = date(year, 1, 1)
# the following line assumes your this_week_int starts at 0
# if this_week_int starts at 1, change to week-2
week_start = year_start + timedelta(days=-year_start.isoweekday(), weeks=week-1)
week_end = week_start + timedelta(days=6)
return week_start, week_end
get_week_days(2018, 1)
# (datetime.date(2017, 12, 31), datetime.date(2018, 1, 6))
get_week_days(2018, 10)
# (datetime.date(2018, 3, 4), datetime.date(2018, 3, 10))
Essentially, .isoweekday() returns 1 for Monday and 7 for Sundays, so when you deduct that weekday it gives you the start at Sunday.
you can go as follows:
from datetime import timedelta
def week_range(date):
year, week, dow = date.isocalendar()
# Find the first day of the week.
if dow == 7:
# Since we want to start with Sunday, let's test for that condition.
start_date = date
else:
# Otherwise, subtract `dow` number days to get the first day
start_date = date - timedelta(dow)
# Now, add 6 for the last day of the week (i.e., count up to Saturday)
end_date = start_date + timedelta(6)
return (start_date, end_date)
Now you have start and end date of week you can find out week dates as you want. For more you can refer this

Python create Ordered Dict with 30 most recent day keys [duplicate]

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)

How can I get the 3rd Friday of a month in Python?

I'm trying to get stock data from Yahoo! Finance using Python 2.7.9, but I only need data for the 3rd Friday of the month. I have a function to get the data, but need a way to get the dates. I want something like this:
def get_third_fris(how_many):
# code and stuff
return list_of_fris
So that calling get_third_fris(6) will return a 6-item-long list of 3rd Fridays following the current date. The dates need to be Unix timestamps.
(I have pretty much no experience with time or datetime, so please explain what your code is doing.)
Thanks!
You can use the calendar module to list weeks, then grab the Friday of that week.
import calendar
c = calendar.Calendar(firstweekday=calendar.SUNDAY)
year = 2015; month = 2
monthcal = c.monthdatescalendar(year,month)
third_friday = [day for week in monthcal for day in week if \
day.weekday() == calendar.FRIDAY and \
day.month == month][2]
You can format to Unix timestamp, but it's non-trivial. I'll refer you to this excellent answer which has info based on whether or not your date is timezone-aware.
We do not need to import anything other than datetime. We can assume 7 days in a week and weekday 0 == Monday.
import datetime
def third_friday(year, month):
"""Return datetime.date for monthly option expiration given year and
month
"""
# The 15th is the lowest third day in the month
third = datetime.date(year, month, 15)
# What day of the week is the 15th?
w = third.weekday()
# Friday is weekday 4
if w != 4:
# Replace just the day (of month)
third = third.replace(day=(15 + (4 - w) % 7))
return third
Assuming you want a range of every 3rd Friday, you can just use pandas, sample code:
import pandas as pd
pd.date_range('2017-12-02','2020-08-31',freq='WOM-3FRI')
Output:
DatetimeIndex(['2017-12-15', '2018-01-19', '2018-02-16', '2018-03-16',
'2018-04-20', '2018-05-18', '2018-06-15', '2018-07-20',
'2018-08-17', '2018-09-21', '2018-10-19', '2018-11-16',
'2018-12-21', '2019-01-18', '2019-02-15', '2019-03-15',
'2019-04-19', '2019-05-17', '2019-06-21', '2019-07-19',
'2019-08-16', '2019-09-20', '2019-10-18', '2019-11-15',
'2019-12-20', '2020-01-17', '2020-02-21', '2020-03-20',
'2020-04-17', '2020-05-15', '2020-06-19', '2020-07-17',
'2020-08-21'],
dtype='datetime64[ns]', freq='WOM-3FRI')
You can use standard python functions to find the third friday of this month:
from datetime import timedelta, date
import calendar
def next_third_friday(d):
""" Given a third friday find next third friday"""
d += timedelta(weeks=4)
return d if d.day >= 15 else d + timedelta(weeks=1)
def third_fridays(d, n):
"""Given a date, calculates n next third fridays"""
# Find closest friday to 15th of month
s = date(d.year, d.month, 15)
result = [s + timedelta(days=(calendar.FRIDAY - s.weekday()) % 7)]
# This month's third friday passed. Find next.
if result[0] < d:
result[0] = next_third_friday(result[0])
for i in range(n - 1):
result.append(next_third_friday(result[-1]))
return result
We can apply the above function to get the timestamps of the next fridays:
import time
def timestamp(d):
return int(time.mktime(d.timetuple()))
fridays = third_fridays(date.today(), 2)
print(fridays)
print(map(timestamp, fridays))
Output:
[datetime.date(2015, 3, 20), datetime.date(2015, 4, 17)]
[1426802400, 1429218000]
How about a more straightforward answer:
import calendar
c = calendar.Calendar(firstweekday=calendar.SATURDAY)
monthcal = c.monthdatescalendar(my_year, my_month)
monthly_expire_date = monthcal[2][-1]
I generalized #pourhaus answer to find the nth day of any month:
def nth_day_of_month(month, year, day_of_week, n):
first_possible_day = {1: 1, 2: 8, 3: 15, 4: 22, 5: 29}[n]
d = datetime.date(year, month, first_possible_day)
w = d.weekday()
if w != day_of_week:
d = d.replace(day=(first_possible_day + (day_of_week - w) % 7))
return d
its easy to use dateutil to get the next friday
import dateutil.parser as dparse
from datetime import timedelta
next_friday = dparse.parse("Friday")
one_week = timedelta(days=7)
friday_after_next = next_friday + one_week
last_friday = friday_after_next + one_week
this leverages the fact that there is always a week between fridays ... although Im not sure this answers your question it should at the very least provide you with a good starting point
Using dateutil.relativedelta:
from dateutil.relativedelta import relativedelta, FR # $ pip install python-dateutil
def third_friday_dateutil(now):
"""the 3rd Friday of the month, not the 3rd Friday after today."""
now = now.replace(day=1) # 1st day of the month
now += relativedelta(weeks=2, weekday=FR)
return now
Or using dateutil.rrule:
from datetime import date, timedelta
from dateutil.rrule import rrule, MONTHLY, FR
def third_friday_rrule(now):
return rrule(MONTHLY, count=1, byweekday=FR, bysetpos=3, dtstart=now.replace(day=1))[0]
def get_third_fris_rrule(how_many):
return list(rrule(MONTHLY, count=how_many, byweekday=FR, bysetpos=3, dtstart=date.today()+timedelta(1)))
Here's a brute force solution (15x times faster):
#!/usr/bin/env python
import calendar
from datetime import date, timedelta
from itertools import islice
DAY = timedelta(1)
WEEK = 7*DAY
def fridays(now):
while True:
if now.weekday() == calendar.FRIDAY:
while True:
yield now
now += WEEK
now += DAY
def next_month(now):
"""Return the first date that is in the next month."""
return (now.replace(day=15) + 20*DAY).replace(day=1)
def third_friday_brute_force(now):
"""the 3rd Friday of the month, not the 3rd Friday after today."""
return next(islice(fridays(now.replace(day=1)), 2, 3))
def get_third_fris(how_many):
result = []
now = date.today()
while len(result) < how_many:
fr = third_friday_brute_force(now)
if fr > now: # use only the 3rd Friday after today
result.append(fr)
now = next_month(now)
return result
print(get_third_fris(6))
Output
[datetime.date(2015, 3, 20),
datetime.date(2015, 4, 17),
datetime.date(2015, 5, 15),
datetime.date(2015, 6, 19),
datetime.date(2015, 7, 17),
datetime.date(2015, 8, 21)]
See Converting datetime.date to UTC timestamp in Python
Here's comparison with other solutions and tests (for all possible 400 years patterns).
I generalized my answer so that anyone can use it for any Nth weekday of a month and using minimal default libraries. My use was to find the DST (daylight savings time) dates for the year (2nd sunday in March & 1st sunday in November).
# Libraries:
from datetime import datetime
# Function:
def get_nth_day_of_month(year, month, Nth, weekday):
# Process is to find out what weekday the 1st of the month is
# And then go straight to the desired date by calculating it
first_of_month_weekday = datetime(year, month, 1).weekday()
day_desired = 7 * (Nth-1) + (weekday - first_of_month_weekday)
if day_desired < 1 : day_desired += 7 #correction for some 1st-weekday situations
return datetime(year, month, day_desired)
# Config:
year = 2022
month = 3 #DST starts in March
weekday = 6 #sunday
Nth = 2 #2nd sunday
dst_start = get_nth_day_of_month(year, month, Nth, weekday)
For my case, this generates the start of DST this year:
In [2]: dst_start
Out [2]: datetime.datetime(2022, 3, 13, 0, 0)
Then for the end of DST in 2022:
month = 11
Nth = 1
dst_end = get_nth_day_of_month(year, month, Nth, weekday)
The result is:
In[4]: dst_end
Out[4]: datetime.datetime(2022, 11, 5, 0, 0)
So in 2022, DST runs from 2022-03-13 to 2022-11-05.
Standard:
Days are numbered Monday = 0 to Sunday = 6
Pure python with no external libs.
Returns the expected day-of-month.
Note: Based on answer from #autonopy, but works.
from datetime import datetime
def get_nth_day_of_month(year, month, Nth, weekday):
first_of_month_weekday = datetime(year, month, 1).weekday()
# Find weekday offset from beginning of month
day_offset = (weekday - first_of_month_weekday) + 1
if day_offset < 1:
day_offset += 7 # correction for some 1st-weekday situations
# Add N weeks
return 7 * (Nth - 1) + day_offset
Tests:
>>> # first Monday of Nov 2021
>>> get_nth_day_of_month(2021, 11, 1, 0)
1
>>> # first Monday of January 2022
>>> get_nth_day_of_month(2022, 1, 1, 0)
3
>>> # first Monday of May 2022
>>> get_nth_day_of_month(2022, 5, 1, 0)
2
>>> # Mother's day 2022
>>> get_nth_day_of_month(2022, 5, 2, 0)
9
Assuming you use pandas:
def exp_friday(df):
mask = np.where((df.index.day > 14) &
(df.index.day < 22) &
(df.index.dayofweek == 4), True, False)
return df[mask]
This is a generic function to give you all the dates of a specific week in a list form.
def frecuencia_daymng(self, start_day, year, month, dayofweek):
"""dayofweek starts on MONDAY in 0 index"""
c = calendar.Calendar(firstweekday=start_day)
monthcal = c.monthdatescalendar(year, month)
ldates = []
for tdate in monthcal:
if tdate[dayofweek].month == month:
ldates.append(tdate[dayofweek])
return ldates
Lets say you want all the mondays of the 2020 10.
frecuencia_daymng(calendar.MONDAY, 2020, 10, 0)
This will give you the output.
[datetime.date(2020, 10, 5),
datetime.date(2020, 10, 12),
datetime.date(2020, 10, 19),
datetime.date(2020, 10, 26)]
So now you have the first, second ... etc monday of the month.
My suggestion is to start with the first day of the month, then find the closest Friday.
4 is represented as Friday from the datetime.weekday() method.
So we then subtract the weekday of the first of the month from 4(Friday)
If the result is negative the closest Friday found was the previous month, so we add 7 days, otherwise we already have the first Friday.
Then the result is as simple as adding another 14 days to get the third Friday and then add the timedelta representing the third Friday to the first day of the month.
from datetime import datetime, timedelta
def get_third_friday(year, month):
first_day_of_month = datetime(year, month, 1)
closest_friday = 4 - first_day_of_month.weekday()
if closest_friday < 0:
first_friday = closest_friday + 7
else:
first_friday = closest_friday
third_friday = first_friday + 14
return first_day_of_month + timedelta(third_friday)
Here's a solution where someone has figured it out already: the relativedelta module that's an extension from the Python dateutil package (pip install python-dateutil).
import datetime
from dateutil import relativedelta
def third_fridays(n):
first_of_this_month = datetime.date.today().replace(day=1)
return (
first_of_this_month
+ relativedelta.relativedelta(weekday=relativedelta.FR(3), months=i)
for i in range(n)
)
The key part here of course is the weekday=relativedelta.FR(3) which says exactly what's needed: the third Friday of the month. Here are the relevant part of the docs for the weekday parameter,
weekday:
One of the weekday instances (MO, TU, etc) available in the
relativedelta module. These instances may receive a parameter N,
specifying the Nth weekday, which could be positive or negative
(like MO(+1) or MO(-2)).
(For those new to Python return (...) is a generator expression which you can just treat as something to iterate over, e.g., for friday in third_fridays(18): print(friday))
from dateutil.relativedelta import *
from datetime import *
def find_mth_friday(your_date,m):
mth_friday = your_date + relativedelta(day=1, weekday=FR(m)) #sets day=1 in your_date and adds m fridays to it.
mth_friday_timestamp = int(mth_friday.strftime("%s")) #converting datetime to unix timestamp
return mth_friday_timestamp
def get_third_fris(n):
output_timestamps = []
today = datetime.now() #gets current system date
for i in range(1,n+1): #value of i varies from 1 to 6 if n=6
next_month = today + relativedelta(months=+i) #adds i months to current system date
third_friday = find_mth_friday(next_month,3) #finds third friday of the month using 'find_mth_friday()', the function we defined
output_timestamps.append(third_friday)
return output_timestamps
print(get_third_fris(6)) #let's try invoking our function with n=6 dates
This is what you wanted right?

Iterating through a range of dates in Python

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)

Creating a range of dates in Python

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))
)

Categories