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

基于.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方法存在多处逻辑漏洞:

  1. 时段覆盖不全:未处理起始时间在傍晚(16:00-21:00)以及夜晚跨次日多个时段的场景
  2. 跨时段判断单一:仅判断是否跨到下一个时段,不支持跨3个及以上时段的情况
  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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 09:57:01