如何在C#中使用LINQ计算指定日期范围的总阅读时长?
解决跨天阅读会话的每日时长统计问题
现有代码的核心问题是仅按会话的开始日期分组统计,跨天的会话会被全部计入开始日期,无法拆分到结束日期。要解决这个问题,我们需要先将每个阅读会话拆分为对应日期的分段时长,再按日期聚合求和。
实现步骤
- 编写辅助方法,将单个
ReadingSession拆分为按日期划分的时长条目(覆盖同一天、跨两天、跨多天的所有场景); - 使用
SelectMany将所有会话的分段条目展开; - 过滤目标日期范围(当前日期和前一天);
- 按日期分组并计算总时长。
完整代码实现
public class ReadingSession { public DateTime StartAt { get; set; } public DateTime EndAt { get; set; } } // 辅助方法:拆分会话到对应日期的时长 private static IEnumerable<(DateOnly Date, TimeSpan Duration)> SplitSessionByDate(ReadingSession session) { var startDate = DateOnly.FromDateTime(session.StartAt); var endDate = DateOnly.FromDateTime(session.EndAt); // 会话在同一天的情况 if (startDate == endDate) { yield return (startDate, session.EndAt - session.StartAt); yield break; } // 计算第一天的时长:从开始时间到当天结束 var endOfStartDay = startDate.ToDateTime(TimeOnly.MaxValue); yield return (startDate, endOfStartDay - session.StartAt); var currentDate = startDate.AddDays(1); // 处理中间的完整天数(跨3天及以上的情况) while (currentDate < endDate) { yield return (currentDate, TimeSpan.FromDays(1)); currentDate = currentDate.AddDays(1); } // 计算最后一天的时长:从当天开始到会话结束 var startOfEndDay = endDate.ToDateTime(TimeOnly.MinValue); yield return (endDate, session.EndAt - startOfEndDay); } // 统计逻辑 public static void CalculateDailyReadingTime() { DateTime currentDate = DateTime.UtcNow; DateTime previousDate = currentDate.AddDays(-1); var targetDates = new HashSet<DateOnly> { DateOnly.FromDateTime(currentDate), DateOnly.FromDateTime(previousDate) }; // 示例数据 var readingSessions = new List<ReadingSession> { new ReadingSession { StartAt = DateTime.Parse("2023-05-01 20:00:00"), EndAt = DateTime.Parse("2023-05-01 21:00:00") }, new ReadingSession { StartAt = DateTime.Parse("2023-05-01 23:00:00"), EndAt = DateTime.Parse("2023-05-02 01:30:00") } }; var result = readingSessions .SelectMany(session => SplitSessionByDate(session)) .Where(item => targetDates.Contains(item.Date)) .GroupBy(item => item.Date) .Select(group => new { Date = group.Key, TotalDuration = TimeSpan.FromSeconds(group.Sum(item => item.Duration.TotalSeconds)) }) .ToList(); // 输出结果验证 foreach (var item in result) { Console.WriteLine($"日期:{item.Date:yyyy-MM-dd},总阅读时长:{item.TotalDuration.TotalHours:F1}小时"); } }
结果验证
运行上述代码后,输出结果与预期完全一致:
- 2023-05-01:2.0小时
- 2023-05-02:1.5小时
关键说明
- 使用
DateOnly(.NET 6+支持)处理日期,避免DateTime的时间部分干扰分组逻辑; - 辅助方法覆盖了所有会话场景,确保跨天时长被精准拆分到对应日期;
SelectMany将拆分后的所有日期条目展开,保证每个日期的时长被独立统计;- 用
HashSet存储目标日期,提升过滤操作的效率。
内容的提问来源于stack exchange,提问作者byteram
相关产品推荐
相关产品推荐

