Basj
Basj

Reputation: 46411

All tuesdays and wednesdays in a date range: is there a more pythonic way?

I'd like to find all the tuesdays and wednesdays (as datetime object) between 2015-11-02 and 2015-12-14. This works:

from datetime import datetime, timedelta

l = []
for i in range(100):
   d = datetime(2015,11,2) + timedelta(days=i)
   if d > datetime(2015,12,14):
       break
   if d.weekday() == 1 or d.weekday() == 2:   # tuesday or wednesday
       l.append(d)
print l   

[datetime.datetime(2015, 11, 3, 0, 0), datetime.datetime(2015, 11, 4, 0, 0), datetime.datetime(2015, 11, 10, 0, 0), datetime.datetime(2015, 11, 11, 0, 0), datetime.datetime(2015, 11, 17, 0, 0), datetime.datetime(2015, 11, 18, 0, 0), datetime.datetime(2015, 11, 24, 0, 0), datetime.datetime(2015, 11, 25, 0, 0), datetime.datetime(2015, 12, 1, 0, 0), datetime.datetime(2015, 12, 2, 0, 0), datetime.datetime(2015, 12, 8, 0, 0), datetime.datetime(2015, 12, 9, 0, 0)]

Is there a more pythonic way to do it?

Upvotes: 6

Views: 2716

Answers (5)

 date_range=pd.date_range(start="2015-11-02", end="2015-12-14",freq="D")
 date_range=[x for x in [x if x.dayofweek in [2,3] else None for x in date_range] if x]

 print(date_range)

output

 [Timestamp('2015-11-04 00:00:00', freq='D'), 
  Timestamp('2015-11-05 00:00:00', freq='D'), 
  Timestamp('2015-11-11 00:00:00', freq='D'), 
  Timestamp('2015-11-12 00:00:00', freq='D'), 
  Timestamp('2015-11-18 00:00:00', freq='D'), 
  Timestamp('2015-11-19 00:00:00', freq='D'), 
  Timestamp('2015-11-25 00:00:00', freq='D'), 
  Timestamp('2015-11-26 00:00:00', freq='D'), 
  Timestamp('2015-12-02 00:00:00', freq='D'), 
  Timestamp('2015-12-03 00:00:00', freq='D'), 
  Timestamp('2015-12-09 00:00:00', freq='D'), 
  Timestamp('2015-12-10 00:00:00', freq='D')]

Upvotes: 0

Padraic Cunningham
Padraic Cunningham

Reputation: 180391

from datetime import datetime, timedelta

start, end = datetime(2015, 11, 2), datetime(2015, 12, 14)
days = (start + timedelta(days=i) for i in range((end - start).days + 1))
l = [d for d in days if d.weekday() in [1,2] ]

This will be a lot faster if you are going over a long span:

def helper(d, i, inc):
    while d.weekday() != i:
        d += timedelta(days=inc)
    return d.replace(hour=0, minute=0, second=0, microsecond=0)


start, end = datetime(2015, 11, 02), datetime(2015,12, 14)


def find_days(st, end, d1, d2):
    if st >= end:
        raise ValueError("Start must be before end")
    else:
        _st, _end = helper(st, d1, inc=-1), helper(end, d2, 1)
        secs = (_end - _st).total_seconds() // 86400
        if st.weekday() == d2:
            yield st
        for i in range(int(secs / 7) + 1):
            if st <= _st <= end:
                yield _st
                nxt = _st + timedelta(days=1)
                if nxt <= end:
                    yield nxt
            _st += timedelta(days=7)
    if _st <= end:
        yield _st
    from pprint import pprint as pp



from pprint import pprint as pp


pp(list(find_days(start, end, 1, 2)))

Output:

[datetime.datetime(2015, 11, 3, 0, 0),
 datetime.datetime(2015, 11, 4, 0, 0),
 datetime.datetime(2015, 11, 10, 0, 0),
 datetime.datetime(2015, 11, 11, 0, 0),
 datetime.datetime(2015, 11, 17, 0, 0),
 datetime.datetime(2015, 11, 18, 0, 0),
 datetime.datetime(2015, 11, 24, 0, 0),
 datetime.datetime(2015, 11, 25, 0, 0),
 datetime.datetime(2015, 12, 1, 0, 0),
 datetime.datetime(2015, 12, 2, 0, 0),
 datetime.datetime(2015, 12, 8, 0, 0),
 datetime.datetime(2015, 12, 9, 0, 0)]

This does what dateutil does and does it faster:

In [12]: def dte():
....:         results = rrule(DAILY,
....:                 dtstart = dt.datetime(2015,11, 2),
....:                 until = end,
....:                 byweekday=(TU, WE),
....:         )
....:         return list(results)
....: 


In [38]: start, end = datetime(2015, 11, 2), datetime(2100, 11, 14)

In [39]: for i in range(600):
             end += timedelta(days=1)
             assert dte() == list(find_days(start, end,1,2 ))
   ....:     

In [40]: start, end = datetime(2015, 11, 2), datetime(2017, 11, 14)


In [41]: timeit  [d for d in date_range(start, end) if d.weekday() in (1, 2)]
10 loops, best of 3: 62.1 ms per loop

In [42]: timeit list(find_days(start, end, 1, 2))
100 loops, best of 3: 8.11 ms per loop

In [43]: timeit dte()
10 loops, best of 3: 131 ms per loop

Upvotes: 6

Basj
Basj

Reputation: 46411

As I already use pandas, this works:

import pandas as pd
print [d for d in pd.date_range(start="2015-11-02", end="2015-12-14") if d.weekday() in [1,2]]

Upvotes: 5

dawg
dawg

Reputation: 103754

If you have a simple data range (similar to Python's range function):

import datetime as dt

def date_range(d1, d2):
    d=d1
    while d<=d2:
        yield d
        d+=dt.timedelta(days=1)  

Then you can use a simple list comprehension:

>>> '\n'.join([d.isoformat() for d in date_range(dt.date(2015,11,2),dt.date(2015,12,14)) if d.weekday() in (1,2)])
2015-11-03
2015-11-04
2015-11-10
2015-11-11
2015-11-17
2015-11-18
2015-11-24
2015-11-25
2015-12-01
2015-12-02
2015-12-08
2015-12-09

If you are concerned that it is wasteful iterating day by day, time it:

$ python -m timeit '
> import datetime as dt
> def date_range(d1, d2, step=1):
>     d=d1
>     while True:
>         if d+dt.timedelta(days=step)>d2:
>             break
>         yield d
>         d+=dt.timedelta(days=step)  
> [d.isoformat() for d in date_range(dt.date(1815,11,2),dt.date(2215,12,14)) if d.weekday() in (1,2)]
> '
10 loops, best of 3: 214 msec per loop

Calculating 400 years of Tuesdays and Wednesdays takes 1/4 second and about 100 millisec for a 1 year range. Cheers.

Upvotes: 4

7stud
7stud

Reputation: 48599

Here it is with the third party module python-dateutils:

from dateutil.rrule import rrule, DAILY, TU, WE
import datetime as dt

results = rrule(DAILY,
        dtstart = dt.datetime(2015,11,2),
        until = dt.datetime(2015, 12, 14),
        byweekday = (TU, WE),
)


for result in results:
    print(result)

--output:--
2015-11-03 00:00:00
2015-11-04 00:00:00
2015-11-10 00:00:00
2015-11-11 00:00:00
2015-11-17 00:00:00
2015-11-18 00:00:00
2015-11-24 00:00:00
2015-11-25 00:00:00
2015-12-01 00:00:00
2015-12-02 00:00:00
2015-12-08 00:00:00
2015-12-09 00:00:00

Upvotes: 5

Related Questions