5

我有一个相隔一个月的日期列表,因为所有日期都是“本月的第一个星期一”。在某些情况下缺少月份,所以我需要编写一个函数来确定所有日期是否是连续的

因此,例如,如果这是日期列表,则该函数将返回 true,因为所有项目都是“本月的第一个星期五”并且没有间隔。下面的示例将返回 true。

 var date = new DateTime(2013, 1, 4);
 var date1 = new DateTime(2013, 2, 1);
 var date2 = new DateTime(2013, 3, 1);
 var date3 = new DateTime(2013, 4, 5);

 var dateArray = new DateTime[]{date, date1, date2, date3};
 bool isConsecutive = IsThisListConsecutive(dateArray);

下面的示例将返回 false,因为即使它们也是“本月的第一个星期五”,它也缺少 2013 年 3 月的项目。

 var date = new DateTime(2013, 1, 4);
 var date1 = new DateTime(2013, 2, 1);
 var date3 = new DateTime(2013, 4, 5);

 var dateArray = new DateTime[]{date, date1, date3};
 bool isConsecutive = IsThisListConsecutive(dateArray);

所以我试图找出 IsThisListConsecutive() 方法的正确逻辑:

这是我的第一次尝试:(请注意,我已经预先知道所有日期都是一周中的同一天和一个月的同一周,所以我唯一要寻找的是缺少的插槽)

  private bool IsThisListConsecutive(IEnumerable<DateTime> orderedSlots)
    {
        DateTime firstDate = orderedSlots.First();
        int count = 0;
        foreach (var slot in orderedSlots)
        {
            if (slot.Month != firstDate.AddMonths(count).Month)
            {
                return false;
            }
            count++;
        }
        return true;
    }

如果列表从一年跨越到另一年,则上面的代码可以工作。我想就创建此函数的更好方法以及如何重写该行以处理跨越数年的日期获得任何建议。

4

6 回答 6

3

因此,为了实现这一点,我们将从一个简单的辅助方法开始,该方法接受一个序列并返回一个成对序列,这些对组成每个项目与其前一个项目。

public static IEnumerable<Tuple<T, T>> Pair<T>(this IEnumerable<T> source)
{
    T previous;
    using (var iterator = source.GetEnumerator())
    {
        if (iterator.MoveNext())
            previous = iterator.Current;
        else
            yield break;

        while(iterator.MoveNext())
        {
            yield return Tuple.Create(previous, iterator.Current);
            previous = iterator.Current;
        }
    }
}

我们还将使用这个简单的方法来确定两个日期是否在同一个月份:

public static bool AreSameMonth(DateTime first, DateTime second)
{
    return first.Year == second.Year 
        && first.Month == second.Month;
}

使用它,我们可以轻松地获取每个日期的月份,并查看它是否是上个月之后的月份。如果所有对都是如此,那么我们有连续的月份。

private static bool IsThisListConsecutive(IEnumerable<DateTime> orderedSlots)
{
    return orderedSlots.Pair()
        .All(pair => AreSameMonth(pair.Item1.AddMonths(1), pair.Item2));
}
于 2013-03-11T16:48:13.183 回答
2

我建议看一下TimeSpan结构。由于运算符重载,您可以TimeSpan通过减去两个日期来获得 a,然后接收TimeSpan表示两个日期之间差异的 a。

http://msdn.microsoft.com/en-us/library/system.timespan.aspx

于 2013-03-11T01:33:37.883 回答
2

注意:这是完全未经测试的,日期检查可能很糟糕或有些多余,但这是我现在能想到的最好的方法^^

public bool AreSameWeekdayEveryMonth(IEnumerable<DateTime> dates)
{
    var en = dates.GetEnumerator();
    if (en.MoveNext())
    {
        DayOfWeek weekday = en.Current.DayOfWeek;
        DateTime previous = en.Current;
        while (en.MoveNext())
        {
            DateTime d = en.Current;
            if (d.DayOfWeek != weekday || d.Day > 7)
                return false;
            if (d.Month != previous.Month && ((d - previous).Days == 28 || (d - previous).Days == 35))
                return false;
            previous = d;
        }
    }
    return true;
}
于 2013-03-11T01:52:05.967 回答
2

好的,当年份交叉时,您的代码不起作用,因为 1 月 1 日可能是一年的星期一和下一个星期二。如果我这样做,我会首先检查

a) 它们是每个月的同一天(使用 DateTime.DayOfWeek)

b) 它们是每个月的同一周* 使用扩展方法 DayOfMonth(请参阅链接)*在 .NET 中计算月份中的星期*

(你说你已经知道 a & b 是真的,所以让我们继续第三个条件)

c) 我们必须确定它们是否连续几个月

//order the list of dates & place it into an array for ease of looping
DateTime[] orderedSlots = slots.OrderBy( t => t).ToArray<DateTime>();


//create a variable to hold the date from the previous month
DateTime temp = orderedSlots[0];


for(i= 1; index < orderedSlots.Length; index++)
{
    if((orderedSlots[index].Month != temp.AddMonths(1).Month |
        orderedSlots[index].Year  != temp.AddMonths(1).Year)){
        return false;
    }

    previousDate =  orderedSlots[index];
}

return true;

如果您需要检查条件 a & b 以及添加更改 if 语句如下

    if( orderedSlots[index].Month != temp.AddMonths(1).Month |
        orderedSlots[index].Year  != temp.AddMonths(1).Year) |
        orderedSlots[index].DayOfWeek != temp.DayOfWeek      |
        orderedSlots[index].GetWeekOfMonth != temp.AddMonths(1).GetWeekOfMonth){
        return false;
    }

请记住,要使用获取月份扩展方法,您必须将代码包含 在 .NET 中计算月份的周数

于 2013-03-11T02:19:03.933 回答
1

好吧,这是对如何解决这个问题的初步想法。

首先,定义一个函数,将日期转换为与它们应该出现的顺序相对应的序数值。

int ToOrdinal(DateTime d, DateTime baseline) {
   if (d.Day <= 7
       && d.DayInWeek == baseline.DayInWeek) {
      // Since there is only one "First Friday" a month, and there are
      // 12 months in year we can easily compose the ordinal.
      // (As per default.kramer's comment, months normalized to [0,11].)
      return d.Year * 12 + (d.Month - 1);
   } else {
      // Was not correct "kind" of day -
      // Maybe baseline is Tuesday, but d represents Wednesday or
      // maybe d wasn't in the first week ..
      return 0;
   }
}

var dates = ..;
var baseline = dates.FirstOrDefault();
var ordinals = dates.Select(d => ToOrdinal(d, baseline));

然后,对于提供的日期,我们最终得到如下序数序列:

[24156 + 0, 24156 + 1, 24156 + 2, 24156 + 3]

[24156 + 0, 24156 + 1, /* !!!! */ 24156 + 3]

从这里开始,迭代列表并确保整数按顺序出现而没有间隙或停顿只是一件小事——也就是说,每个项目/整数都比前一个多一个。

于 2013-03-11T03:40:36.903 回答
0

我可能会误解您正在尝试做的事情,但我认为这会奏效,假设您不必处理古代日期。查看转换为“总月数”的日期是否有任何差距

int totalMonths = date.Year * 12 + (date.Month - 1);
于 2013-03-11T01:56:27.913 回答