【问题标题】:Calculate availabilites of a resource计算资源的可用性
【发布时间】:2016-12-14 19:09:08
【问题描述】:

我有一个应用程序显示一周的可预订时间段(资源)。

时间段总是 30 分钟长,可以从 :00 或 :30 开始。

可用性在内部由一周的分钟表示(在一个数组中),0 表示一周的第一分钟午夜,10079 表示一周的最后一分钟。这意味着具有 100% 可用性的资源在一个数组中有 10080 个数字,从 0 到 10079。

例如以下表示本周第一天 09:55-11:05 之间有 70 分钟的可用时间:

[595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665]

我将如何计算和显示任何可能的时间段(即至少连续 30 分钟)?

给定上面的数据集和当前周,并以星期一为一周的第一天:

[Mon Aug 08 2016 10:00:00, Mon Aug 08 2016 10:30:00]
[Mon Aug 08 2016 10:30:00, Mon Aug 08 2016 11:00:00]

我不知道这是否容易,但我目前有点不知道如何在 javascript 中执行此操作?任何提示都非常感谢!

【问题讨论】:

  • 具有 100% 可用性的资源是否在一个数组中有 10080 个数字,从 0 到 10079?
  • 是的,完全正确!谢谢,我会在问题中添加说明。
  • 这个问题太宽泛了。例如,“计算一个时隙”可能意味着许多不同的东西,而“显示任何可能的时隙”是一个更大的问题。 (您也想要 HTML/CSS 渲染吗?)请尝试将其缩小到问题的特定部分,显示您尝试过的内容以及遇到的问题。谢谢。
  • @FellowStranger 我已经付出了相当大的努力来回答你的问题。如果我可以为您提供更多帮助,请告诉我。

标签: javascript time timezone


【解决方案1】:

这是一个基本函数,它采用间隔长度 n 和一组数字(表示可用分钟数)。

通过调用getIntervals (30) (availableMinutes),我们将获得所有可用的时间段……

注意,下面的range 过程仅用于创建示例数据。您无需将其包含在您的程序中。

// getIntervals :: Number -> [Number] -> [{from: Number, to: Number}]
const getIntervals = n=> availability=> {
  // intervals computed by reducing availability ...
  let {slots} = availability.reduce(({slots, count, prev}, m)=> {
    // initialize state with first minute
    if (prev === undefined)
      return {slots, count, prev: m}
    // if current interval is empty, we must begin on an interval marker
    else if (count === 0 && prev % n !== 0)
      return {slots, count, prev: m}
    // if current minute is non-sequential, restart search for next interval
    else if (prev + 1 !== m)
      return {slots, count: 0, prev: m}
    // if current interval is complete, concat valid interval
    else if (count === n - 1)
      return {slots: [...slots, {from: m - n, to: prev}], count: 0, prev: m}
    // otherwise, current minute is sequential, add to current interval
    else
      return {slots, count: count + 1, prev: m}
  }, {slots: [], count: 0, prev: undefined})
  // return `slots` value from reduce computation
  return slots
}

// range :: Number -> Number -> [Number]
const range = min=> max=> {
  let loop = (res, n) => n === max ? res : loop([...res, n], n + 1)
  return loop([], min)
}

// create sample data
let availability = [
  ...range (55) (400),     // [55, 56, 57, ..., 399]
  ...range (3111) (3333),  // [3111, 3112, 3113 ,..., 3332]
  ...range (8888) (9000)   // [8888, 8889, 8890, ..., 8999]
]

// get the intervals
console.log(getIntervals (30) (availability))

只要确保这些分钟标记是UTC timestamp 的分钟偏移量,一切都会变得简单。显示这些分钟标记只需将 X 分钟添加到设置为资源周开始的 UTC 时间戳。

我们将在这里创建一个名为 timestampAddMinutes 的小程序,它采用分钟数并根据资源的时间戳将其转换为 Date 对象 week

然后,我们创建一个 getIntervalDates 过程,将其应用于数组中每个区间的每个 fromto

// timestampAddMinutes :: Date -> Number -> Date
const timestampAddMinutes = t=> m=> {
  let d = new Date(t.getTime())
  d.setMinutes(t.getMinutes() + m)
  return d
}

// getIntervalDates :: [{from: Minute, to: Minute}] -> [{from: Date, to: Date}]
const getIntervalDates = intervals => {
  return intervals.map(({from,to}) => ({
    from: timestampAddMinutes (week) (from),
    to: timestampAddMinutes (week) (to)
  }))
}
  
// sample timestamp for a resource
// Monday at midnight, timezone offset 0
let week = new Date("2016-08-08 00:00:00 +0000")

// interval result from previous code section
let intervals = [
  { from: 60, to: 89 }, { from: 90, to: 119 }, { from: 120, to: 149 },
  { from: 150, to: 179 }, { from: 180, to: 209 }, { from: 210, to: 239 },
  { from: 240, to: 269 }, { from: 270, to: 299 }, { from: 300, to: 329 },
  { from: 330, to: 359 }, { from: 360, to: 389 }, { from: 3120, to: 3149 },
  { from: 3150, to: 3179 }, { from: 3180, to: 3209 }, { from: 3210, to: 3239 },
  { from: 3240, to: 3269 }, { from: 3270, to: 3299 }, { from: 3300, to: 3329 },
  { from: 8910, to: 8939 }, { from: 8940, to: 8969 }
]

// convert intervals to dates
console.log(getIntervalDates(intervals))

您会看到fromto 分别转换为Date 对象。还要注意我的浏览器(在 EDT 时区中)如何自动转换 UTC 时间戳以显示在我当前的时区中。上面的输出将以TZ 字符串格式显示它们,但它们是完全可用的 Date 对象。这意味着您可以拨打他们的任何Date method 以获取具体的详细信息,例如星期几、小时或分钟等。

由于我们的getIntervals 函数运行正常,您还会看到每个间隔为 30 分钟,并从 :00:30 开始

输出

[ { from: Sun Aug 07 2016 21:00:00 GMT-0400 (EDT), to: Sun Aug 07 2016 21:29:00 GMT-0400 (EDT) },
  { from: Sun Aug 07 2016 21:30:00 GMT-0400 (EDT), to: Sun Aug 07 2016 21:59:00 GMT-0400 (EDT) },
  { from: Sun Aug 07 2016 22:00:00 GMT-0400 (EDT), to: Sun Aug 07 2016 22:29:00 GMT-0400 (EDT) },
  { from: Sun Aug 07 2016 22:30:00 GMT-0400 (EDT), to: Sun Aug 07 2016 22:59:00 GMT-0400 (EDT) },
  { from: Sun Aug 07 2016 23:00:00 GMT-0400 (EDT), to: Sun Aug 07 2016 23:29:00 GMT-0400 (EDT) },
  { from: Sun Aug 07 2016 23:30:00 GMT-0400 (EDT), to: Sun Aug 07 2016 23:59:00 GMT-0400 (EDT) },
  { from: Mon Aug 08 2016 00:00:00 GMT-0400 (EDT), to: Mon Aug 08 2016 00:29:00 GMT-0400 (EDT) },
  { from: Mon Aug 08 2016 00:30:00 GMT-0400 (EDT), to: Mon Aug 08 2016 00:59:00 GMT-0400 (EDT) },
  { from: Mon Aug 08 2016 01:00:00 GMT-0400 (EDT), to: Mon Aug 08 2016 01:29:00 GMT-0400 (EDT) },
  { from: Mon Aug 08 2016 01:30:00 GMT-0400 (EDT), to: Mon Aug 08 2016 01:59:00 GMT-0400 (EDT) },
  { from: Mon Aug 08 2016 02:00:00 GMT-0400 (EDT), to: Mon Aug 08 2016 02:29:00 GMT-0400 (EDT) },
  { from: Wed Aug 10 2016 00:00:00 GMT-0400 (EDT), to: Wed Aug 10 2016 00:29:00 GMT-0400 (EDT) },
  { from: Wed Aug 10 2016 00:30:00 GMT-0400 (EDT), to: Wed Aug 10 2016 00:59:00 GMT-0400 (EDT) },
  { from: Wed Aug 10 2016 01:00:00 GMT-0400 (EDT), to: Wed Aug 10 2016 01:29:00 GMT-0400 (EDT) },
  { from: Wed Aug 10 2016 01:30:00 GMT-0400 (EDT), to: Wed Aug 10 2016 01:59:00 GMT-0400 (EDT) },
  { from: Wed Aug 10 2016 02:00:00 GMT-0400 (EDT), to: Wed Aug 10 2016 02:29:00 GMT-0400 (EDT) },
  { from: Wed Aug 10 2016 02:30:00 GMT-0400 (EDT), to: Wed Aug 10 2016 02:59:00 GMT-0400 (EDT) },
  { from: Wed Aug 10 2016 03:00:00 GMT-0400 (EDT), to: Wed Aug 10 2016 03:29:00 GMT-0400 (EDT) },
  { from: Sun Aug 14 2016 00:30:00 GMT-0400 (EDT), to: Sun Aug 14 2016 00:59:00 GMT-0400 (EDT) },
  { from: Sun Aug 14 2016 01:00:00 GMT-0400 (EDT), to: Sun Aug 14 2016 01:29:00 GMT-0400 (EDT) } ]

单个参数的威力

只是为了展示getIntervals 的灵活性,如果您将资源的间隔长度更改为 75 分钟(而不是 30 分钟),它会是什么样子。

// using the same `availability` input data and a 75-minute interval length
let availableDates = getIntervalDates (getIntervals (75) (availability))

console.log(availableDates)

输出 - 每个间隔为 75 分钟,并从 75 分钟间隔标记开始

[ { from: Sun Aug 07 2016 21:15:00 GMT-0400 (EDT), to: Sun Aug 07 2016 22:29:00 GMT-0400 (EDT) },
  { from: Sun Aug 07 2016 22:30:00 GMT-0400 (EDT), to: Sun Aug 07 2016 23:44:00 GMT-0400 (EDT) },
  { from: Sun Aug 07 2016 23:45:00 GMT-0400 (EDT), to: Mon Aug 08 2016 00:59:00 GMT-0400 (EDT) },
  { from: Mon Aug 08 2016 01:00:00 GMT-0400 (EDT), to: Mon Aug 08 2016 02:14:00 GMT-0400 (EDT) },
  { from: Wed Aug 10 2016 00:30:00 GMT-0400 (EDT), to: Wed Aug 10 2016 01:44:00 GMT-0400 (EDT) },
  { from: Wed Aug 10 2016 01:45:00 GMT-0400 (EDT), to: Wed Aug 10 2016 02:59:00 GMT-0400 (EDT) } ]

替代实现

如果您必须在代码中添加 cmets,您可能做错了什么。因此,我在上述实现中严重依赖 cmets 来明确意图,从而打破了我自己的一条规则。

这个实现改为使用微小的程序,每个程序都有很明显的意图。此外,我使用了switch 语句,它允许我们在逻辑上对一些循环响应进行分组,而无需巨大的if 条件。

// getIntervals :: Number -> [Number] -> [{from: Number, to: Number}]
const getIntervals = n=> availability=> {
  let emptyCount = x=> x === 0
  let filledCount = x=> x + 1 === n
  let invalidPrev = x=> x === undefined
  let invalidMarker = x=> x % n !== 0
  let nonsequential = (x,y)=> x + 1 !== y
  let make = (from,to) => ({from,to})
  return availability.reduce(({acc, count, prev}, m)=> {
    switch (true) {
      case invalidPrev(prev):
      case emptyCount(count) && invalidMarker(prev):
      case nonsequential(prev, m):
        return {acc, count: 0, prev: m}
      case filledCount(count):
        return {acc: [...acc, make(m - n, prev)], count: 0, prev: m}
      default:
        return {acc, count: count + 1, prev: m}
    }
  }, {acc: [], count: 0, prev: undefined}).acc
}

// range :: Number -> Number -> [Number]
const range = min=> max=> {
  let loop = (res, n) => n === max ? res : loop([...res, n], n + 1)
  return loop([], min)
}

// create sample data
let availability = [
  ...range (55) (400),     // [55, 56, 57, ..., 399]
  ...range (3111) (3333),  // [3111, 3112, 3113 ,..., 3332]
  ...range (8888) (9000)   // [8888, 8889, 8890, ..., 8999]
]

// get the intervals
console.log(getIntervals (30) (availability))

现在您可以看到循环仅以 3 种方式之一响应。

  1. 它会重置count 并将prev 更新到当前分钟
  2. 它将一个新间隔(使用make)附加到acc 并执行count 重置
  3. 或者,作为default的情况,它增加count并更新prev

此外,现在程序已经被分解成小块,您可以看到其中一些可以轻松地在应用程序的其他区域中重用

// eq :: a -> a -> Bool
const eq = x=> y=> y === x

// isZero :: Number -> Bool
const isZero = eq (0)

// isUndefined :: a -> Bool
const isUndefined = eq (undefined)

// isSequential :: Number -> Number -> Bool
const isSequential = x=> eq (x + 1)

// isDivisibleBy :: Number -> Number -> Boolean
const isDivisibleBy = x=> y=> (isZero) (y % x)

// getIntervals :: Number -> [Number] -> [{from: Number, to: Number}]
const getIntervals = n=> availability=> {
  let make = (from,to) => ({from,to})
  return availability.reduce(({acc, count, prev}, m)=> {
    switch (true) {
      case isUndefined (prev):
      case isZero (count) && ! isDivisibleBy (n) (prev):
      case ! isSequential (prev) (m):
        return {acc, count: 0, prev: m}
      case isSequential (count) (n):
        return {acc: [...acc, make(m - n, prev)], count: 0, prev: m}
      default:
        return {acc, count: count + 1, prev: m}
    }
  }, {acc: [], count: 0, prev: undefined}).acc
}

是的,它的工作原理是一样的。这里的想法是,如果您要不止一次地做同样的事情,为什么不定义一个过程并使用它呢?您必须做到这一点,但您可以看到通用的微型程序如何降低getIntervals 的整体复杂性。

现在,任何时候你想检查两个数字是否是连续的,你都有一个过程——isSequential。与isDivisibleBy 相同——您可以轻松检查y 是否可以被x 整除,而无需在应用中需要的任何地方复制y % x === 0


而且,不管你信不信,即使我在这里给你的这些小程序也可以分解成更多部分

// comp :: (b -> c) -> (a -> b) -> (a -> c)
const comp = f=> g=> x=> f (g (x))

// comp2 :: (c -> d) -> (a -> b -> c) -> (a -> b -> d)
const comp2 = comp (comp) (comp)

// mod :: Number -> Number -> Number
const mod = x=> y=> y % x

// isDivisibleBy :: Number -> Number -> Boolean
const isDivisibleBy = comp2 (isZero) (mod)

底线是:不要对它发疯;知道何时足够抽象足以解决您的特定问题。只要知道那里有令人难以置信的技术可以将复杂性抽象掉。学习这些技术并知道何时应用它们。你会很高兴你做到了^_^

【讨论】:

  • 这是我见过的最彻底和写得最好的答案之一。它没有立即被接受的原因是因为我去睡觉了。很快就非常感谢您的努力!
  • @FellowStranger 乐于提供帮助。昨天我不得不匆忙完成该代码,因为我必须开始工作。我回来是为了给你进一步的解释,这是你应得的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-28
  • 1970-01-01
  • 1970-01-01
  • 2021-07-09
  • 1970-01-01
  • 2011-01-04
相关资源
最近更新 更多