【问题标题】:Flattening two arrays of date ranges in JavaScript?在 JavaScript 中展平两个日期范围数组?
【发布时间】:2016-11-08 13:15:28
【问题描述】:

我正在构建可用性日历。这是一个月度视图,天数本质上是一个布尔值,它们可以是可用的,也可以是不可用的。日历上可以存在多个“产品”。比较简单。

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

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

UI 看起来与这个日历非常相似:

当用户更新他们的可用性时,真正的麻烦就来了。他们可以选择“更新所有”或“更新一个”。例如,如果房间 1 在 1 月 5 日已经不可用,而用户现在想要使从 1 月 1 日到 1 月 10 日的所有房间不可用,我需要从数组中删除 Room 1 January 5th 对象,因为它与新的重叠数据。

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

[
  {
    "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。

【问题讨论】:

  • 你有什么问题?

标签: javascript arrays performance datetime data-structures


【解决方案1】:

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

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);
.as-console-wrapper { max-height: 100% !important; top: 0; }

说明

作为第一步,将两个数组连接起来并按开始日期递增排序。

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

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

这对由将累积到最终结果的数组(它开始为空)和最后遇到的endDate日期组成,该日期设置为很久以前。

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

如果上一期完全包含p,则直接忽略,[res, end]保持原样。

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

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

大约 90000000

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

【讨论】:

    【解决方案2】:

    我不知道这是否是 最快 方法,但一个好的起点是利用 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)

    【讨论】:

      【解决方案3】:

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

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

      此提议适用于未排序的数据。

      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);
      .as-console-wrapper { max-height: 100% !important; top: 0; }
      &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.15.2/moment.min.js"&gt;&lt;/script&gt;

      【讨论】:

        【解决方案4】:

        从现在开始使用isbetween 方法。 设计:用户选择开始和结束日期后,使用他的开始日期并将其与日期数组(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});
        

        由于 delete 不会重新索引和更新长度属性,您可能希望重新索引它,但这不是强制性的。

        请注意,我假设没有“范围交叉”(当范围 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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-03-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多