在JavaScript中展平两个日期范围数组?

时间:2016-11-08 13:15:29

标签: javascript arrays performance datetime data-structures

我正在构建可用性日历。这是一个月视图,天几乎是一个布尔值,它们可以是可用的,也可以是不可用的。有多种产品"可以存在于日历上。相对简单。

我存储这些"可用性范围"作为一个尽可能简洁的对象数组。因此,单个产品的可能数据集如下所示:

[
  {
    "startDate": "2016-11-08",
    "endDate": "2016-11-08"
  },
  {
    "startDate": "2016-11-11",
    "endDate": "2016-11-14"
  },
  {
    "startDate": "2016-11-20",
    "endDate": "2016-11-22"
  }
]

用户界面与此日历非常相似:

enter image description here

真正的麻烦来自用户更新其可用性。他们可以选择更新所有"或者"更新一个"。例如,如果房间1在1月5日已经不可用,并且用户现在想要从1月1日到1月10日使所有房间都不可用,我需要从阵列中移除1月1日房间1对象,因为它与新的重叠数据。

此外,我想合并任何连续的时间跨度,例如:

[
  {
    "startDate": "2016-11-08",
    "endDate": "2016-11-08"
  },
  {
    "startDate": "2016-11-09",
    "endDate": "2016-11-09"
  },
]

应该合并到:

[
  {
    "startDate": "2016-11-08",
    "endDate": "2016-11-09"
  },
]

我意识到这是一个相对复杂的问题,但肯定必须有一个预先存在的解决方案,或者至少类似的东西?

我可以访问momentJs。

4 个答案:

答案 0 :(得分:1)

我不知道这是否是最快方法,但一个好的起点是利用Array.sort按日期规范化数据,以便任何连续日期将彼此相邻,然后使用简单的数组遍历来合并连续日期。

下面的示例使用sort数组遍历。



let states = [
  {
    "startDate": "2016-11-08",
    "endDate": "2016-11-08"
  },
  {
    "startDate": "2016-11-11",
    "endDate": "2016-11-14"
  },
  {
    "startDate": "2016-11-20",
    "endDate": "2016-11-22"
  },
  {
    "startDate": "2016-11-15",
    "endDate": "2016-11-19"
  },
];


console.log('Started With:', states);
 
let sorted_states = states.sort(function(a,b){
  return (new Date(a.startDate)) - (new Date(b.startDate))
});

// simple array traversal method
for (let i = sorted_states.length - 1; i--;){
  let current = sorted_states[i];
  let next = sorted_states[i+1];

  interval = ((new Date(next.startDate) - new Date(current.endDate)));
  if (interval <= (1000 * 60 * 60 * 24)){ // one day
    newEntry = {
      startDate : current.startDate,
      endDate   : next.endDate,
    };
    sorted_states[i] = newEntry;
    sorted_states.splice(i+1, 1); // delete coalesced entry
  }
}

console.log('Ended With:', sorted_states)
&#13;
&#13;
&#13;

答案 1 :(得分:0)

您可以使用两个对象作为开始和结束日期的哈希表,并使用使用时刻获取明天的日期,并查看日期是否为开始日期。

然后更新对象,从开始和结束对象中删除,然后重新注册。

此提案适用于未分类的数据。

&#13;
&#13;
function getTomorrow(day) {
    return moment(day).add(1, 'days').format("YYYY-MM-DD");
}

var array = [{ startDate: "2016-11-08", endDate: "2016-11-08" }, { startDate: "2016-11-09", endDate: "2016-11-09" }, { startDate: "2016-12-02", endDate: "2016-12-02" }, { startDate: "2016-12-03", endDate: "2016-12-03" }, { startDate: "2016-12-01", endDate: "2016-12-01" }, { startDate: "2016-12-04", endDate: "2016-12-04" }, ],
    start = {},
    end = {},
    result;

array.forEach(function (object) {
    start[object.startDate] = object;
    end[object.endDate] = object;
});

Object.keys(end).forEach(function (today) {
    var tomorrow = getTomorrow(today);
    if (start[tomorrow]) {
        end[today].endDate = start[tomorrow].endDate;
        end[tomorrow] = end[today];
        delete end[today];
        delete start[tomorrow];
    }
});

result = Object.keys(start).map(function (k) {
    return start[k];
});

console.log(result);
&#13;
.as-console-wrapper { max-height: 100% !important; top: 0; }
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.15.2/moment.min.js"></script>
&#13;
&#13;
&#13;

答案 2 :(得分:0)

立即使用isbetween方法。 设计:在用户选择开始日期和结束日期后,使用他的开始日期并将其与日期数组(arr)中的每个条目进行比较,如果某个条目的开始日期在用户选择的范围内,则将其删除。在迭代完所有条目后,添加用户选择范围到arr

userSdate="2016-11-08"
userEdate="2016-11-09"
for(i=0;i<arr.length;i++)
{
    tmpSdate = arr[i].startDate;
    tmpEdate = arr[i].endDate;
    console.log( tmpSdate + " : " + tmpEdate );
    isIn=moment(tmpSdate ).isBetween(userSdate,userEdate, null, '[]'); //note:[] means inclusive range
    if(isIn)
    {
      delete(arr[i]);//remove arr[i]
    }
}
arr.push({ "startDate": userSdate, "endDate" : userEdate});

由于删除不重新索引和更新长度属性,您可能需要重新索引它,尽管这不是强制性的。

请注意,我假设没有&#34;范围交叉&#34; (当范围A的开始完全位于范围B内但不是它(A)结束时),见下文

1                                      1
A-2------2----3----3--4----------4-----B            (no range crossing)
  C------D    E----F  G----------H

A--2------------------2--4--------------4---B           (range crossing)
   C-------3----------D  G--3-----------H
           E----------------F

答案 3 :(得分:0)

这是一个函数(ES6),可用于合并两个数组,每个数组包含句点。我在下面将它应用于一些示例数据,这些数据比您提供的数据更加扩展,因此它涵盖了几种重叠和邻接的情况:

&#13;
&#13;
function mergePeriods(a, b) {
    return a.concat(b)
        .sort( (a, b) => a.startDate.localeCompare(b.startDate) )
        .reduce( ([res, end], p) => 
            new Date(p.startDate).getTime()<=new Date(end).getTime()+90000000
                ? p.endDate > end
                    ? [res, res[res.length-1].endDate = p.endDate]
                    : [res, end]
                : [res.concat(p), p.endDate],
            [[], '1970-01-01'])[0];
}

// sample data
var unavailable1 = [
  {
    "startDate": "2016-11-08",
    "endDate": "2016-11-08"
  },
  {
    "startDate": "2016-11-11",
    "endDate": "2016-11-14"
  },
  {
    "startDate": "2016-11-20",
    "endDate": "2016-11-22"
  },
  {
    "startDate": "2016-11-27",
    "endDate": "2016-11-27"
  },
  {
    "startDate": "2016-11-29",
    "endDate": "2016-11-29"
  }
];

var unavailable2 = [
  {
    "startDate": "2016-11-09",
    "endDate": "2016-11-09"
  },
  {
    "startDate": "2016-11-12",
    "endDate": "2016-11-15"
  },
  {
    "startDate": "2016-11-18",
    "endDate": "2016-11-21"
  },
  {
    "startDate": "2016-11-26",
    "endDate": "2016-11-28"
  }
];

// merge the sample data
var res = mergePeriods(unavailable1, unavailable2);

console.log(res);
&#13;
.as-console-wrapper { max-height: 100% !important; top: 0; }
&#13;
&#13;
&#13;

解释

作为第一步,两个数组通过增加开始日期进行连接和排序。

然后在该数组上调用reduce,其起始值(对于累加器)等于:

[[], '1970-01-01']

这一对包含将被累积到最终结果的数组(它开始为空),以及最后遇到的endDate日期,它被设置为很久以前的时间。

此对在reduce回调中作为[res, end]收到,并且数组中的当前元素名为p。然后进行一些比较以检测句点pend的关系。如果重叠是邻接,则更新(扩展)当前结果中的前一个元素以匹配p.endDate,这也将成为下一次迭代中end的新值。

如果上一期间完全包含p,则会被忽略,[res, end]会保留原样。

如果句点p与前一个句点不相交,则会将其与结果(concat)连接,end设置为此p.endDate。< / p>

当结果以这种方式组合并且reduce返回时,我们不再对最新的结束日期感兴趣,而只对数组进行解释,这解释了最终的[0]

约90000000

值90000000表示25小时内的毫秒数。这是为了检测两个周期是否相邻。额外1小时不会受到伤害,并且可以很好地处理一夜之间的DST变化。也可以使用momentjs完成此操作,但这在普通JavaScript中也不麻烦。