基于.NET 6 LINQ计算不同时段阅读时长占比的问题求助
问题描述
需要基于用户阅读会话的起止时间,统计不同时段的阅读会话分布占比(跨时段的会话需计入涉及的所有时段)。已定义时段范围:
- 黎明:4:30-5:30
- 上午:5:30-12:00
- 下午:12:00-16:00
- 傍晚:16:00-21:00
- 夜晚:21:00-次日4:30
现有.NET 6代码计算结果不符合预期,尤其黎明时段统计异常,需修正逻辑并提供可行代码片段。
现有核心代码
占比计算方法
private async Task<ProgressDto> GetReadingTimePercentagesAsync() { //... var readingPeriods = readingSessions .SelectMany(SplitSessionsIntoPeriods) .ToList(); var night = readingPeriods.Count(t => t == ReadingSessionPeriod.Night); var dawn = readingPeriods.Count(t => t == ReadingSessionPeriod.Dawn); var morning = readingPeriods.Count(t => t == ReadingSessionPeriod.Morning); var afternoon = readingPeriods.Count(t => t == ReadingSessionPeriod.Afternoon); var evening = readingPeriods.Count(t => t == ReadingSessionPeriod.Evening); var totalTimes = readingPeriods.Count(); return new() { Afternoon = afternoon / (double)totalTimes * 100, Dawn = dawn / (double)totalTimes * 100, Evening = evening / (double)totalTimes * 100, Morning = morning / (double)totalTimes * 100, Night = night / (double)totalTimes * 100 }; }
会话拆分方法
private List<ReadingSessionPeriod> SplitSessionsIntoPeriods(ChallengeKoranReadingProgress session) { var dawnStart = new TimeSpan(4, 30, 0); //凌晨4:30 var morningStart = new TimeSpan(5, 30, 0); //上午5:30 var afternoonStart = new TimeSpan(12, 0, 0); //中午12:00 var eveningStart = new TimeSpan(16, 0, 0); //下午4:00 var nightStart = new TimeSpan(19, 0, 0); //晚上9:00 var list = new List<ReadingSessionPeriod>(); if (session.StartAt.TimeOfDay >= nightStart) { //跨至下一时段 if(session.EndAt.TimeOfDay > dawnStart) { list.Add(ReadingSessionPeriod.Dawn); } list.Add(ReadingSessionPeriod.Night); } else if(session.StartAt.TimeOfDay >= dawnStart) { if (session.EndAt.TimeOfDay > morningStart) { list.Add(ReadingSessionPeriod.Morning); } list.Add(ReadingSessionPeriod.Dawn); } else if(session.StartAt.TimeOfDay >= morningStart) { if (session.EndAt.TimeOfDay > afternoonStart) { list.Add(ReadingSessionPeriod.Afternoon); } list.Add(ReadingSessionPeriod.Morning); } else if (session.StartAt.TimeOfDay >= afternoonStart) { if (session.EndAt.TimeOfDay > eveningStart) { list.Add(ReadingSessionPeriod.Evening); } list.Add(ReadingSessionPeriod.Afternoon); } return list; }
实体定义
public class ReadingSession { public DateTime StartAt {get;set;} public DateTime EndAt {get;set;} } public enum ReadingSessionPeriod { Night, Dawn, Morning, Afternoon, Evening }
问题分析与修复
现有SplitSessionsIntoPeriods方法存在多处逻辑漏洞:
- 时段覆盖不全:未处理起始时间在傍晚(16:00-21:00)以及夜晚跨次日多个时段的场景
- 跨时段判断单一:仅判断是否跨到下一个时段,不支持跨3个及以上时段的情况
- 黎明时段误判:夜晚跨时段时,仅判断结束时间是否大于黎明开始时间,未考虑是否超出黎明结束时间(5:30),导致错误计入
修复后的核心逻辑
重新设计时段匹配逻辑,通过枚举所有时段并判断会话与时段的时间交集,确保所有跨时段场景都被覆盖:
修复后的会话拆分方法
private List<ReadingSessionPeriod> SplitSessionsIntoPeriods(ReadingSession session) { // 定义各时段的时间范围(夜晚跨午夜,需特殊处理) var periodDefinitions = new Dictionary<ReadingSessionPeriod, (TimeSpan Start, TimeSpan End)> { { ReadingSessionPeriod.Dawn, (new TimeSpan(4,30,0), new TimeSpan(5,30,0)) }, { ReadingSessionPeriod.Morning, (new TimeSpan(5,30,0), new TimeSpan(12,0,0)) }, { ReadingSessionPeriod.Afternoon, (new TimeSpan(12,0,0), new TimeSpan(16,0,0)) }, { ReadingSessionPeriod.Evening, (new TimeSpan(16,0,0), new TimeSpan(21,0,0)) }, { ReadingSessionPeriod.Night, (new TimeSpan(21,0,0), new TimeSpan(4,30,0)) } }; var result = new List<ReadingSessionPeriod>(); var sessionStart = session.StartAt.TimeOfDay; var sessionEnd = session.EndAt.TimeOfDay; var isCrossMidnight = sessionEnd < sessionStart; // 判断会话是否跨午夜 foreach (var (period, (periodStart, periodEnd)) in periodDefinitions) { bool isOverlap; if (period == ReadingSessionPeriod.Night) { // 夜晚时段:跨午夜的会话只要覆盖夜晚任意部分,或非跨午夜会话完全在夜晚范围内 isOverlap = isCrossMidnight ? (sessionStart >= periodStart || sessionEnd <= periodEnd) : (sessionStart >= periodStart && sessionEnd <= periodEnd); } else { // 非夜晚时段:跨午夜会话只要结束时间在时段内,或非跨午夜会话与时段有交集 isOverlap = isCrossMidnight ? (sessionStart >= periodStart || sessionEnd <= periodEnd) : (sessionStart < periodEnd && sessionEnd > periodStart); } if (isOverlap) { result.Add(period); } } return result; }
优化后的占比计算方法
原计算逻辑可简化为LINQ聚合操作,提升可读性:
private async Task<ProgressDto> GetReadingTimePercentagesAsync(List<ReadingSession> readingSessions) { var periodCounts = readingSessions .SelectMany(SplitSessionsIntoPeriods) .GroupBy(p => p) .ToDictionary(g => g.Key, g => g.Count()); var totalCount = periodCounts.Values.Sum(); return new ProgressDto { Dawn = periodCounts.TryGetValue(ReadingSessionPeriod.Dawn, out var dawn) ? dawn / (double)totalCount * 100 : 0, Morning = periodCounts.TryGetValue(ReadingSessionPeriod.Morning, out var morning) ? morning / (double)totalCount * 100 : 0, Afternoon = periodCounts.TryGetValue(ReadingSessionPeriod.Afternoon, out var afternoon) ? afternoon / (double)totalCount * 100 : 0, Evening = periodCounts.TryGetValue(ReadingSessionPeriod.Evening, out var evening) ? evening / (double)totalCount * 100 : 0, Night = periodCounts.TryGetValue(ReadingSessionPeriod.Night, out var night) ? night / (double)totalCount * 100 : 0 }; }
关键改进点
- 全时段覆盖:明确所有时段的时间范围,包括跨午夜的夜晚时段
- 通用交集判断:对每个时段单独判断会话是否与之重叠,支持跨多个时段的场景(如从20:00读到次日6:00,会计入傍晚、夜晚、黎明、上午四个时段)
- 黎明时段修正:通过精确的时间交集判断,避免误将跨黎明但未覆盖黎明时段的会话计入
内容的提问来源于stack exchange,提问作者byteram
相关产品推荐
相关产品推荐

