7

Consider the following list of day-of-week-hour pairs in 24H format:

{
 'Mon': [9,23],
 'Thu': [12, 13, 14],
 'Tue': [11, 12, 14],
 'Wed': [11, 12, 13, 14]
 'Fri': [13],
 'Sat': [],
 'Sun': [],
}

and two time points, e.g.:

  • Start:

    datetime.datetime(2015, 7, 22, 17, 58, 54, 746784)
    
  • End:

    datetime.datetime(2015, 8, 30, 10, 22, 36, 363912)
    

Say we need to know how many hours there are between these two datetimes (either rounding up or down) for each of the day-of-week-hour pairs specified above.

How can I approach this problem in Python? I explored timedelta and relativedelta in a general level of detail but I didn't find anything that provides something close to this.

For simplicity, we can assume that everything refers to the same timezone.


Perhaps a simpler problem is to focus on a single day-hour pair, e.g. How many Wednesdays: 14 are there between two arbitrary datetimes?

Amelio Vazquez-Reina
  • 91,494
  • 132
  • 359
  • 564

5 Answers5

4

Perhaps something like this:

from calendar import day_abbr
from datetime import datetime, timedelta


def solve(start, end, data):
    days = list(day_abbr)
    output = dict.fromkeys(days, 0)

    while start <= end:
        day = days[start.weekday()]
        if start.hour in data[day]:
            output[day] += 1
        start = start + timedelta(minutes=60)

    return output


data = {
 'Mon': [9, 23],
 'Thu': [12, 13, 14],
 'Tue': [11, 12, 14],
 'Wed': [11, 12, 13, 14],
 'Fri': [13],
 'Sat': [],
 'Sun': [],
}

start = datetime(2015, 7, 22, 17, 58, 54, 746784)
end = datetime(2015, 8, 30, 10, 22, 36, 363912)

print solve(start, end, data)
# {'Wed': 20, 'Sun': 0, 'Fri': 6, 'Tue': 15, 'Mon': 10, 'Thu': 18, 'Sat': 0} 

Getting count for each day by hour:

from calendar import day_abbr
from collections import defaultdict
from datetime import datetime, timedelta
from pprint import pprint


def solve(start, end, data):
    days = list(day_abbr)
    output = defaultdict(lambda: defaultdict(int))

    while start <= end:
        day = days[start.weekday()]
        if start.hour in data[day]:
            output[day][start.hour] += 1
        start = start + timedelta(minutes=60)
    return {k: dict(v) for k, v in output.items()}


data = {
 'Mon': [9, 23],
 'Thu': [12, 13, 14],
 'Tue': [11, 12, 14],
 'Wed': [11, 12, 13, 14],
 'Fri': [13],
 'Sat': [],
 'Sun': [],
}

start = datetime(2015, 7, 22, 17, 58, 54, 746784)
end = datetime(2015, 8, 30, 10, 22, 36, 363912)

pprint(solve(start, end, data))
# output 
{'Fri': {13: 6},
 'Mon': {9: 5, 23: 5},
 'Thu': {12: 6, 13: 6, 14: 6},
 'Tue': {11: 5, 12: 5, 14: 5},
 'Wed': {11: 5, 12: 5, 13: 5, 14: 5}}
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
2

Maybe I am not grasping your issue fully but you could get all the hours between the two dates and sum how many times each hour and day appears between the two dates:

from datetime import datetime
from dateutil import  rrule,parser


d={
 'Mon': [9, 23],
 'Thu': [12, 13, 14],
 'Tue': [11, 12, 14],
 'Wed': [11, 12, 13, 14],
 'Fri': [13],
 'Sat': [],
 'Sun': [],
}

st = datetime(2015, 7, 22, 17, 58, 54, 746784)

ed = datetime(2015, 8, 30, 10, 22, 36, 363912)
dates = list(rrule.rrule(rrule.HOURLY,
                         dtstart=parser.parse(st.strftime("%Y-%m-%d %H:%M:%S")),
                         until=parser.parse(ed.strftime("%Y-%m-%d %H:%M:%S"))))


days = {"Mon":0,"Tue": 1,"Wed":2,"Thu": 3,"Fri":4,"Sat":5,"Sun":6}

for k, val in d.items():
    for v in val:
        print("day: {} hour: {}".format(k,v))
        day = days[k]
        print(sum((v == dt.hour and dt.weekday() == day) for dt in dates))

Output:

day: Wed hour: 11
5
day: Wed hour: 12
5
day: Wed hour: 13
5
day: Wed hour: 14
5
day: Fri hour: 13
6
day: Tue hour: 11
5
day: Tue hour: 12
5
day: Tue hour: 14
5
day: Mon hour: 9
6
day: Mon hour: 23
5
day: Thu hour: 12
5
day: Thu hour: 13
5
day: Thu hour: 14
5

Not sure if you want the sum for all hours in each list or the total for each individual hour but either way you can store the output in a dict.

counts = {'Thu':{}, 'Sun':{}, 'Fri':{}, 'Mon':{}, 'Tue':{}, 'Sat':{}, 'Wed':{}}
for k, val in d.items():
    for v in val:
        day = days[k]
        sm = sum((v == dt.hour and dt.weekday() == day) for dt in dates)
        counts[k][v] = sm

from pprint import pprint as pp
pp(counts)

Output:

{'Fri': {13: 6},
 'Mon': {9: 5, 23: 5},
 'Sat': {},
 'Sun': {},
 'Thu': {12: 6, 13: 6, 14: 6},
 'Tue': {11: 5, 12: 5, 14: 5},
 'Wed': {11: 5, 12: 5, 13: 5, 14: 5}}
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
2

Here's a solution with loops and datetime:

import datetime

pairs = {1: [9,23],
2: [11, 12, 14],
3: [11, 12, 13, 14],
4: [12, 13, 14],
5: [13],
6: [],
7: []
}

start = datetime.datetime(2015, 7, 22, 17, 58, 54, 746784)
end = datetime.datetime(2015, 8, 30, 10, 22, 36, 363912)
result={}
for d,hl in pairs.items():
    for h in hl:
        result[(d,h)] = 0
        for diff in range((end-start).days*24):
            comp = start + datetime.timedelta(hours=diff)
            if comp.isoweekday() == d and comp.hour == h:
                result[(d,h)] += 1

 

>>> result
{(3, 12): 5, (5, 13): 6, (3, 13): 5, (1, 23): 5, (2, 11): 5, (3, 11): 5, (4, 14): 6, (4, 13): 6, (4, 12): 6, (2, 12): 5, (2, 14): 5, (3, 14): 5, (1, 9): 5}

I'll also try for a solution with timestamp() and %.

TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
2

Here is another solution with arithmetic:

import datetime

pairs = {1: [9,23],
2: [11, 12, 14],
3: [11, 12, 13, 14],
4: [12, 13, 14],
5: [13],
6: [],
7: []
}

start = datetime.datetime(2015, 7, 22, 17, 58, 54, 746784)
end = datetime.datetime(2015, 8, 30, 10, 22, 36, 363912)
result={}
weeks = (end-start).days//7

for d,hl in pairs.items():
    for h in hl:
        initial = weeks
        if d > start.isoweekday() or (
           d == start.isoweekday() and h >= start.hour):
            initial += 1
        result[(d,h)] = initial

 

>>> for k in sorted(result):
...     print(k, result[k])
...
(1, 9) 5
(1, 23) 5
(2, 11) 5
(2, 12) 5
(2, 14) 5
(3, 11) 5
(3, 12) 5
(3, 13) 5
(3, 14) 5
(4, 12) 6
(4, 13) 6
(4, 14) 6
(5, 13) 6
TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
  • Thanks! -- Any reasons to believe one of the solutions is more efficient than the other? (e.g. looping through hundreds of thousands of `pairs` examples) – Amelio Vazquez-Reina Jul 23 '15 at 03:03
  • @AmelioVazquez-Reina - I'm not sure; I didn't test it. I just had the suspicion that a bit of arithmetic would be faster than looping through every possible hour. If you want to try some of these solutions out and let us know your results, I'd be interested in seeing them. – TigerhawkT3 Jul 23 '15 at 03:14
  • To clarify, I didn't test the relative _performance_ of each algorithm. – TigerhawkT3 Jul 23 '15 at 03:49
  • @AmelioVazquez-Reina - I just refactored this, removing a lot of unnecessary stuff. It should be clearer and faster now. – TigerhawkT3 Jul 23 '15 at 17:16
0

So, if I understand your question correctly, I'd start with finding the first occurence of the "hour" within the time range and then look for the next occurences week by week. Like this:

#!/usr/bin/python
from __future__ import print_function
import datetime
import dateutil.relativedelta


def hours_between(start, end, weekday, hour):
    first = start + dateutil.relativedelta.relativedelta(
        weekday=weekday, hour=hour,
        minute=0, second=0, microsecond=0)
    week = dateutil.relativedelta.relativedelta(weeks=1)

    all_dates = []
    d = first
    while d < end:
        all_dates.append(d)
        d += week

    return all_dates


def main():
    start = datetime.datetime(2015, 7, 22, 17, 58, 54, 746784)
    end = datetime.datetime(2015, 8, 30, 10, 22, 36, 363912)
    all_dates = hours_between(start, end, dateutil.relativedelta.WE, 14)
    print(all_dates)
    print(len(all_dates))

main()
neingeist
  • 246
  • 3
  • 9