如何在DateTime值数组中找到平均日期/时间

本文关键字:日期 时间 DateTime 数组 | 更新日期: 2023-09-27 18:14:39

如果我有一个DateTime值数组:

List<DateTime> arrayDateTimes;

如何找到它们之间的平均DateTime ?

例如,如果我有:

2003-May-21 15:00:00
2003-May-21 19:00:00
2003-May-21 20:00:00

海损应为:

2003-May-21 18:00:00

如何在DateTime值数组中找到平均日期/时间

如果列表比较大,可以使用下面的方法

var count = dates.Count;
double temp = 0D;
for (int i = 0; i < count; i++)
{
    temp += dates[i].Ticks / (double)count;
}
var average = new DateTime((long)temp);

这应该不会溢出,但它确实假设日期时间是有序的:

var first = dates.First().Ticks;
var average = new DateTime(first + (long) dates.Average(d => d.Ticks - first));

上面的语句实际上溢出了更大的列表和更大的间隙。我认为你可以使用秒范围。此外,这可能不是最有效的方法,但对我来说,完成10M个日期仍然相对较快。不知道是不是更容易读,YYMV.

var first = dates.First();
var average = first.AddSeconds(dates.Average(d => (d - first).TotalSeconds));

来源:摘自这里并稍作修改。

List<DateTime> dates = new List<DateTime>();
//Add dates
for (int i = 1; i <= 28; i++) //days
    for (int j = 1; j <= 12; j++) //month
        for (int k = 1900; k <= 2013; k++) //year
            dates.Add(new DateTime(k, j, i, 1, 2, 3)); //over 38000 dates

那么你可以这样做:

var averageDateTime = DateTime
                            .MinValue
                            .AddSeconds
                            ((dates
                                 .Sum(r => (r - DateTime.MinValue).TotalSeconds))
                                     / dates.Count);
Console.WriteLine(averageDateTime.ToString("yyyy-MMM-dd HH:mm:ss"));

输出:1956-Dec-29 06:09:25

最初文章中的代码是这样的:

double totalSec = 0;
for (int i = 0; i < dates.Count; i++)
{
    TimeSpan ts = dates[i].Subtract(DateTime.MinValue);
    totalSec += ts.TotalSeconds;
}
double averageSec = totalSec / dates.Count;
DateTime averageDateTime = DateTime.MinValue.AddSeconds(averageSec);

代码:

var count = dates.Count;
double temp = 0D;
for (int i = 0; i < count; i++)
{
    temp += dates[i].Ticks / (double)count;
}
var average = new DateTime((long)temp);

是错误的。平均值=(x1 + x2 +…)xN)/N不是(x1/N + x2/N +…xN/N)

试题:

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

class Program
{
    static void Main(string[] args)
    {
        List<DateTime> dates = new List<DateTime>(){
        new DateTime(2003, 5, 21, 16, 0, 0), new DateTime(2003, 5, 21, 17, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 17, 0, 0), new DateTime(2003, 5, 21, 18, 0, 0),
        new DateTime(2003, 5, 21, 19, 0, 0), new DateTime(2003, 5, 21, 20, 0, 0),
        new DateTime(2003, 5, 21, 16, 0, 0), new DateTime(2003, 5, 21, 17, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
    };
        var averageDate = dates.Average();
        Console.WriteLine(averageDate);
        Console.ReadKey();
    }
}
public static class Extensions
{
    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());
    }
}

使用双秒代替长刻度可以避免任何实际输入的溢出-这里的扩展方法。

    public static DateTime Average(this IEnumerable<DateTime> elements)
    {
        if (elements == null)
        {
            throw new ArgumentNullException(nameof(elements));
        }
        var enumerated = elements.ToArray(); //so we don't iterate a potentially one-use stream multiple times.
        if (!enumerated.Any())
        {
            throw new ArgumentException("Average of 0 elements is undefined", nameof(elements));
        }
        var epoch = enumerated.Min();
        var secondsSinceEpoch = enumerated.Select(d => (d - epoch).TotalSeconds).ToArray();
        var n = secondsSinceEpoch.LongLength;
        double totalSecondsSinceEpoch = secondsSinceEpoch.Sum();
        return epoch.AddSeconds(totalSecondsSinceEpoch / n);
    }
    [TestMethod]
    public void HugeDateAverage_DoesntThrow()
    {
        var epoch = new DateTime(1900,1,1);
        try
        {
            var dates = Enumerable.Range(1, 1_000_000_000)
             .Select(i => epoch.AddSeconds(i));
            var result = dates.Average();
        }
        catch (Exception ex)
        {
            Assert.Fail();
        }
    }

如果你真的想要退化,你可以检测溢出并在一半的元素上递归,小心奇数n的情况。这是未经测试的,但这里的想法:

    //NOT FOR ACTUAL USE - JUST FOR FUN
    public static DateTime AverageHuge(this IEnumerable<DateTime> elements)
    {
        if (elements == null)
        {
            throw new ArgumentNullException(nameof(elements));
        }
        var enumerated = elements.ToArray(); //so we don't iterate a potentially one-use stream multiple times.
        if (!enumerated.Any())
        {
            throw new ArgumentException("Average of 0 elements is undefined", nameof(elements));
        }
        var epoch = enumerated.Min();
        var secondsSinceEpoch = enumerated.Select(d => (d - epoch).TotalSeconds).ToArray();
        var n = secondsSinceEpoch.LongLength;
        if (n > int.MaxValue)
        {
            //we could actually support more by coding Take+Skip with long arguments.
            throw new NotSupportedException($"only int.MaxValue elements supported");
        }
        try
        {
            double totalSecondsSinceEpoch = secondsSinceEpoch.Sum(); //if this throws, we'll have to break the problem up
            //otherwise we're done.
            return epoch.AddSeconds(totalSecondsSinceEpoch / n);
        }
        catch (OverflowException) { } //fall out of this catch first so we don't throw from a catch block
        //Overengineering to support large lists whose totals would be too big for a double.
        //recursively get the average of each half of values.
        int pivot = (int)n / 2;
        var avgOfAvgs = (new []
        {
            enumerated.Take(pivot).AverageHuge(),
            enumerated.Skip(pivot).Take(pivot).AverageHuge()
        }).AverageHuge();
        if (pivot * 2 == n)
        {   // we had an even number of elements so we're done.
            return avgOfAvgs;
        }
        else
        {   //we had an odd number of elements and omitted the last one.
            //it affects the average by 1/Nth its difference from the average (could be negative)
            var adjust = ((enumerated.Last() - avgOfAvgs).TotalSeconds) / n;
            return avgOfAvgs.AddSeconds(adjust);
        }
        
    }

所有这些涉及long的解决方案都会导致溢出。

我提议:

public static DateTime DateTimeAverage(IEnumerable<DateTime> dates) => DateTime.FromOADate(dates.Select(d => d.ToOADate()).Average());

neouser99的答案是正确的。它通过进行增量平均来防止溢出。

然而,David jimsamnez的答案是错误的,因为它没有处理溢出和他对公式的误解。

平均值=(x1 + x2 +…)xN)/N不是(x1/N + x2/N +…xN/N)

这是同一个公式。这是使用分配律的简单数学:

2(x + y) = 2x + 2y

平均公式与将总和乘以1/N相同。或者将每个单独的X乘以1/N,然后将它们相加。

1/n (x1 + x2 + ... xn)

由分配律得到:

x1/n + x2/n + ... xn/n

这里有一些关于分配律的信息

他的答案也很糟糕,因为它不像公认的答案那样防止溢出。

我本想评论他的回答,但我没有足够的声望。