.net 如何在C#中使用LINQ计算特定日期范围的总持续时间?

t30tvxxf  于 2023-05-08  发布在  .NET
关注(0)|答案(4)|浏览(152)

我有一个对象列表,它们具有StartAtEndAtDateTime类型的属性,表示阅读会话的开始和结束时间。我想计算一下每天阅读的总时间,考虑到一次阅读可以跨越两天。例如,如果阅读课程从5月1日晚上11:00开始,到5月2日凌晨1:30结束,我想在5月1日计算1小时的阅读,在5月2日计算1.5小时的阅读。如何在C#中使用LINQ实现这一点?

DateTime currentDate  = DateTime.UtcNow;
DateTime previousDate = currentDate.AddDays(-1);

var result = readingSessions
    .Where(x => x.StartAt.Date == currentDate.Date || x.StartAt.Date == previousDate.Date)
    .GroupBy(x => x.StartAt.Date)
    .Select(x => new
        {
            Date = x.Key,
            Sum = TimeSpan.FromSeconds(x.Sum(y => (y.EndAt - y.StartAt).TotalSeconds))
        })
    .ToList();
public class ReadingSession
{
   public DateTime StartAt {get;set;}
   public DateTime EndAt {get;set;}
}

详情

开始时间结束时间
2023-05-01 08:00:00 PM2023-05-01 09:00:00 PM
2023-05-01 11:00:00 PM2023-05-02 01:30:00 AM

如何计算?

假设我们想根据给定的StartAt和EndAt时间计算5月1日和5月2日的总阅读持续时间。
对于第一行,用户在5月1日下午8:00开始阅读,并在同一天晚上9:00结束。因此,5月1日的总阅读时长为1小时。
对于第二行,用户在5月1日的11:00 PM开始阅读,并在5月2日的1:30 AM结束。在这种情况下,我们需要将阅读时间分为两部分:一个是5月1日的,一个是5月2日的。
5月1日,用户从晚上11:00到晚上11:59阅读,总共1小时。5月2日,用户从12:00 AM阅读到1:30 AM,总共1. 5小时。因此,5月1日的总阅读时长为1小时,5月2日的总阅读时长为1.5小时。

结果应该是什么样的?

5月1日,阅读器从晚上8点开始,到晚上9点结束,这给了我们1小时的持续时间。他们也从晚上11点开始阅读,一直持续到凌晨12点,这又增加了一个小时,5月1日总共有2个小时。
5月2日,读者从凌晨12:00开始阅读,到凌晨1:30结束,这给了我们1. 5小时的持续时间。
因此,5月1日的总阅读时间为2小时,5月2日为1.5小时。

liwlm1x9

liwlm1x91#

下面是我解决的方法:首先,我确定ReadingSession是否跨越多天。如果它跨越了几天,我会把它分开。剩下的就和你已经做的差不多了。

internal class Program
    {
        static void Main(string[] args)
        {
            List<ReadingSession> readingSessions = new List<ReadingSession>()
            {
                new ()
                { 
                    StartAt = new DateTime(2023, 5, 1, 23, 0, 0),
                    EndAt = new DateTime(2023, 5, 2, 1, 0, 0)
                },
                new ()
                {
                    StartAt = new DateTime(2023, 5, 1, 22, 0, 0),
                    EndAt = new DateTime(2023, 5, 1, 23, 0, 0)
                },
            };

            var dailyDurations = readingSessions
                .SelectMany(rs => 
                {
                    if (rs.SingleDay)
                    { 
                        return new[] { rs };
                    }
                    else
                    {
                        // split it into two sessions,
                        // one for each day
                        return new[]
                        {
                            new ReadingSession { StartAt = rs.StartAt, EndAt = rs.StartAt.Date.AddDays(1) },
                            new ReadingSession { StartAt = rs.EndAt.Date, EndAt = rs.EndAt }
                        };
                    }
                })
                .GroupBy(rs => rs.StartAt.Date)
                .Select(g => new
                {
                    Date = g.Key, Sum = g.Sum(rs => (rs.EndAt - rs.StartAt).TotalMinutes)
                })
                .ToList();
        }

        public class ReadingSession
        {
            public DateTime StartAt { get; set; }
            public DateTime EndAt { get; set; }
            public bool SingleDay => EndAt.Date == StartAt.Date;
        }
xienkqul

xienkqul2#

以下是我的解决方案:

var readingSessions = new List<ReadingSession>()
        {
            new ReadingSession(){StartAt = DateTime.Parse("2023-05-01 10:22:59 PM"), EndAt = DateTime.Parse("2023-05-02 01:00:00 AM")},
            new ReadingSession(){StartAt = DateTime.Parse("2023-05-01 10:22:59 PM"), EndAt = DateTime.Parse("2023-05-01 11:00:00 PM")}
        };

        IEnumerable <(int DayOfYear,TimeSpan TotalDuration)> result = readingSessions.Select(session => 
        (
            session.EndAt.DayOfYear,
            new TimeSpan(
                            readingSessions.Where(r => r.EndAt.DayOfYear == session.EndAt.DayOfYear)
                                           .Sum(rs => rs.EndAt.DayOfYear == session.StartAt.DayOfYear?
                                                        rs.EndAt.Subtract(rs.StartAt).Ticks :
                                                        rs.EndAt.TimeOfDay.Ticks)
                        )
        ));
        result.ToList()
              .ForEach(r => Console.WriteLine($"DayOfYear: {r.DayOfYear} TotalDuration: {r.TotalDuration}"));

有几点需要考虑:
1.最好是将和提取到不同的函数中
1.如果阅读会话的持续时间超过2天,则代码将仅对开始日期和结束日期的持续时间求和

mv1qrgav

mv1qrgav3#

谢谢你澄清你的问题。我认为你想要的是一种方法,将一个单一的ReadingSession和分裂成 * 每日 * 阅读会话。这将为您提供仅在一天内发生的会话列表。
例如,如果一个阅读会话持续了5天,下面将返回5个单独的阅读会话的列表,每天一个,第一个在开始时间开始,最后一个在结束时间结束,其他的都在一天的开始/结束:

static List<ReadingSession> SplitIntoDailySessions(ReadingSession session)
{
    if (session == null) return null;

    // Capture initial start value (so as not to modify 'session' later)
    var start = session.StartAt;
    var result = new List<ReadingSession>();

    // While the end date is greater than the start date, create a new session 
    // from the start date until the beginning of the next day and add it to
    // our reault, then reset the start date to the beginning of the next day.
    while(session.EndAt.Date > start.Date)
    {
        result.Add(new ReadingSession { StartAt = start, 
            EndAt = start.Date.AddDays(1) });

        start = start.Date.AddDays(1);
    }

    // Now our start and end are on the same date, so we can add the final result
    result.Add(new ReadingSession { StartAt = start, EndAt = session.EndAt });

    return result;
}

现在,我们可以把我们最初的阅读课程列表,分成每天的课程,就像这样:

List<ReadingSession> dailyReadingSessions = readingSessions
    .SelectMany(rs => SplitIntoDailySessions(rs))
    .ToList();

现在,您的原始代码应该可以正常工作了,因为dailyReadingSessions中的每个阅读会话只跨越一个Date

dwbf0jvd

dwbf0jvd4#

假设阅读会话总是少于24小时(例如跨度最多两天),那么您可以拆分跨越午夜的任何阅读会话,然后使用代码对结果进行分组和计算。我省略了Where方法,因为我不确定你希望最终的过滤器是什么:

var result = readingSessions
                .SelectMany(r => r.EndAt.Date > r.StartAt.Date // spans two days?
                                ? new[] { // reading time on first day
                                          new ReadingSession { StartAt = r.StartAt, EndAt = r.EndAt.Date },
                                          // reading time on second day
                                          new ReadingSession { StartAt = r.EndAt.Date, EndAt = r.EndAt }
                                        }
                                : new[] { r })
                .GroupBy(x => x.StartAt.Date) // group all reading time for each day
                .Select(x => new {
                    Date = x.Key,
                    Sum = TimeSpan.FromSeconds(x.Sum(y => (y.EndAt - y.StartAt).TotalSeconds))
                })
                .ToList();

注意:这不是非常有效,因为它为每一天的阅读会话生成一个数组,然后将其丢弃-您可以通过扫描列表两次并使用union将一天的会话与两天的会话合并,以牺牲时间为代价提高空间效率,但这可能不值得复杂性。这让我觉得SplitUnion方法可能很好。

相关问题