584

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
Thomas Browne
  • 23,824
  • 32
  • 78
  • 121

23 Answers23

707

Marginally better...

base = datetime.datetime.today()
date_list = [base - datetime.timedelta(days=x) for x in range(numdays)]
Ohad Eytan
  • 8,114
  • 1
  • 22
  • 31
S.Lott
  • 384,516
  • 81
  • 508
  • 779
442

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()
Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
fantabolous
  • 21,470
  • 7
  • 54
  • 51
  • ...and if you want to convert the data range into a `DataFrame` (since it's a `DatetimeIndex`), you can just use the `to_frame()` method: ```pd.date_range(start='2018-09-09', end='2020-02-02').to_frame(name='dates')``` – Bilbottom Jun 23 '22 at 05:58
  • Any idea why `pd.date_range` is going to be deprecated if there isn't an equivalent method in Python datetime? `FutureWarning: The pandas.datetime class is deprecated and will be removed from pandas in a future version.` – Bill Dec 20 '22 at 17:58
  • Yeah, I was wondering why there isn''d a strictly datetime solution to this, but I can't find this same fucntion in datetime at all. – bart cubrich Feb 06 '23 at 23:54
  • @Bill I don't get that warning for `pd.date_range` on pandas 1.5.3; maybe you're using `pd.datetime` somewhere? See https://stackoverflow.com/a/60856931/3427777 – fantabolous Apr 27 '23 at 23:40
  • @fantabolous, yeah I don't know what I was doing there. I think I was confused. Please ignore my comment. – Bill Apr 28 '23 at 04:15
139

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")
HpTerm
  • 8,151
  • 12
  • 51
  • 67
Sandeep
  • 28,307
  • 3
  • 32
  • 24
50

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)]
Ayman Hourieh
  • 132,184
  • 23
  • 144
  • 116
42

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()))
BOFH
  • 471
  • 4
  • 2
  • 31
    Requires http://labix.org/python-dateutil. Looks nice but hardly worth an external dependency just to save one line. – Beni Cherniavsky-Paskin Jul 11 '12 at 16:31
  • 1
    Can't believe anyone things the other answers are very pythonic. While rrule is a terrible name, this one is at least easy on the eyes. – boatcoder Sep 23 '15 at 11:44
  • 9
    @BeniCherniavsky-Paskin: it is *very* easy to introduce subtle bugs while implementing period (calendar) arithmetic. `dateutil.rrule` implements [iCalendar RFC](https://tools.ietf.org/html/rfc5545) -- it is easier to use functions with a well-defined behavior instead of multiple implementations of almost the same functionality that are ever so slightly different. `dateutil.rrule` allows to limit bug fixing to a single place. – jfs Sep 28 '15 at 17:37
  • 5
    @Mark0978: `rrule` name is not arbitrary; it is from [the corresponding rfc](https://tools.ietf.org/html/rfc5545) – jfs Sep 28 '15 at 17:37
  • 1
    Yea, I wasn't blaming dateutil for the unfortunate choice of names :-) – boatcoder Sep 29 '15 at 14:28
  • 1
    Granted in general, but for the OP's simple goal of jumping exactly N days, date + timedelta as used in most solutions sounds like a well-defined way to express it. I'd love to learn of an actual bug. – Beni Cherniavsky-Paskin Sep 30 '15 at 22:11
  • 2
    @BeniCherniavsky-Paskin https://gist.github.com/Asday/be44c79fa5ead8461e8da8da2b93c30e there's your bug. That date doesn't exist. – Adam Barnes Feb 28 '20 at 10:43
  • I take back my initial sentiment. Using _only_ stdlib is impractical because it doesn't include a timezone database. And I suspect that's a large part of the weight of dateutil and similar libs? – Beni Cherniavsky-Paskin Mar 08 '20 at 14:58
41

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(),
    )
]
Hosane
  • 915
  • 9
  • 19
28

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!

Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
snake_charmer
  • 2,845
  • 4
  • 26
  • 39
  • 2
    That is not a one liner any more than many of the other answers to the question. – Thomas Browne Apr 16 '16 at 16:48
  • 11
    I have never pretended that this was more a one-liner than the others answers, neither that this was a better solution. I showed a piece of code that does something slightly different than what you asked for but that I would have been glad to find here given the title of the question. – snake_charmer Apr 16 '16 at 17:43
14

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)]
Derek Powell
  • 503
  • 5
  • 8
11

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)))
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Nelu
  • 151
  • 1
  • 6
9

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.

bfontaine
  • 18,169
  • 13
  • 73
  • 107
wmlynarski
  • 516
  • 5
  • 8
8

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))
Rob Young
  • 1,235
  • 11
  • 19
5

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
roopesh
  • 1,636
  • 2
  • 13
  • 16
4

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
Tim P
  • 415
  • 3
  • 11
  • Might want to update your answer post with the code in your comment. Python gets mangled in comments, and kinda needs the formatting. – Jake Bathman Dec 30 '15 at 16:50
4

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.

  • The `arange` with the steps is quite nice, but `listOfDates` consists of [numpy datetime64](https://docs.scipy.org/doc/numpy/reference/arrays.datetime.html) instead of python native datetimes. – F.Raab Aug 29 '18 at 16:27
  • 2
    However you can use `np.arange(…).astype(dt.datetime)` to make `arange` return native python datetime instead of numpy datetime64. – F.Raab Aug 29 '18 at 16:35
4

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')]
kundan
  • 1,278
  • 14
  • 27
3

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)
milton
  • 988
  • 6
  • 19
3

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)

Chad Lowe
  • 721
  • 1
  • 6
  • 12
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)
cdmh
  • 3,294
  • 2
  • 26
  • 41
  • 1
    This is just a very verbose version of the main answer https://stackoverflow.com/a/993367/122792 with semantically almost no difference, however upvoting you for effort! Please also note that `numdays` is given and therefore your `number_of_days` derived from two dates diverges from the exact requirements of the question. – Thomas Browne Feb 02 '22 at 16:20
2

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)
AidinZadeh
  • 724
  • 1
  • 8
  • 15
0
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()
Sasmita
  • 11
  • 1
    He wanted to go back, not forward.. So the `end` should be `base - datetime.timedelta`. Moreover... Why is this solution better than the original one? – frarugi87 Jul 16 '15 at 11:31
0
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
Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92
xmduhan
  • 965
  • 12
  • 14
0

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)
Dimitris Kougioumtzis
  • 2,339
  • 1
  • 25
  • 36
0

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))
        )
Yuval
  • 3,207
  • 32
  • 45