0

I have a list of times that I need to assign to time windows (7, 9, 12, 15, 18) to make sure each time window is covered by an element in my list.

from datetime import date, time, datetime

def nearest(items, target):
    return min(items, key=lambda x: abs(x - target))

time_list = [datetime.datetime(2019, 12, 17, 7, 30), 
             datetime.datetime(2019, 12, 17, 9, 0), 
             datetime.datetime(2019, 12, 17, 16, 0), 
             datetime.datetime(2019, 12, 17, 18, 30), 
             datetime.datetime(2019, 12, 17, 21, 30), 
             datetime.datetime(2019, 12, 17, 12, 30), 
             datetime.datetime(2019, 12, 17, 19, 0), 
             datetime.datetime(2019, 12, 17, 0, 0), 
             datetime.datetime(2019, 12, 17, 14, 30)]

target_times = [datetime.combine(date.today(),time(i,0)) for i in range(6,19,3)]
coverage = [abs(nearest(time_list, t)-t)<time(1,30) for t in target_times]

Desired output:

[True, True, True, True, True]

This currently returns a "<" error, which I can get around, but I am not sure if this is the best way to accomplish what I want to do. I have numpy and scipy libraries available and can probably get others.

DanGoodrick
  • 2,818
  • 6
  • 28
  • 52
  • Your desired output has 5 entries in the list, which matches the target_times ... So are you looking to see if any elements of `time_list` are in _any_ bin of `target_times`? – Tee Dec 17 '19 at 21:53
  • I want to know if each of the `target_times` is covered by one of the `time_list` elements – DanGoodrick Dec 17 '19 at 22:00

3 Answers3

1

This is a solution that I put together based on this SO question How to check if the current time is in range in python?. It works but I wonder if there is a better solution out there.

def time_in_range(start, end, x):
    today = timezone.localtime().date()
    start = timezone.make_aware(datetime.combine(today, start))
    end = timezone.make_aware(datetime.combine(today, end))
    x = timezone.make_aware(datetime.combine(today, x))
    if end <= start:
        end += timedelta(days=1) # tomorrow!
    if x <= start:
        x += timedelta(days=1) # tomorrow!
    return start <= x <= end

downloaded = [False, False, False, False, False]
times = [time(i,0) for i in [5,8,10,13,16,20]]
for i in range(5):
    for start_time in start_times:
        if time_in_range(times[i], times[i+1], start_time):
            downloaded[i] = True
DanGoodrick
  • 2,818
  • 6
  • 28
  • 52
1

The answer for your immediate question is that you are trying to compare a timedelta ob object to a time object. In this case you would want to create a timedelta object like so:

timedelta(hours=1, minutes=30)

Instead of

time(1,30)
0

You may wish to use pandas here; because it has built-in ways of dealing with bins and intervals.

In this case, you have four (4) time intervals:

  1. [7 to 9)
  2. [9 to 12)
  3. [12 to 15)
  4. [15 to 18)
  5. ...and is there supposed to be another interval for 18 to 24?

The "[" means inclusive, and the ")" is exclusive.

You can load time_list into a pandas dataframe:

import pandas as pd

df = pd.DataFrame(time_list,columns=['timestamp']) # `columns` is how you name the column(s)

Then, the reason I suggest pandas is that it has a helpful function called .cut() which bins values.

>>> bins = [7, 9, 12, 15, 18]
>>> pd.cut(df['timestamp'].dt.hour,bins=bins).unique().dropna().sort_values()
[(7, 9], (9, 12], (12, 15], (15, 18]]

You can then test for the appropriate length of the result of the above operation like this (the subtraction of 1 is because with five bins:

>>> covered_intervals = _ # an underscore gets the most recent value in the interpreter
>>> len(covered_intervals) == len(bins) - 1
True

And if you want a list of booleans you can do:

result = []
for i in covered_intervals:
    if i.left in bins:
        result.append(True)
    else:
        result.append(False)
mechanical_meat
  • 163,903
  • 24
  • 228
  • 223