如何使用LINQ查询对DateTime字段进行平均值计算?

5

我有一个数据库,在DateTime字段中有以下记录:

2012-04-13 08:31:00.000
2012-04-12 07:53:00.000
2012-04-11 07:59:00.000
2012-04-10 08:16:00.000
2012-04-09 15:11:00.000
2012-04-08 08:28:00.000
2012-04-06 08:26:00.000

我想运行一个Linq to SQL查询,以获取上述记录的平均时间。 我尝试了以下操作:

(From o In MYDATA Select o.SleepTo).Average()

由于“SleepTo”是一个日期时间字段,所以在使用Average()时会出错。如果我尝试获取整数的平均值,则上面的linq查询将有效。

我需要做什么才能使其适用于日期时间?


你有一个SleepFrom值吗? - yamen
我会尝试为此创建自己的扩展方法。 - frenchie
3个回答

10

每个DateTime实际上都是以“滴答数”存储的。 DateTime的属性被定义为“自公元1年1月1日午夜12:00:00以来已经过去的100纳秒间隔数量”。(请参见http://msdn.microsoft.com/en-us/library/system.datetime.ticks.aspx

您可以将DateTime转换为ticks,然后进行平均值计算,最后再将其转换回datetime。

var averageTicks = (long) dates.Select(d => d.Ticks).Average();
var averageDate = new DateTime(averageTicks);

使用您的数据结构和格式,它会看起来像这样:

var averageTicks = (long)(from o in MYDATA select o.SleepTo.Ticks).Average();
var averageDate = new DateTime(averageTicks);

如果您想获取每个SleepTo值的平均时间(忽略日期组件),您可以获取仅时间的滴答数:

var averageTicks = (long)(from o in MYDATA select o.SleepTo.TimeOfDay.Ticks).Average();
var averageTime = new TimeSpan(averageTicks);

这样做不会得到期望的答案,因为问题涉及SleepTo的平均时间,而不是平均DateTime。我强烈建议将SleepTo转换为自午夜以来的TimeSpan(甚至是Ticks),然后对其进行平均,否则所有答案都是错误的。 - yamen
代码示例和Jade原帖中的最后一个问题让我相信他/她正在寻求平均DateTime的帮助。根据您的建议,@yamen,我添加了一个新的片段,用于计算时间组件的平均值。谢谢! - Duane Theriot
7
Ticks 的数量很多时,似乎 .Average(...) 会引发溢出异常。我猜它试图将所有长整型值相加?(看起来是转换为双精度浮点型) - jocull

4

以下是几个可以帮助解决此问题的扩展方法... 如果您在列表中有很多 DateTime,使用 LINQ 对 ticks(long 变量)进行平均值计算时,核心问题是可能会溢出。

    public static long Average(this IEnumerable<long> longs)
    {
        long count = longs.Count();
        long mean = 0;
        foreach (var val in longs)
        {
            mean += val / count;
        }
        return mean;
    }

    public static DateTime Average(this IEnumerable<DateTime> dates)
    {
        return new DateTime(dates.Select(x => x.Ticks).Average());
    }

谢谢。当项目数量足够大时,这是对已接受答案的改进。 - user585968
非常感谢!改进得很好! - klutch1991
我认为由于您在val/count中使用了整数除法,这可能会积累误差。尝试使用new[] { 3l, 5l, 7l }的平均值 - 正确的平均值是5,而您得到的是4。我认为您应该使用double进行中间计算。当扩展Linq时,我也更喜欢使用Linq,因为它更难理解 :) public static long Average(this IEnumerable<long> longs) { var count = longs.Count(); return (long)(longs.Aggregate(0.0, (r, l) => r += (double)l / count)); } - NetMage
在这个正在平均的“Tick”精度级别上,平均数中的轻微舍入误差可能并不重要。但我不能确定!在来回转换为“double”时可能会有额外的精度损失。 - jocull

1

数据库LINQ提供程序似乎不理解如何在绝对日期上做平均。如果您仔细思考一下,这是有道理的(平均值是总和除以计数-总和是什么?)。

因此,如果您无法运行以下内容:

(From o In MYDATA Select o.SleepTo).Sum()

那么您也无法使用.Average()

由于您想要的实际上是SleepTo的平均时间,因此您需要将日期的时间组件作为TimeSpan(时间减去午夜)获取并进行平均处理。 您是否碰巧有SleepFrom

同时,您可能会发现这篇文章很有启发性:LINQ平均时间跨度?


我尝试了Sum函数,但它也不支持日期。我的样本数据是我起床的时间。我正在尝试获取我平均起床的时间。 - Jade
这就是我的观点,求和不起作用 - 因此平均值也不起作用。你最好的办法是剥离日期组件,只获取时间。将其转换为从午夜开始的时间跨度,然后就可以得到平均值。这将给出您的平均醒来时间(上午)。请参阅我发布的链接。 - yamen
我更新了我的回答,询问您是否有“SleepFrom”(这会使一切变得简单),或者您只想知道从午夜以来的时间?也就是说,您想知道平均醒来时间还是平均睡眠时间? - yamen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接