Как в массиве найти ближайшую дату

I have an array with days in it. Each day is an object, for example:

{day_year: "2012", day_month: "08", day_number: "03", day_name: "mon"}

I have also added a timestamp attribute to each day object, by using:

function convertDays() {
    var max_i = days.length;
    for(var i = 0; i < max_i; i++) {
        var tar_i = days[i];
        tar_i.timestamp = new Date(tar_i.day_year, tar_i.day_month, tar_i.day_number);
    }
}

The days in the array are arbitrary, so there is no real logic to them.

Now I want to find the two closest days to any given date. So if the array with days contains

  • August 2, 2012
  • August 4, 2012
  • August 23, 2012

And I search for August 11, 2012, I want it to return August 4, 2012 and August 23, 2012.

I have tried using an answer from another question, that looks like this:

function findClosest(a, x) {
    var lo, hi;
    for(var i = a.length; i--;) {
        if(a[i] <= x && (lo === undefined || lo < a[i])) lo = a[i];
        if(a[i] >= x && (hi === undefined || hi > a[i])) hi = a[i];
    }
    return [lo, hi];
}

However, this returns unidentified.

What would be the most efficient (least processor/memory intensive way) to achieve this?

Edit: “However, how are those results “strange”? Could you provide an example of your code and data?”

I’m now using the following to generate an array of dates:

var full_day_array = [];
for(var i = 0; i < 10; i++) {
    var d = new Date();
    d.setDate(d.getDate() + i);
    full_day_array.push({day_year: d.getFullYear().toString(), day_month: (d.getMonth() + 1).toString(), day_number: d.getDate().toString()});
}

The strange part is, using the code below, this only works for an array of 10 dates or shorter. Whenever I use an array of 11 or more dates, the results become unexpected.

For instance: using an array of 15 dates, starting on August 6, 2012, to August 21, 2012. If I then call findClosest(full_day_array, new Date("30/07/2012"); you would expect it to return {nextIndex: 0, prevIndex: -1}. However, it returns {nextIndex: 7, prevIndex: -1}. Why?

function findClosest(objects, testDate) {
    var nextDateIndexesByDiff = [],
        prevDateIndexesByDiff = [];

    for(var i = 0; i < objects.length; i++) {
        var thisDateStr = [objects[i].day_month, objects[i].day_number, objects[i].day_year].join('/'),
            thisDate    = new Date(thisDateStr),
            curDiff     = testDate - thisDate;

        curDiff < 0
            ? nextDateIndexesByDiff.push([i, curDiff])
            : prevDateIndexesByDiff.push([i, curDiff]);
    }

    nextDateIndexesByDiff.sort(function(a, b) { return a[1] < b[1]; });
    prevDateIndexesByDiff.sort(function(a, b) { return a[1] > b[1]; });


    var nextIndex;
    var prevIndex;

    if(nextDateIndexesByDiff.length < 1) {
        nextIndex = -1;
    } else {
        nextIndex = nextDateIndexesByDiff[0][0];
    }
    if(prevDateIndexesByDiff.length < 1) {
        prevIndex = -1;
    } else {    
        prevIndex = prevDateIndexesByDiff[0][0];
    }
    return {nextIndex: nextIndex, prevIndex: prevIndex};
}

Как найти ближайшую дату в массиве?

Приходит массив из которого надо выбрать ближайшую дату по принципу “сегодня или позже”:

const Array = [
  {
    date: '01/01/2022',
    id: '1111',
  },
  {
    date: '31/03/2022',
    id: '2222',
  },
  {
    date: '23/05/2022',
    id: '3333',
  }
];
const findClosest = (data, accessor, target = Date.now()) =>
  data.reduce((prev, curr) => {
    const a = Math.abs(accessor(curr).getTime() - target);
    const b = Math.abs(accessor(prev).getTime() - target);
    return a - b < 0 ? curr : prev;
  });

const processDateString = (dateString) => {
  const [date, month, year] = dateString.split(///g).map(Number);
  return new Date(year, month - 1, date);
};

const closest = findClosest(Array, ({ date }) => processDateString(date));
const closestDate = closest.date;
console.log(closestDate);

возвращает ближайшую, но в прошлом.
Подскажите как переписать на “сегодня или позже”?


  • Вопрос задан

    более года назад

  • 281 просмотр

/**
 * My item definition
 * @typedef {Object} myItem
 * @property {string} date
 * @property {string} id
 */

/**
 * Find item with the date of today 
 * or the nearest one in the future
 * @param {myItem[]} data 
 * @returns {?myItem}
 */
const getNearest = data => {
  const now = new Date();
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());

  const index = data
    .map(({date}, orginalIndex) => {
      const [d, m, y] = date.split('/');
      return { sorter: new Date(y, m - 1, d), orginalIndex };
    })
    .sort((a, b) => a.sorter - b.sorter)
    .find(item => item.sorter >= today)
    ?.orginalIndex;

  return data[index] || null;
}


// использование
const data = [
  { date: '01/01/2022', id: '1' },
  { date: '31/03/2022', id: '2' },
  { date: '23/05/2022', id: '3' },
  { date: '05/04/2022', id: '4' },
  { date: '03/04/2022', id: '5' },
  { date: '09/04/2022', id: '6' },
];

getNearest(data) // { date: "03/04/2022", id: "5" }

Пригласить эксперта

const parseDate = (date) => Date.parse(date.split('/').reverse().join('-'));
const findClosest = (list, date = (new Date()).toLocaleDateString('en-GB')) => {
  const findDate = parseDate(date);
  return list.reduce(
    (acc, cur) => {
      const delta = parseDate(cur.date) - findDate;
      return (delta >= 0 && delta < acc.delta) ? { delta, el: cur } : acc;
    },
    { delta: Number.MAX_SAFE_INTEGER, el: null },
  ).el;
};


  • Показать ещё
    Загружается…

19 мая 2023, в 14:02

100000 руб./за проект

19 мая 2023, в 13:22

7000 руб./за проект

11 мая 2023, в 16:20

1500 руб./в час

Минуточку внимания

У нас есть массив такого вида:

Array
(
    [0] => 2016-02-22 00:20:00
    [1] => 2016-02-25 08:45:00
    [2] => 2016-02-25 19:10:00
    [3] => 2016-02-25 20:00:00
    [4] => 2016-02-26 15:55:00
    [5] => 2016-02-28 17:10:00
)

Нужно из него выбрать ближайшую дату относительно текущего времени.
Например, если сейчас 25.02.2016-14:45, то ближайшей будет 2016-02-25 08:45:00

VenZell's user avatar

VenZell

19.8k5 золотых знаков43 серебряных знака61 бронзовый знак

задан 26 фев 2016 в 12:43

Artyom  Kondra's user avatar

4

Вот рабочий пример

<?php

$dates = [
    '2016-02-22 00:20:00',
    '2016-02-25 08:45:00',
    '2016-02-25 19:10:00',
    '2016-02-25 20:00:00',
    '2016-02-26 15:55:00',
    '2016-02-28 17:10:00'
];

$now = time();
$past_dates = [];

foreach ($dates as $date) {
    $timestamp = strtotime($date);
    if ($now >= $timestamp) {
        $past_dates[] = $timestamp;
    }
}

$result = 'unknown';

if (count($past_dates) > 0) {
    $result = date('Y-m-d H:i:s', max($result));
}

echo $result;

ответ дан 26 фев 2016 в 13:00

VenZell's user avatar

VenZellVenZell

19.8k5 золотых знаков43 серебряных знака61 бронзовый знак


Пусть имеется диапазон с датами. Найдем дату из этого диапазона, которая является ближайшей к заданной. Решение этой задачи аналогично решению, изложенного в статье

Поиск ЧИСЛА ближайшего к заданному

.

Пусть в диапазоне

A4:A12

имеется список дат. Будем в нем искать дату из ячейки

С4

. Если диапазон не содержит искомого значения, то будет возвращено ближайшее значение.

Искомая дата необязательно должна совпадать с какой-нибудь датой или даже находиться в диапазоне поиска (см.

Файл примера

):


Решение


Результат поиска


Примечание

=

ВПР(C4;A4:A12;1;ИСТИНА)

=

ПРОСМОТР(C4;A4:A12;A4:A12)

ищется наибольшее значение, которое меньше, чем искомое значение (если искомая дата меньше, чем минимальное значение из диапазона, то будет возвращена ошибка #Н/Д)

если столбец не отсортирован по возрастанию, то результат непредсказуем

=

ИНДЕКС(A4:A12; ПОИСКПОЗ(МИН(ABS(A4:A12-C4));ABS(A4:A12-C4);0))

ищется

ближайшая

к критерию дата (если обнаружено 2 ближайших числа (одно больше, другое меньше критерия), то выводится то, которое расположено выше (номер строки меньше))

столбец м.б. не отсортирован

=

ИНДЕКС(A4:A12; ПОИСКПОЗ(МАКС(ЕСЛИ(A4:A12<=C4;A4:A12;””)); $A$4:$A$12;0);1)

ищется

наибольшее значение, которое меньше

, чем искомое значение (если искомая дата меньше, чем минимальное значение из диапазона, то будет возвращена ошибка #Н/Д)

столбец м.б. не отсортирован

=

ИНДЕКС(A4:A12; ПОИСКПОЗ(МИН(ЕСЛИ(A4:A12>=C4;A4:A12;””)); $A$4:$A$12;0);1)

ищется

наименьшее значение, которое больше

, чем искомое значение (если искомая дата больше, чем максимальное значение из диапазона, то будет возвращена ошибка #Н/Д)

столбец м.б. не отсортирован

=

МАКС(ЕСЛИ(МИН(ABS(A4:A12-C4))=ABS(A4:A12-C4);A4:A12;МИН(A4:A12)))

ищется

ближайшая

к критерию дата (если обнаружено 2 ближайших числа (одно больше, другое меньше критерия), то выводится то, которое больше

столбец м.б. не отсортирован

=

МИН(ЕСЛИ(МИН(ABS(A4:A12-C4))=ABS(A4:A12-C4);A4:A12;МАКС(A4:A12)))

ищется

ближайшая

к критерию дата (если обнаружено 2 ближайших числа (одно больше, другое меньше критерия), то выводится то, которое меньше

столбец м.б. не отсортирован

Последние 5 решений реализованы с использованием

формул массива

. Для пошагового просмотра хода вычислений используйте клавишу

F9

.

Как видно из таблицы, применение функции

ВПР()

со значением аргумента

интервальный_просмотр

равным ИСТИНА, имеет недостатки. Во-первых, найденное значение м.б. далеко не ближайшим (задав в качестве критерия дату 06.02.2009 получим не ближайшую дату 07.02.2009, а наибольшее значение, которое меньше, чем искомое значение, т.е. 05.01.2009). Во-вторых, если искомая дата меньше, чем минимальное значение из диапазона, то будет возвращена ошибка #Н/Д. В-третьих, требуется

сортировка

списка, что не всегда удобно.

Хорошим решением является

формула массива

=ИНДЕКС(A4:A12; ПОИСКПОЗ(МИН(ABS(A4:A12-C4));ABS(A4:A12-C4);0))

свободная от всех указанных недостатков, но имеющая свои: формула относительно сложная и является

формулой массива

.


Совет

: т.к. дата в MS EXCEL хранится в числовом виде (см. статью

Как Excel хранит дату и время

), то формулы для поиска ближайшего числа также будут работать для дат (см. раздел

Ближайшее ЧИСЛО

).

#php #arrays #date

#php #массивы #Дата

Вопрос:

 $dates[] = array("date" => "2016-02-18 02:00:00", "duration" => "600");
$dates[] = array("date" => "2016-02-18 02:05:00", "duration" => "300");
$dates[] = array("date" => "2016-02-18 02:10:00", "duration" => "600");
$dates[] = array("date" => "2016-02-18 02:25:00", "duration" => "300");
$dates[] = array("date" => "2016-02-18 02:30:00", "duration" => "600");
function closestDates($array, $date){   
    foreach($array as $day)
         $interval[] = abs(strtotime($date["date"]) - strtotime($day["date"]));
    asort($interval);
    $closest = key($interval);
    $alreadyChosen[] = $array[$closest];
    return $alreadyChosen;
}
$returnedDates = closestDates($dates, array("date" => "2016-02-18 02:00:00", "duration" => "600"));
print_r($returnedDates);

// This returns 
Array ( 
    [0] => Array ( 
        [date] => 2016-02-18 02:00:00 
        [duration] => 600 
    ) 
)
  

Как я могу адаптировать вышеуказанную функцию, чтобы иметь возможность проверять dates массив и помещать следующую ближайшую дату в alreadyChosen массив на основе времени продолжительности.

 $dates = (
    '0'=> array("date" => "2016-02-18 02:00:00", "duration" => "600"),
    '1'=> array("date" => "2016-02-18 02:05:00", "duration" => "300"),
    '2'=> array("date" => "2016-02-18 02:10:00", "duration" => "600"),
    '3'=> array("date" => "2016-02-18 02:25:00", "duration" => "300"),
    '4'=> array("date" => "2016-02-18 02:30:00", "duration" => "600")
);

// Expected result to be after the checks:
$alreadyChosen = array
(
    '0'=> array("date" => "2016-02-18 02:00:00", "duration" => "600"),
    '1'=> array("date" => "2016-02-18 02:10:00", "duration" => "600"),
    '2'=> array("date" => "2016-02-18 02:25:00", "duration" => "300"),
    '3'=> array("date" => "2016-02-18 02:30:00", "duration" => "600")
);
  

Комментарии:

1. Я считаю, что функция closestDate уже работает нормально …. Теперь, как вы хотите использовать $ alreadyChosen??? для меня это непонятно в вашем вопросе

2. что вы подразумеваете под doesn't check on duration time . Также в вашем вопросе упоминаются два $alreadyChosen

3. не могли бы вы подробнее рассказать о своем ожидаемом результате.. Я не могу ее получить

4. @undefined_variable Ожидаемый результат — иметь возможность передавать начальную дату, которая была бы 2016-02-18 02:00:00 длительностью 600 (10 минут) в зависимости от продолжительности, найдите ближайшую дату, которая была бы равна концу продолжительности или больше продолжительности. Затем так далее…..

5. @undefined_variable Я обновил свой вопрос, теперь его должно быть легче понять.

Ответ №1:

Если вы хотите получить наиболее близкую дату к дате, которую вы вводите в функцию, и предотвратить даты, которые уже выбраны, вы могли бы сделать это так:

 function pickMostCloserDate($dateArray, $compareDate, amp;$pickedDates = array())
{
    $dates = [];  
    foreach($dateArray as $key => $originalDate){
        $date = $date['date'];
        //compare options
        if(!in_array($originalDate, $pickedDates)){
           $dates[$key] = abs(strtotime($compareDate) - strtotime($date));
        }
    }

    asort($dates);

    $dateIndex = array_shift(array_keys($dates));

    array_push($pickedDates, $dateArray[$dateIndex]);
    return $dateArray[$dateIndex];

}
  

Это вернет самую близкую дату и добавит найденную дату в выбранные даты, так что на самом деле вам нужно сделать это:

 $pickedDates = [];
$closeDate = pickMostCloserDate($arrayWithDates, '2016-05-05 00:00:00', $pickedDates);
  

echo $CloseDate;

// следующий раунд для выбора даты

Надеюсь, это поможет. Пожалуйста, имейте в виду, что я написал это из head, нужно исправить.

Комментарии:

1. Привет, я обновил свой вопрос дополнительной информацией, это должно помочь вам с тем, что я ищу.

2. Я еще больше обновил свой ответ, теперь его должно быть легче понять.

3. Привет, Крис, это должно помочь вам с вашей проблемой. Функция выберет ближе к определенной дате, а также добавит найденную дату в переменную pickedDates. По мере того, как вы будете вызывать функцию, будут выбираться новые даты.

4. Привет, Камил. Ваш ответ пока дает мне именно то, что я уже получил в своем вопросе. Пожалуйста, проверьте мой ожидаемый результат, чтобы дать себе представление о том, что я ищу.

5. Крис, я не уверен, понимаю ли я, чего ты хочешь. Но, как я вижу, во втором массиве больше дат. Что на самом деле делает эта функция, если вы будете вызывать функцию несколько раз. Он добавит даты в переменную $pickedDates. Таким образом, в этом случае вы, вероятно, могли бы поместить эту функцию в цикл, чтобы сохранить эти даты в этой переменной. Это то, что я понял, чего вы хотели.

Ответ №2:

Я нашел решение, выполнив следующее: (я многое изменил в своем вопросе, чтобы заставить его работать)

 $dates[] = array("date" => "2016-02-18 02:00:00", "meeting_id" => "1", "class_id" => "10", "duration" => "600");
$dates[] = array("date" => "2016-02-18 02:10:00", "meeting_id" => "1", "class_id" => "10", "duration" => "600");
$dates[] = array("date" => "2016-02-18 02:20:00", "meeting_id" => "1", "class_id" => "10", "duration" => "600");
$dates[] = array("date" => "2016-02-18 02:30:00", "meeting_id" => "1", "class_id" => "10", "duration" => "600");
$dates[] = array("date" => "2016-02-18 02:10:00", "meeting_id" => "2", "class_id" => "10", "duration" => "600");
$dates[] = array("date" => "2016-02-18 02:25:00", "meeting_id" => "2", "class_id" => "10", "duration" => "300");
$dates[] = array("date" => "2016-02-18 02:30:00", "meeting_id" => "3", "class_id" => "10", "duration" => "600");
$dates[] = array("date" => "2016-02-18 02:40:00", "meeting_id" => "3", "class_id" => "10", "duration" => "600");
$dates[] = array("date" => "2016-02-18 02:30:00", "meeting_id" => "4", "class_id" => "11", "duration" => "600");
$dates[] = array("date" => "2016-02-18 02:40:00", "meeting_id" => "4", "class_id" => "11", "duration" => "600");
$dates[] = array("date" => "2016-02-18 02:50:00", "meeting_id" => "4", "class_id" => "11", "duration" => "600");
$dates[] = array("date" => "2016-02-18 03:00:00", "meeting_id" => "4", "class_id" => "11", "duration" => "600");

$firstDates[] = array("date" => "2016-02-18 02:00:00", "meeting_id" => "1", "class_id" => "10", "duration" => "600");
$firstDates[] = array("date" => "2016-02-18 02:10:00", "meeting_id" => "1", "class_id" => "10", "duration" => "600");
$firstDates[] = array("date" => "2016-02-18 02:20:00", "meeting_id" => "1", "class_id" => "10", "duration" => "600");
$firstDates[] = array("date" => "2016-02-18 02:30:00", "meeting_id" => "1", "class_id" => "10", "duration" => "600");

$children[] = array("id" => 1, "class_id" => "10", "fullname" => "Callum");
$children[] = array("id" => 2, "class_id" => "10", "fullname" => "Daniel");
$children[] = array("id" => 3, "class_id" => "11", "fullname" => "Jake");

function dateExists($array, $child, $date) {
    if (empty($array)) {
        return false;
    }
    $flag = false;
    foreach($array as $value) {
        if ($value['child']['id'] == $child['id'] amp;amp; $value['meeting']['meeting_id'] === $date['meeting_id']){
            return true;
        }
        if ($value['meeting']['date'] == $date['date']) {
            return true;
        }
        $start = strtotime($value['meeting']['date']);
        $end = $start   $value['meeting']['duration'];
        $ts = strtotime($date['date']);
        if ($ts > $start amp;amp; $ts < $end) {
            $flag = true;
            break;
        }
    }
    return $flag;
}


$results = [];

function buildDates($dates, $children, $key, $firstDate) {
    $temp = [];
    $pickedDates = array();
    foreach ($children as $child) {
        if($firstDate["class_id"] === $child["class_id"]) {
            $temp[] = array('child' => $child, 'meeting' => $firstDate);
            $pickedDates[$firstDate['date']] = $temp;
        }
        break;
    }
    foreach ($dates as $key => $date) {
        foreach ($children as $child) {
            if($date["class_id"] === $child["class_id"]) {
                if (!dateExists($temp, $child, $date)) {
                    $temp[] = array('child' => $child, 'meeting' => $date);
                    $pickedDates[$firstDate['date']] = $temp;
                }
            }
        }
    }
    return $pickedDates = array($firstDate["date"] => $temp);
}

foreach ($firstDates as $date) {
    $results[] = buildDates($dates, $children, $date);
}

print_r($results);
  

Добавить комментарий