Swift中如何将每日PriceData数组转换为月度数组(含数据缺失处理)
解决每日价格数据转月度数据(含日期缺失处理)
核心思路
- 先过滤无效日期数据并按时间排序,确保数据基础可靠
- 保留原始数据的首尾条目,保证时间范围的完整性
- 生成首尾日期之间的所有月份,对每个月份优先选取目标日期(如20日)的数据
- 若目标日期无数据,则选取当月最接近目标日期的条目作为替代
- 合并所有数据并去重排序,得到最终月度数据集
完整实现代码
首先添加日期处理工具和PriceData扩展:
import Foundation // 假设你的PriceData定义如下 struct PriceData: Identifiable, Hashable { // 实现Hashable用于去重 let id = UUID() let date: String let close: Double } private let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd" formatter.locale = Locale(identifier: "en_US_POSIX") formatter.timeZone = TimeZone(secondsFromGMT: 0) return formatter }() extension PriceData { var dateValue: Date? { dateFormatter.date(from: date) } }
然后实现核心转换函数:
func extractData(_ dataArray: [PriceData]) -> [PriceData] { // 过滤无效日期数据并按时间排序 let validData = dataArray.filter { $0.dateValue != nil } .sorted { $0.dateValue! < $1.dateValue! } guard validData.count >= 2 else { return validData } let firstData = validData.first! let lastData = validData.last! guard let startDate = firstData.dateValue, let endDate = lastData.dateValue else { return [firstData, lastData] } // 生成首尾日期之间的所有月份 func monthsBetween(start: Date, end: Date) -> [Date] { guard let calendar = Calendar(identifier: .gregorian) else { return [] } var months: [Date] = [] var currentMonth = calendar.date(byAdding: .month, value: 1, to: start)! while currentMonth < end { months.append(currentMonth) currentMonth = calendar.date(byAdding: .month, value: 1, to: currentMonth)! } return months } let middleMonths = monthsBetween(start: startDate, end: endDate) // 为指定月份选取最优数据(优先20日,无则取最接近的) func bestPriceDataForMonth(targetMonth: Date, from data: [PriceData]) -> PriceData? { guard let calendar = Calendar(identifier: .gregorian) else { return nil } let components = calendar.dateComponents([.year, .month], from: targetMonth) guard let startOfMonth = calendar.date(from: components), let endOfMonth = calendar.date(byAdding: DateComponents(month: 1, day: -1), to: startOfMonth) else { return nil } // 筛选当月所有有效数据 let monthlyData = data.filter { guard let date = $0.dateValue else { return false } return date >= startOfMonth && date <= endOfMonth } guard !monthlyData.isEmpty else { return nil } // 优先查找当月20日的数据 let targetDay = 20 if let targetDate = calendar.date(bySetting: .day, value: targetDay, of: startOfMonth), let targetData = monthlyData.first(where: { $0.dateValue == targetDate }) { return targetData } // 无20日数据时,取最接近20日的条目 let targetDate = calendar.date(bySetting: .day, value: targetDay, of: startOfMonth) ?? startOfMonth return monthlyData.min(by: { guard let d1 = $0.dateValue, let d2 = $1.dateValue else { return false } let diff1 = abs(d1.timeIntervalSince(targetDate)) let diff2 = abs(d2.timeIntervalSince(targetDate)) return diff1 < diff2 }) } // 收集中间月份的数据 var middleData: [PriceData] = [] for month in middleMonths { if let data = bestPriceDataForMonth(targetMonth: month, from: validData) { middleData.append(data) } } // 合并、去重并排序 let allData = [firstData] + middleData + [lastData] let uniqueData = Array(Set(allData)) .sorted { $0.dateValue! < $1.dateValue! } return uniqueData }
可调整的策略
- 如果遇到整月无数据的极端情况,可以在
bestPriceDataForMonth函数中添加逻辑,比如返回上一个月的最后一条数据,或者跳过该月 - 可以替换选取规则:比如不取最接近20日的,而是取当月最后一条可用数据,只需修改
bestPriceDataForMonth中的返回逻辑为monthlyData.last即可 - 目标日期可自定义(比如改为每月15日),只需修改
targetDay的值
内容的提问来源于stack exchange,提问作者SPR
相关产品推荐
相关产品推荐

