5

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

List<DateTime> dtlist = new List<DateTime>();
列表 1 值 =“1985 年 7 月 25 日”
列表 2 值 =“1956 年 12 月 31 日”       
列表 3 值 =“1978 年 2 月 21 日”
列表 4 值 =“2005 年 3 月 18 日”

输出顺序应为:

3月18日
7月25日
12 月 31 日
2月21日

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

4

8 回答 8

7

请注意,由于(例如)闰日,我们不能将所有日期都投影到当前年份的日期。变成是错误的February 29th, 2000February 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 正确地指出,此代码将对您的日期列表进行两次排序:一次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-地铁
7月25日
12月31日
2月21日
于 2013-03-01T14:37:17.270 回答
2
//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();
于 2013-03-01T15:45:49.740 回答
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. 演示(>1000 个随机日期以证明它正在工作,包括闰年)

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

于 2013-03-01T14:44:02.090 回答
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));
        }

于 2020-09-25T13:27:02.917 回答
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即将到来的生日的完整 s,您可以dd-MMM根据需要对其进行格式化。另请注意,在 .NET 4.0 之前,您需要

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

代替

.Select(birthdate.AddYears)
于 2013-03-01T15:01:34.783 回答
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);

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

于 2013-03-01T17:18:30.863 回答
-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();            
于 2013-03-01T14:22:11.183 回答
-1

我不认为你可以用你收集的日期来做到这一点。诀窍是生日都是过去的特定日期,但您希望能够计算出该日期将在来年再次出现。这意味着您需要首先将每个生日(例如1985 年7 月 25 日)转换为该月/日组合的下一次出现(例如2013 年7 月 25 日)。只有这样,其他答案中提到的 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);
}

此方法确定下一个生日的时间,然后对这些日期进行排序。

于 2013-03-01T14:34:58.533 回答