Search code examples
pythonlistdatetimeminpython-datetime

How can I find all datetimes in a list within n minutes of a given value?


I have two lists of times (Hour:Min:Sec format) and I've been struggling to compare each entry in list_a against all of list_b to identify values that fall within 30 minutes:

list_a = ["10:26:42", "8:55:43", "7:34:11"]
list_b = ["10:49:20", "8:51:10", "10:34:35", "8:39:47", "7:11:49", "7:42:10"]

Expected Output:

10:26:42 is within 30m of 10:49:20, 10:34:35
8:55:43 is within 30m of 8:51:10, 8:39:47
7:34:11 is within 30m of 7:11:49, 7:42:10

So far what I've been doing is:

import datetime

# Convert the Lists to Datetime Format

for data in list_a:
    convert = datetime.datetime.strptime(data,"%H:%M:%S")
    list_a_times.append(convert)

for data in list_b:
    convert = datetime.datetime.strptime(data,"%H:%M:%S")
    list_b_times.append(convert)

# Using a Value of List A, Find the Closest Value in List B

for data in list_a_times:
     closest_to_data = min(list_b_times, key=lambda d: abs(d - data))

     print(data, closest_to_data)

This kind of works, but it only finds one nearest value! How can I manipulate the min() function to keep providing values as long as they're within the desired 30 minutes or less?


Solution

  • IIUC, you want to compare all combinations, so you need to check all.

    Please read the end of the answer for a note on datetime/timedelta.

    Using itertools.product:

    list_a = ['10:26:42', '8:55:43', '7:34:11']
    list_b = ['10:49:20', '8:51:10', '10:34:35', '8:39:47', '7:11:49', '7:42:10']
    
    import datetime
    from itertools import product
    
    str2time = lambda s: datetime.datetime.strptime(s, "%H:%M:%S")
    
    for a,b in product(map(str2time, list_a), map(str2time, list_b)):
        if abs(a-b).total_seconds() <= 1800:
            print(f'{a:%H:%M:%S} is within 30m of {b:%H:%M:%S}')
    

    output:

    10:26:42 is within 30m of 10:49:20
    10:26:42 is within 30m of 10:34:35
    08:55:43 is within 30m of 08:51:10
    08:55:43 is within 30m of 08:39:47
    07:34:11 is within 30m of 07:11:49
    07:34:11 is within 30m of 07:42:10
    

    Using nested for loops:

    import datetime
    
    str2time = lambda s: datetime.datetime.strptime(s, "%H:%M:%S")
    
    for a in map(str2time, list_a):
        start = f'{a:%H:%M:%S} is within 30m of'
        for b in map(str2time, list_b):
            if abs(a-b).total_seconds() <= 1800:
                print(f'{start} {b:%H:%M:%S}', end='')
                start = ','
        if start == ',':
            print()
    

    output:

    10:26:42 is within 30m of 10:49:20, 10:34:35
    08:55:43 is within 30m of 08:51:10, 08:39:47
    07:34:11 is within 30m of 07:11:49, 07:42:10
    

    note on datetime

    Using datetime without date will default to 1900-01-01, which can have edge effects close to midnight. Instead, you could use timedelta objects. With my code you need to change the str2time function to:

    def str2time(s):
        h,m,s = map(int, s.split(':'))
        return datetime.timedelta(hours=h, minutes=m, seconds
    

    And alter a bit the code to be able to convert to string:

    z = datetime.datetime(1900,1,1)
    
    for a in map(str2time, list_a):
        start = f'{z+a:%H:%M:%S} is within 30m of'
        for b in map(str2time, list_b):
            if abs(a-b).total_seconds() <= 1800:
                print(f'{start} {z+b:%H:%M:%S}', end='')
                start = ','
        if start == ',':
            print()