You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何判断多时间范围重叠?求Objective-C/Swift多人可用时间交集函数

Swift实现:找出所有人员共同空闲的时间段

我来给你提供一个Swift的实现方案,用来计算所有指定人员都有空的时间段。这个方案既支持输入每个人的空闲时间段(直接求交集),也支持输入每个人的忙碌时间段(先转换为空闲再求交集)。

第一步:定义时间区间结构体

首先我们需要一个TimeRange结构体来表示时间区间,同时提供从字符串初始化的便捷方法:

import Foundation

struct TimeRange {
    let start: Date
    let end: Date
    
    // 基础初始化器,确保开始时间早于结束时间
    init?(start: Date, end: Date) {
        guard start < end else {
            return nil
        }
        self.start = start
        self.end = end
    }
    
    // 从字符串初始化,默认使用"yyyy-MM-dd HH:mm"格式
    init?(startString: String, endString: String, dateFormat: String = "yyyy-MM-dd HH:mm") {
        let formatter = DateFormatter()
        formatter.dateFormat = dateFormat
        guard let start = formatter.date(from: startString),
              let end = formatter.date(from: endString),
              start < end else {
            return nil
        }
        self.start = start
        self.end = end
    }
}

第二步:扩展时间区间的交集计算

为TimeRange添加扩展,用来计算两个时间区间的交集:

extension TimeRange {
    // 计算与另一个区间的交集,无交集则返回nil
    func intersection(with other: TimeRange) -> TimeRange? {
        let intersectStart = max(start, other.start)
        let intersectEnd = min(end, other.end)
        return intersectStart < intersectEnd ? TimeRange(start: intersectStart, end: intersectEnd) : nil
    }
}

第三步:核心函数——求所有人的共同空闲时间段

这个函数接收每个人的空闲时间段数组,返回所有人员都有空的时间段:

func findCommonFreeTimeRanges(for peopleFreeRanges: [[TimeRange]]) -> [TimeRange] {
    guard !peopleFreeRanges.isEmpty else {
        return []
    }
    
    // 初始候选集为第一个人的所有空闲区间
    var commonRanges = peopleFreeRanges[0]
    
    // 遍历剩下的每个人的空闲区间
    for personRanges in peopleFreeRanges.dropFirst() {
        var newCommonRanges: [TimeRange] = []
        
        // 逐个计算当前候选区间与当前人员区间的交集
        for commonRange in commonRanges {
            for personRange in personRanges {
                if let intersection = commonRange.intersection(with: personRange) {
                    newCommonRanges.append(intersection)
                }
            }
        }
        
        // 如果中途没有交集了,直接返回空数组
        guard !newCommonRanges.isEmpty else {
            return []
        }
        
        commonRanges = newCommonRanges
    }
    
    // 对结果按开始时间排序
    return commonRanges.sorted { $0.start < $1.start }
}

第四步:测试你的示例

用你提供的示例数据来验证:

// 构建三个人的空闲时间段
guard let p1 = TimeRange(startString: "2016-01-01 12:00", endString: "2016-05-01 03:00"),
      let p2 = TimeRange(startString: "2016-01-01 03:00", endString: "2016-05-01 03:00"),
      let p3 = TimeRange(startString: "2016-01-01 03:00", endString: "2016-04-30 13:31") else {
    fatalError("无效的时间区间格式")
}

let peopleFreeRanges = [[p1], [p2], [p3]]
let commonRanges = findCommonFreeTimeRanges(for: peopleFreeRanges)

// 格式化输出结果
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
for range in commonRanges {
    print("start: \(dateFormatter.string(from: range.start)), end: \(dateFormatter.string(from: range.end))")
}

运行这段代码会输出:

start: 2016-04-30 12:00, end: 2016-04-30 13:31

完全符合你的预期结果。

额外扩展:支持从忙碌时间段计算

如果你的输入是每个人的忙碌时间段,我们可以先把忙碌时间段转换为空闲时间段,再计算共同空闲区间:

extension Array where Element == TimeRange {
    // 合并重叠或相邻的时间区间
    func merged() -> [TimeRange] {
        guard !isEmpty else {
            return []
        }
        
        let sortedRanges = sorted { $0.start < $1.start }
        var mergedRanges = [sortedRanges[0]]
        
        for current in sortedRanges.dropFirst() {
            guard let last = mergedRanges.last else { continue }
            if current.start <= last.end {
                // 合并区间
                let newRange = TimeRange(start: last.start, end: max(last.end, current.end))!
                mergedRanges.removeLast()
                mergedRanges.append(newRange)
            } else {
                mergedRanges.append(current)
            }
        }
        
        return mergedRanges
    }
    
    // 计算区间的补集(即空闲时间段),需要指定一个总时间范围
    func complement(in totalRange: TimeRange) -> [TimeRange] {
        let mergedBusy = self.merged()
        var freeRanges: [TimeRange] = []
        var currentStart = totalRange.start
        
        for busy in mergedBusy {
            if currentStart < busy.start {
                freeRanges.append(TimeRange(start: currentStart, end: busy.start)!)
            }
            currentStart = busy.end
        }
        
        if currentStart < totalRange.end {
            freeRanges.append(TimeRange(start: currentStart, end: totalRange.end)!)
        }
        
        return freeRanges
    }
}

// 从忙碌时间段计算共同空闲的函数
func findCommonFreeTimeRangesFromBusyRanges(for peopleBusyRanges: [[TimeRange]], totalTimeRange: TimeRange) -> [TimeRange] {
    // 把每个人的忙碌时间段转成空闲时间段
    let peopleFreeRanges = peopleBusyRanges.map { $0.complement(in: totalTimeRange) }
    return findCommonFreeTimeRanges(for: peopleFreeRanges)
}

比如,如果你的示例数据是忙碌时间段,你可以这样调用:

// 假设总时间范围是从2016-01-01 00:00到2016-05-01 04:00
guard let totalRange = TimeRange(startString: "2016-01-01 00:00", endString: "2016-05-01 04:00"),
      let p1Busy = TimeRange(startString: "2016-01-01 12:00", endString: "2016-05-01 03:00"),
      let p2Busy = TimeRange(startString: "2016-01-01 03:00", endString: "2016-05-01 03:00"),
      let p3Busy = TimeRange(startString: "2016-01-01 03:00", endString: "2016-04-30 13:31") else {
    fatalError("无效的时间区间")
}

let peopleBusyRanges = [[p1Busy], [p2Busy], [p3Busy]]
let commonFreeRanges = findCommonFreeTimeRangesFromBusyRanges(for: peopleBusyRanges, totalTimeRange: totalRange)

// 输出结果
for range in commonFreeRanges {
    print("start: \(dateFormatter.string(from: range.start)), end: \(dateFormatter.string(from: range.end))")
}

这样就能得到同样的结果啦。

内容的提问来源于stack exchange,提问作者John

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:25:20