在LINQ中按即将到来的生日订购日期

时间:2013-03-01 14:15:47

标签: c# linq

我想在网格中显示即将到来的生日。我有随机顺序的值列表,我希望使用LINQ从今天的日期(假设当前日期 3月1日)获取订单。

List<DateTime> dtlist = new List<DateTime>();
List 1 value = "25-July-1985"
List 2 value = "31-Dec-1956"       
List 3 value = "21-Feb-1978"
List 4 value = "18-Mar-2005"

输出顺序应为:

18-Mar
25-July
31-Dec
21-Feb

注意:我在这里没有使用任何数据库来获取值。

8 个答案:

答案 0 :(得分:7)

请注意,由于(例如)闰日,我们无法将所有日期投影到当前年份的日期。将February 29th, 2000变为February 29th, 2013将是错误的。

让我们先按日期和日期订购日期:

var ordered = from dt in dtlist
              orderby dt.Month, dt.Day
              select dt;

现在我们需要查找当前日期之前的所有日期(无论年份):

private static bool IsBeforeNow(DateTime now, DateTime dateTime)
{
    return dateTime.Month < now.Month
        || (dateTime.Month == now.Month && dateTime.Day < now.Day);
}

我最初的建议是跳过/取出我们想要的日期并将它们连接在一起:

var now = DateTime.Now;
var afterNow = ordered.SkipWhile(dt => IsBeforeNow(now, dt));
var beforeNow = ordered.TakeWhile(dt => IsBeforeNow(now, dt));

var birthdays = Enumerable.Concat(afterNow, beforeNow);

但是,用户Rawling正确pointed out此代码将订购您的日期列表两次:评估afterNow时为一次,beforeNow时为评估。他建议按IsBeforeNow订购日期更为优雅,因为它不需要跳过/取走和连接。不再需要先前的代码块,然后LINQ查询部分变为:

var now = DateTime.Now;
var birthdays = from dt in dtlist
                orderby IsBeforeNow(now, dt), dt.Month, dt.Day
                select dt;

birthdays是你的结果。这已被纳入以下代码:


完整代码:

static void Main(string[] args)
{
    var dtlist = new[]{
        DateTime.Parse("25-July-1985"),
        DateTime.Parse("31-Dec-1956"),
        DateTime.Parse("21-Feb-1978"),
        DateTime.Parse("18-Mar-2005")
    };

    var now = DateTime.Now;
    var birthdays = from dt in dtlist
                    orderby IsBeforeNow(now, dt), dt.Month, dt.Day
                    select dt;

    foreach (var dt in birthdays)
    {
        Console.WriteLine(dt.ToString("dd-MMM"));
    }
    Console.ReadLine();
}

private static bool IsBeforeNow(DateTime now, DateTime dateTime)
{
    return dateTime.Month < now.Month
        || (dateTime.Month == now.Month && dateTime.Day < now.Day);
}

打印:

18-mrt
25-jul
31-dec
21-feb

答案 1 :(得分:1)

List.Sort中的自定义比较器委托工作:

DateTime now = DateTime.Today;
dtlist.Sort((d1, d2) =>
    {
        if (DateTime.IsLeapYear(d1.Year) && d1.Month == 2 && d1.Day == 29)
            d1 = d1.Date.AddMilliseconds(-1);
        if (DateTime.IsLeapYear(d2.Year) && d2.Month == 2 && d2.Day == 29)
            d2 = d2.Date.AddMilliseconds(-1);
        var dtTrunc1 = new DateTime(now.Year, d1.Month, d1.Day, d1.Hour, d1.Minute, d1.Second, d1.Millisecond);
        var dtTrunc2 = new DateTime(now.Year, d2.Month, d2.Day, d2.Hour, d2.Minute, d2.Second, d2.Millisecond);
        TimeSpan diff1 = dtTrunc1 - now;
        TimeSpan diff2 = dtTrunc2 - now;
        if (diff1.Ticks >= 0 && diff2.Ticks >= 0 || diff1.Ticks < 0 && diff2.Ticks < 0)
            return diff1.Ticks.CompareTo(diff2.Ticks);
        else if (diff1.Ticks < 0 && diff2.Ticks >= 0)
            return int.MaxValue;
        else
            return int.MinValue;
    });

1. Demo(您的样本数据)

2. Demo(&gt; 1000个随机日期,证明它​​有效,包括闰年)

此方法不需要创建新列表,它会对原始列表进行排序。如果DateTime变量也包含它,它也会按时间排序。

答案 2 :(得分:1)

//A mock up value for comparison (as DayOfYear has a leap year issue)
int doy = DateTime.Today.Month*31 + DateTime.Today.Day;

var results = dtlist.OrderBy( a => 
              (a.DateOfBirth.Month * 31 + a.DateOfBirth.Day) +
              (a.DateOfBirth.Month * 31 + a.DateOfBirth.Day > doy ? 0 : 400 ))
             .ToList();

答案 3 :(得分:1)

任何范围和所有情况下的解决方案:

        static void Main(string[] args)
        {
            List<DateTime> birthdays = new List<DateTime>() {
            new DateTime(1977,1,29),
            new DateTime(1977,1,30),
            new DateTime(1977,1,31)
            };

            var daysFrom = 30;
            var start = new DateTime(DateTime.Now.Year,DateTime.Now.Month,DateTime.Now.Day);
            start = new DateTime(2020, 12, 31); // border test case
            var last = start.AddDays(daysFrom);
            var yearSwitch = last.Year - start.Year;
            var res = birthdays.Where(bday =>
                {
                    var bn = new DateTime(start.Year, bday.Month, bday.Day);
                    if (bday.DayOfYear < daysFrom)
                    {
                        bn = bn.AddYears(yearSwitch);
                    }
                    return bn >= start && bn <= last;
                }
            ).ToList();
            Console.WriteLine("List:{0}", string.Join(",", res));
        }

答案 4 :(得分:0)

有很多方法可以解决错误!这是我的尝试:

  • 对于每次出生日期,计算下一次出生的时间 - 这被定义为第一个生日(通过连续添加一年到达的日期)生日)即今天或之后
  • 按升序排序下一次出生
  • 返回列表

示例代码:

var dtlist  = new[]{
    DateTime.Parse("25-July-1985"),
    DateTime.Parse("31-Dec-1956"),
    DateTime.Parse("21-Feb-1978"),
    DateTime.Parse("18-Mar-2005")
};

var today = DateTime.Today;

var nextbirthDays = 
    from birthdate in dtlist
    select 
        Enumerable.Range(0, 1000)
        .Select(birthdate.AddYears)
        .First(birthday => birthday >= today);

var ordered = 
    nextBirthdays
    .OrderBy(d => d)
    .ToList();

请注意,这会返回即将到来的生日的完整DateTime,您可以根据需要将其格式化为dd-MMM。另请注意,在.NET 4.0之前,您需要

.Select(age => birthdate.AddYears(age))

而不是

.Select(birthdate.AddYears)

答案 5 :(得分:0)

其他人表示DayOfYear有一个leap year issue。实际上我找不到任何问题:2月29日有DayOfYear == 60而3月1日有DayOfYear == 60非闰年和DayOfYear == 61闰年,两者都是正确的。因此,您可以使用DayOfYear进行过滤,但是您不能将其用于排序,因为这会混淆2月29日与3月1日,3月1日和3月2日等等。但是按月和日排序是可以的:

var upcomingBirthdays = birthdays.Where(dt => dt.DayOfYear >= DateTime.Today.DayOfYear).OrderBy(dt => dt.Month).ThenBy(dt => dt.Day);

var upcomingBirthdays = from birthday in birthdays
                        where birthday.DayOfYear >= DateTime.Today.DayOfYear
                        orderby birthday.Month, birthday.Day
                        select birthday;

如果某人的生日是2月29日,它将在3月1日的非闰年出现,它将在闰年的那一天出现。这就是我在生日日历中的期望。

<强>更新

我的实施仍然存在一个问题:它不会显示明年即将到来的生日。

由于丢失的生日恰好是原始列表的其余部分,您可以为它们执行相同的操作,然后将结果连接起来:

var thisYear = from birthday in birthdays
               where birthday.DayOfYear >= DateTime.Today.DayOfYear
               orderby birthday.Month, birthday.Day
               select birthday;

var nextYear = from birthday in birthdays
               where birthday.DayOfYear < DateTime.Today.DayOfYear
               orderby birthday.Month, birthday.Day
               select birthday;

var upcomingBirthdays = thisYear.Concat(nextYear);

这样,结果始终按照即将到来的生日顺序包含原始列表的所有条目。

答案 6 :(得分:-1)

var currentYear = DateTime.Today.Year;
var birthDays = dtList.Select(d => Tuple.Create(d, new DateTime(currentYear, d.Month, d.Day)))
    .OrderBy(tuple => tuple.Item2)
    .Where(tuple => tuple.Item2 > DateTime.Today)
    .Select(tuple => tuple.Item1)
    .ToList();            

答案 7 :(得分:-1)

我认为您不能按原样收集日期。诀窍在于,出生日期是过去的具体日期,但您希望能够确定该日期何时会在来年重现。这意味着您需要首先将每个生日(例如25-七月 - 1985 )转换为该月/日组合的下一次出现(例如25-七月 - 2013 ) 。只有这样,其他答案中提到的OrderBy才有效。

public IEnumerable<DateTime> UpcomingBirthdays(IEnumerable<DateTime> birthDates)
{
    return birthDates.Select(
        bd => new DateTime( 
            ((bd.Month >= DateTime.Today.Month || (bd.Month == DateTime.Today.Month && bd.Day >= DateTime.Today.Day)) ? DateTime.Today.Year : DateTime.Today.Year + 1),
            bd.Month,
            bd.Day)
        ).OrderBy(bd => bd);
}

此方法确定接下来每个生日的时间,然后命令那些日期。