3

I'm trying to find the closest past date in a list of dates to the current date. I've found several examples of searching for the closest date in a list but all these allow for the option of a further date to be returned.

The list of datetimes has the following form:

datelist =
[datetime.datetime(2019, 12, 31, 0, 0),
 datetime.datetime(2020, 1, 10, 0, 0),
 datetime.datetime(2020, 1, 20, 0, 0),
 datetime.datetime(2020, 1, 31, 0, 0),
 datetime.datetime(2020, 2, 10, 0, 0),
 datetime.datetime(2020, 2, 20, 0, 0)]

Up until now I have been implementing the following function to find the closest date;

def nearest_ind(items, pivot):
    '''
    Find the nearest value to the given value (the pivot) in a list.
    '''
    time_diff = np.abs([date - pivot for date in items])
    return time_diff.argmin(0)

But this returns the closest date to the current date, half of the time this returns the closest date in the future. I've also been looking at implementing numpy searchsorted function, but this seems to always return the closest future date, regardless of whether I select "right" or "left" for the side parameter, in the following ways:

np.searchsorted(datelist, datetime.datetime.now(), side='right')
np.searchsorted(datelist, datetime.datetime.now(), side='left')

The numpy searchsorted function returns (if I was to run this today, 7th February 2020) 4 (corresponding to datetime.datetime(2020, 2, 10, 0, 0)) in either instance. Does anyone know of a way to ensure the closest date in the past is always returned?

JackLidge
  • 391
  • 4
  • 16
  • This was what I had looked at before to get the function shown in the question, but I think all answers left open the possibility of returning dates in the future if they were closer – JackLidge Feb 07 '20 at 08:56
  • What if the date to be searched has an exact match? Would you skip that and select the one before that or select the exact match date? – Divakar Feb 07 '20 at 08:59
  • Ideally I'd select the matching date, but if it selects the time period before that wouldn't be a problem. – JackLidge Feb 07 '20 at 09:06

2 Answers2

6

Using min with two keys would be one way:

from datetime import datetime

now = datetime.now()
min(datelist, key=lambda x: (x>now, abs(x-now)) )

Output:

datetime.datetime(2020, 1, 31, 0, 0)
Chris
  • 29,127
  • 3
  • 28
  • 51
1

You can use np.searchsorted as you already attempted with its side arg set as right, so that it considers only the ones before it or same as itself. Now, since by its definition searchsorted gets us the index of position of the date to be searched in the larger sorted array, we need to subtract 1 to get the closest one before it.

Hence, simply do -

datelist[np.searchsorted(datelist, datetime.datetime.now(), side='right')-1]

Sample runs -

In [48]: datelist = [datetime.datetime(2020, 2, 5, 0, 0),
    ...:  datetime.datetime(2020, 2, 8, 0, 0),
    ...:  datetime.datetime(2020, 2, 12, 0, 0),
    ...:  datetime.datetime(2020, 2, 20, 0, 0)]

In [49]: datelist[np.searchsorted(datelist, datetime.datetime(2020, 2, 11, 0, 0), side='right')-1]
Out[49]: datetime.datetime(2020, 2, 8, 0, 0)

In [50]: datelist[np.searchsorted(datelist, datetime.datetime(2020, 2, 7, 0, 0), side='right')-1]
Out[50]: datetime.datetime(2020, 2, 5, 0, 0)
Divakar
  • 218,885
  • 19
  • 262
  • 358