JS 如何计算某个时间在某个时间段内

遇到一个问题,太难算了。

const arr = [
  {
    start: '00:00',
    end: '00:30',
    count: 0
  },
  {
    start: '00:30',
    end: '01:00',
    count: 0
  },
  {
    start: '01:00',
    end: '01:30',
    count: 0
  },
  {
    start: '01:30',
    end: '02:00',
    count: 0
  },
  {
    start: '02:00',
    end: '02:30',
    count: 0
  },
  {
    start: '23:30',
    end: '24:00',
    count: 0
  },
];

const time = {
  start: '10:00', // 早上10点
  end: '01:00' // 凌晨1点
};

一共24个时间段,每隔30分钟一个时间段, 中间给我省略了, 给出一个开始和结束时间是否在列表的时间段内。

这个东西算了半天都算不出来。太复杂了,求大神给个代码公式。

上面的代码正确是, 10点到24点 count 都为1, 凌晨0点到1点都为1

阅读 4.6k
5 个回答
// 比较时间大小
function compare(st, et){
    let sts = st.split(':')
    let ets = et.split(':')
    let s = sts[0] * 60 + Number(sts[1])
    let e = ets[0] * 60 + Number(ets[1])
    return s >= e
}

// 时间阈值
const tarr = compare(time.start, time.end) ? [{start: "00:00", end: time.end}, {start: time.start, end: '24:00'}] : [time]

arr.forEach(item => {
    item.count = tarr.some(citem => compare(item.start, citem.start) && compare(citem.end, item.end)) ? 1 : 0
})
已参与了 SegmentFault 思否社区 10 周年「问答」打卡 ,欢迎正在阅读的你也加入。

我感觉还不对,你自己测试一下吧

// 判断时间对象是不是,正常顺序
let isSortTime=(time={start:'00:00',end:'00:00'})=>{
    return time.start <= time.end;
}
// 判断t1时间段是不是在t2时间段里面。
let isIncludeTime=(t1={start:'00:00',end:'00:00'}, t2={start:'00:00',end:'00:00'})=>{
    if(isSortTime(t1)){
        return t1.start >= t2.start && t1.end<= t2.end;
    } else {
        return (t1.start <= t2.start ||  t1.end > t2.start);
    }
    
}
let calc = (arr, time)=>{
    return arr.map(item=>{
        return {...item,count:Number(isIncludeTime(time,item))};
    });
}
calc(arr, time);

如果你的arr就是30分钟一段,那等同于arr = [{start: 0, end: 0.5, count: 0}, ...];
先检查time,如果跨0点了转换为 timeArr = [{start: 10, end: 23.5},{start: 0, end: 1}],如果不哭啊0点则转换为timeArr = [{start: 8, end: 9.5}]

timeArr.map(time => {
    arr.map(item => {
        // 如果不是(开始和结束都小于开始时间或开始和结束都大于结束时间)
        if (! ((item.start < time.start && item.end < time.start) || (item.start > time.end && item.end > time.end))) {
            item.count ++;
        }    
    });
});

已参与了 SegmentFault 思否社区 10 周年「问答」打卡 ,欢迎正在阅读的你也加入。

给你个思路,但不是答案。按照这个思路,可以写出来答案。

直接看代码和注释

function countSegment(start, end) {
    // 先把开始/结束时间转换成分钟(数值)
    let [mStart, mEnd] = [start, end].map(t => {
        const [h, m] = t.split(":").map(Number);
        return h * 60 + m;
    });

    // 结束时间小于开始时间,说明是第二天,加一天的分钟数 1440
    if (mEnd < mStart) {
        mEnd += 1440;
    }

    // 以半小时为单位,开始时间向前取整对齐,结束时间向后取整对齐
    mStart -= mStart % 30;
    // 如果 00:30 要算在 [00:00, 00:30] 和 [00:30, 01:00] 两个区间
    // 那么按左闭右开算法,需要把结时间算成 01:00,
    // 也就是说它属于 [00:30, 01:00)
    // 比如 30 分,那么对齐后就是 (30 + 1) / 30,向上取整再 * 30,得到 60
    mEnd = Math.ceil((mEnd + 1) / 30) * 30;

    // 上面那句,如果在临界点只属于前面那个区间的时候,应该写成
    // mEnd = Math.ceil(mEnd / 30) * 30 - 1;

    return Array
        .from(
            // 按区间经历了多少个半小时,产生数组
            Array((mEnd - mStart) / 30),
            // 计算数组每个元素的起止时间(分钟)
            (_, i) => [mStart + i * 30, mStart + (i + 1) * 30]
        )
        .map(([start, end]) => ({
            start: [Math.floor(start / 60) % 24, start % 60].map( n => `${n}`.padStart(2, "0")).join(":"),
            end: [Math.floor(end / 60) % 24, end % 60].map( n => `${n}`.padStart(2, "0")).join(":"),
            count: 1
        }));
}

console.log(countSegment("10:00", "01:00"));

输出

[
  { start: '10:00', end: '10:30', count: 1 },
  { start: '10:30', end: '11:00', count: 1 },
  { start: '11:00', end: '11:30', count: 1 },
  { start: '11:30', end: '12:00', count: 1 },
  { start: '12:00', end: '12:30', count: 1 },
  { start: '12:30', end: '13:00', count: 1 },
  ....
  { start: '23:00', end: '23:30', count: 1 },
  { start: '23:30', end: '00:00', count: 1 },
  { start: '00:00', end: '00:30', count: 1 },
  { start: '00:30', end: '01:00', count: 1 },
  { start: '01:00', end: '01:30', count: 1 }
]

之所以说是思路,看运行结果,基本可以达到要求,但是并不是题主需要的结果。

按题主需要的结果,是要按 00:0024:00,产生 48 个元素的数组,然后可以根据上述思路计算索引号来给 count 计数。先想想,我晚点再来给答案。


下方答案

function countSegment(start, end) {
    // 先生成 00:00 ~ 24:00 的 48 个区间
    // 准备好这个数组,后面计算 count 后,它就是要反回的结果
    const segments = Array
        .from(
            { length: 48 },
            // [起始时间小时数,起始时间分钟数]
            (_, i) => [(i - i % 2) / 2, i % 2 * 30]
        )
        .map(([hour, minute]) => ({
            start: [hour, minute].map(n => `${n}`.padStart(2, "0")).join(":"),
            end: [hour + minute / 30, 30 - minute].map(n => `${n}`.padStart(2, "0")).join(":"),
            count: 0
        }));

    // 先把开始/结束时间转换成分钟(数值)
    let [mStart, mEnd] = [start, end].map(t => {
        const [h, m] = t.split(":").map(Number);
        return h * 60 + m;
    });

    // 结束时间小于开始时间,说明是第二天,加一天的分钟数 1440
    if (mEnd < mStart) {
        mEnd += 1440;
    }

    // 以半小时为单位,开始时间向前取整对齐,结束时间向后取整对齐
    mStart -= mStart % 30;
    // 如果 00:30 要算在 [00:00, 00:30] 和 [00:30, 01:00] 两个区间
    // 那么按左闭右开算法,需要把结时间算成 01:00,
    // 也就是说它属于 [00:30, 01:00)
    // 比如 30 分,那么对齐后就是 (30 + 1) / 30,向上取整再 * 30,得到 60
    mEnd = Math.ceil((mEnd + 1) / 30) * 30;

    // 上面那句,如果在临界点只属于前面那个区间的时候,应该写成
    // mEnd = Math.ceil(mEnd / 30) * 30 - 1;

    // 只需要计算时间范围所覆盖的时间段区间序号即可
    const startIndex = mStart / 30;

    Array
        .from(
            Array((mEnd - mStart) / 30),
            // 计算所覆盖的序号
            (_, i) => startIndex + i
        )
        // 根据序列列表,逐个计数
        .forEach(i => {
            segments[i % 48].count++;
        });

    return segments;
}

console.log(countSegment("10:00", "01:00"));
[
  { start: '00:00', end: '00:30', count: 1 },
  { start: '00:30', end: '01:00', count: 1 },
  { start: '01:00', end: '01:30', count: 1 },
  ... 都是 0 
  { start: '10:00', end: '10:30', count: 1 },
  { start: '10:30', end: '11:00', count: 1 },
  { start: '11:00', end: '11:30', count: 1 },
  { start: '11:30', end: '12:00', count: 1 },
  ... 这里省略的都是 1
  { start: '22:30', end: '23:00', count: 1 },
  { start: '23:00', end: '23:30', count: 1 },
  { start: '23:30', end: '24:00', count: 1 }
]

已参与了 SegmentFault 思否社区 10 周年「问答」打卡 ,欢迎正在阅读的你也加入。

这里提供一种判断的思路:

由于时间是字符格式,直接比较大小有点困难,可以先将时间转换为分钟(即整数),然后再进行比较;对于字符串格式的时间区间,同理,也可以先转换为整数格式的分钟区间。

以1天24小时来说,不论时间区间的跨度为30分钟,或者60分钟,区别只在于基准时间区间的长度为48或者24,具体判断的方式是相同的。

下面给出具体的代码:

/** 时间区间计数 */
function count(startTime, endTime) {
    let basicTimeArrays = [...basicTimeArray()];
    let targetTimeArrays = judgeOverZero(convertTimeRangeToIntArray(startTime, endTime));

    basicTimeArrays.forEach(basicRange => {
        // 如果目标时间在基准时间区间内,则 count = 1
        // 情形一:边界时间同时算做在两个区间,比如10:00既算做在[09:30, 10:00]区间,也算做在[10:00, 10:30]区间
        let inRange = targetTimeArrays.some(targetRange => !((basicRange.start < targetRange[0] && basicRange.end < targetRange[0])
            || (basicRange.start > targetRange[1] && basicRange.end > targetRange[1])));
        
        // 情形二:边界时间只算做在右侧区间,比如10:00只算做在[10:00, 10:30]区间
        // let inRange = targetTimeArrays.some(targetRange => !((basicRange.start <= targetRange[0] && basicRange.end <= targetRange[0])
        //     || (basicRange.start >= targetRange[1] && basicRange.end >= targetRange[1])));
        if (inRange) {
            basicRange.count = 1;
        }
    });
    
    return basicTimeArrays;
}

/** 生成基准时间区间,这里的时间跨度为30分钟,那么24小时的话就存在48个区间 */
function basicTimeArray() {
    let timeArrays = new Array(48).fill({});
    timeArrays.forEach((item, index) => timeArrays[index] = {start: index * 30, end: (index + 1) * 30, count: 0});

    return timeArrays;
}

/** 将字符串时间转换为整数(分钟) */
function convertTimeToInt(time) {
    let times = time.split(":");

    return parseInt(times[0]) * 60 + parseInt(times[1]);
}

/** 将字符串时间区间转换为整数区间 */
function convertTimeRangeToIntArray(startTime, endTime) {
    return [convertTimeToInt(startTime), convertTimeToInt(endTime)];
}

/** 判断时间区间是否跨过了0点,如果跨过了0点则需要分割为两个时间区间 */
function judgeOverZero(timeArray) {
    let convertedArrays = [];
    if (timeArray[0] < timeArray[1]) {
        convertedArrays.push(timeArray);
        return convertedArrays;
    }

    // 如果时间跨越了0点,则分割为两个数组
    convertedArrays.push([0, timeArray[1]]);
    convertedArrays.push([timeArray[0], 1440]);

    return convertedArrays;
}

console.info(count('10:00', '24:00'));
console.info(count('10:00', '01:00'));

已参与了 SegmentFault 思否社区 10 周年「问答」打卡 ,欢迎正在阅读的你也加入。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题