【问题标题】:How to get every monday of given month?如何获得给定月份的每个星期一?
【发布时间】:2017-07-11 16:44:01
【问题描述】:

如何获取给定月份的每个“星期一”?

一个例子;
输入:2017 年 7 月 11 日 (11.07.2017)
输出:(3,10,17,24,31)
3.7.2017 星期一
10.7.2017 星期一
17.7.2017 星期一
24.7.2017 星期一
31.7.2017

我可以获得给定月份的天数(对于 2017 年 7 月,它是 31 天)。然后如果 dayOfWeek 等于星期一,则编写一个迭代(for loop a.e.),添加到列表中。但这不是好的代码,因为 for 循环将工作 31 次。应该有更好的算法来归档目标。

我正在使用 C# .net 框架 4.6

更新
感谢大家的帮助,在我得到了一些答案之后;我用一个简单而肮脏的基准代码测试了所有代码,以找到更快的算法。

这是我的基准代码;

using System;
using System.Collections.Generic;
using System.Linq;

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Columns;
using BenchmarkDotNet.Attributes.Jobs;
using BenchmarkDotNet.Engines;

using X.Core.Helpers;

namespace X.ConsoleBenchmark
{
    [SimpleJob(RunStrategy.ColdStart, targetCount: 5)]
    [MinColumn, MaxColumn, MeanColumn, MedianColumn]
    public class LoopTest
    {
        [Benchmark]
        public void CalculateNextSalaryDateWithLoopAllDays()
        {
            DateTime date = new DateTime(2017, 7, 3);
            const int oneMillion = 1000000;
            for (int i = 0; i < oneMillion; i++)
            {
                List<DateTime> allXDaysInMonth = date.GetAllXDaysInMonthWithLoopAllDays(DayOfWeek.Tuesday);
                if (allXDaysInMonth != null && allXDaysInMonth.FirstOrDefault().Day != 4)
                {
                    throw new ApplicationException("Calculate method has errors.");
                }
            }
        }

        [Benchmark]
        public void CalculateNextSalaryDate()
        {
            DateTime date = new DateTime(2017, 7, 3);
            const int oneMillion = 1000000;
            for (int i = 0; i < oneMillion; i++)
            {
                List<DateTime> allXDaysInMonth = date.GetAllXDaysInMonth(DayOfWeek.Tuesday);
                if (allXDaysInMonth != null && allXDaysInMonth.FirstOrDefault().Day != 4)
                {
                    throw new ApplicationException("Calculate method has errors.");
                }
            }
        }

        [Benchmark]
        public void Maccettura_GetAllDayOfWeekPerMonth()
        {
            DateTime exampleDate = new DateTime(2017, 7, 3);
            const int oneMillion = 1000000;
            for (int i = 0; i < oneMillion; i++)
            {
                var date = new DateTime(exampleDate.Year, exampleDate.Month, 1);

                if (date.DayOfWeek != DayOfWeek.Thursday)
                {
                    int daysUntilDayOfWeek = ((int)DayOfWeek.Thursday - (int)date.DayOfWeek + 7) % 7;
                    date = date.AddDays(daysUntilDayOfWeek);
                }

                List<DateTime> days = new List<DateTime>();

                while (date.Month == exampleDate.Month)
                {
                    days.Add(date);
                    date = date.AddDays(7);
                }

                if (days.FirstOrDefault().Day != 6)
                {
                    throw new ApplicationException("Calculate method has errors.");
                }
            }
        }

        [Benchmark]
        public void ScottHannen_GetWeekdaysForMonth()
        {
            DateTime exampleDate = new DateTime(2017, 7, 3);
            const int oneMillion = 1000000;
            for (int i = 0; i < oneMillion; i++)
            {
                IEnumerable<DateTime> days = ScottHannen_GetDaysInMonth(exampleDate).Where(day => day.DayOfWeek == DayOfWeek.Thursday);

                if (days.FirstOrDefault().Day != 6)
                {
                    throw new ApplicationException("Calculate method has errors.");
                }
            }
        }

        private IEnumerable<DateTime> ScottHannen_GetDaysInMonth(DateTime date)
        {
            var dateLoop = new DateTime(date.Year, date.Month, 1);

            while (dateLoop.Month == date.Month)
            {
                yield return dateLoop;
                dateLoop = dateLoop.AddDays(1);
            }
        }

        [Benchmark]
        public void Trioj_GetWeekdaysForMonth()
        {
            DateTime exampleDate = new DateTime(2017, 7, 3);
            const int oneMillion = 1000000;
            for (int i = 0; i < oneMillion; i++)
            {
                IEnumerable<DateTime> days = Trioj_GetDatesInMonthByWeekday(exampleDate, DayOfWeek.Thursday);

                if (days.FirstOrDefault().Day != 6)
                {
                    throw new ApplicationException("Calculate method has errors.");
                }
            }
        }

        private List<DateTime> Trioj_GetDatesInMonthByWeekday(DateTime date, DayOfWeek dayOfWeek)
        {
            // We know the first of the month falls on, well, the first.
            var first = new DateTime(date.Year, date.Month, 1);
            int daysInMonth = DateTime.DaysInMonth(date.Year, date.Month);

            // Find the first day of the week that matches the requested day of week.
            if (first.DayOfWeek != dayOfWeek)
            {
                first = first.AddDays(((((int)dayOfWeek - (int)first.DayOfWeek) + 7) % 7));
            }

            // A weekday in a 31 day month will only occur five times if it is one of the first three weekdays.
            // A weekday in a 30 day month will only occur five times if it is one of the first two weekdays.
            // A weekday in February will only occur five times if it is the first weekday and it is a leap year.
            // Incidentally, this means that if we subtract the day of the first occurrence of our weekday from the 
            // days in month, then if that results in an integer greater than 27, there will be 5 occurrences.
            int maxOccurrences = (daysInMonth - first.Day) > 27 ? 5 : 4;
            var list = new List<DateTime>(maxOccurrences);

            for (int i = 0; i < maxOccurrences; i++)
            {
                list.Add(new DateTime(first.Year, first.Month, (first.Day + (7 * i))));
            }

            return list;
        }

        [Benchmark]
        public void Jonathan_GetWeekdaysForMonth()
        {
            DateTime exampleDate = new DateTime(2017, 7, 3);
            const int oneMillion = 1000000;
            for (int i = 0; i < oneMillion; i++)
            {
                IEnumerable<DateTime> days = Jonathan_AllDatesInMonth(exampleDate.Year, exampleDate.Month).Where(x => x.DayOfWeek == DayOfWeek.Thursday);

                if (days.FirstOrDefault().Day != 6)
                {
                    throw new ApplicationException("Calculate method has errors.");
                }
            }
        }

        private static IEnumerable<DateTime> Jonathan_AllDatesInMonth(int year, int month)
        {
            int days = DateTime.DaysInMonth(year, month);
            for (int day = 1; day <= days; day++)
            {
                yield return new DateTime(year, month, day);
            }
        }

        [Benchmark]
        public void Swatsonpicken_GetWeekdaysForMonth()
        {
            DateTime exampleDate = new DateTime(2017, 7, 3);
            const int oneMillion = 1000000;
            for (int i = 0; i < oneMillion; i++)
            {
                IEnumerable<DateTime> days = Swatsonpicken_GetDaysOfWeek(exampleDate, DayOfWeek.Thursday);

                if (days.FirstOrDefault().Day != 6)
                {
                    throw new ApplicationException("Calculate method has errors.");
                }
            }
        }

        private static IEnumerable<DateTime> Swatsonpicken_GetDaysOfWeek(DateTime startDate, DayOfWeek desiredDayOfWeek)
        {
            var daysOfWeek = new List<DateTime>();
            var workingDate = new DateTime(startDate.Year, startDate.Month, 1);
            var offset = ((int)desiredDayOfWeek - (int)workingDate.DayOfWeek + 7) % 7;

            // Jump to the first desired day of week.
            workingDate = workingDate.AddDays(offset);

            do
            {
                daysOfWeek.Add(workingDate);

                // Jump forward seven days to get the next desired day of week.
                workingDate = workingDate.AddDays(7);
            } while (workingDate.Month == startDate.Month);

            return daysOfWeek;
        }

        [Benchmark]
        public void AliaksandrHmyrak_GetWeekdaysForMonth()
        {
            DateTime exampleDate = new DateTime(2017, 7, 3);
            const int oneMillion = 1000000;
            for (int i = 0; i < oneMillion; i++)
            {
                IEnumerable<DateTime> days = AliaksandrHmyrak_GetDaysOfWeek(exampleDate, DayOfWeek.Thursday);

                if (days.FirstOrDefault().Day != 6)
                {
                    throw new ApplicationException("Calculate method has errors.");
                }
            }
        }

        private static List<DateTime> AliaksandrHmyrak_GetDaysOfWeek(DateTime date, DayOfWeek dayOfWeek)
        {
            var daysInMonth = DateTime.DaysInMonth(date.Year, date.Month);
            var i = 1;

            List<DateTime> result = new List<DateTime>(5);

            do
            {
                var testDate = new DateTime(date.Year, date.Month, i);

                if (testDate.DayOfWeek == dayOfWeek)
                {
                    result.Add(testDate);
                    i += 7;
                }
                else
                {
                    i++;
                }

            } while (i <= daysInMonth);

            return result;
        }

    }
}

这是结果表;

如果你愿意,我可以删除任何代码和图片名称
我标记了乔纳森的回答。简单、干净、更快(有趣)。

【问题讨论】:

  • 转到每月的第一个星期一。然后添加 7 天,只要您在目标月份。
  • 嗯,星期一是每 7 天一次,无需遍历该月的所有 31 天。查看哪一天是每月的第一天,根据确定哪一天是第一个星期一,之后的每个星期一循环。
  • @litelite 有正确的答案,找到第一个星期一并添加 7 直到月份不同。它真的很简单。
  • 即使有一个名为 GetEveryMondayInMonth() 的函数,在底层,它仍然会执行某种循环。您的“应该是更好的算法”方法使这个问题无法回答。是什么让算法变得更好?
  • @Lost_In_Library 只需在循环中使用Date 结构和AddDays。你有一个属性可以检查current month。您所有的边缘案例都由Date 处理

标签: c# datetime


【解决方案1】:

其他答案有效,但我更愿意使用来自 foreach day in month 的 Jon Skeet 的 AllDaysInMonth 函数

public static IEnumerable<DateTime> AllDatesInMonth(int year, int month)
    {
        int days = DateTime.DaysInMonth(year, month);
        for (int day = 1; day <= days; day++)
        {
            yield return new DateTime(year, month, day);
        }
    }

然后你可以像这样使用 LINQ 调用:

var mondays = AllDatesInMonth(2017, 7).Where(i => i.DayOfWeek == DayOfWeek.Monday);

但我想这取决于你要使用它多少次,是否值得将其分解为一个单独的函数。

【讨论】:

  • 你已经把它变成了一个单独的函数,它创建了许多不必要的 DateTime 对象,只是为了再次迭代。我觉得这样效率很低
  • 我同意,这不是最有效的。但在我看来,非常可读和可维护。我想这取决于您是否要使用它。
【解决方案2】:

试试这样的:

public static IEnumerable<DateTime> GetAllDayOfWeekPerMonth(int month, int year, DayOfWeek dayOfWeek)
{
    var date = new DateTime(year, month, 1);

    if(date.DayOfWeek != dayOfWeek)
    {
        int daysUntilDayOfWeek = ((int) dayOfWeek - (int) date.DayOfWeek + 7) % 7;
        date = date.AddDays(daysUntilDayOfWeek);
    }

    List<DateTime> days = new List<DateTime>();

    while(date.Month == month)
    {
        days.Add(date);
        date = date.AddDays(7);         
    }

    return days;
}

演示小提琴here

【讨论】:

    【解决方案3】:

    从不科学的角度来看,这在检查两年内随机月份的给定工作日的几千次迭代中运行得更快一些。

    差异是微不足道的。它是毫秒。所以我会做任何更容易阅读的事情。我发现这更容易阅读,尽管在另一个答案中,函数名称已经足够清楚了。如果函数名称清晰并且经过单元测试,那么我不会在其余部分上分心。

    public class WeekdaysByMonth
    {
        public IEnumerable<DateTime> GetWeekdaysForMonth(DateTime month, DayOfWeek weekDay)
        {
            return GetDaysInMonth(month).Where(day => day.DayOfWeek == weekDay);
        }
    
        private IEnumerable<DateTime> GetDaysInMonth(DateTime date)
        {
            var dateLoop = new DateTime(date.Year,date.Month,1);
            while (dateLoop.Month == date.Month)
            {
                yield return dateLoop;
                dateLoop = dateLoop.AddDays(1);
            }
        }
    }
    

    【讨论】:

    • 然后在另一条评论中引用的 Jon Skeet 的函数几乎可以肯定比我的函数更有效地获得一个月的日子。我还测试了一个版本,该版本在计算后存储当月的天数。那速度有点快,但快了几毫秒。
    【解决方案4】:

    这里是:

        private static List<DateTime> GetDaysOfWeek(DateTime date, DayOfWeek dayOfWeek)
        {            
            var daysInMonth = DateTime.DaysInMonth(date.Year, date.Month);
            var i = 1;
    
            List<DateTime> result = new List<DateTime>(5);
    
            do
            {
                var testDate = new DateTime(date.Year, date.Month, i);
    
                if (testDate.DayOfWeek == dayOfWeek)
                {
                    result.Add(testDate);
                    i += 7;
                }
                else
                {
                    i++;
                }
    
            } while (i <= daysInMonth);
    
            return result;
        }
    

    【讨论】:

      【解决方案5】:

      我的版本实现了相同的结果,但通过计算从该月的第一天到所需的第一次出现的偏移量,避免了从每月的第一天到第一个星期一(或您想要的任何一周的一天)循环天。

      public static IEnumerable<DateTime> GetDaysOfWeek(DateTime startDate, DayOfWeek desiredDayOfWeek)
      {
          var daysOfWeek = new List<DateTime>();
          var workingDate = new DateTime(startDate.Year, startDate.Month, 1);
          var offset = ((int)desiredDayOfWeek - (int)workingDate.DayOfWeek + 7) % 7;
      
          // Jump to the first desired day of week.
          workingDate = workingDate.AddDays(offset);
      
          do
          {
              daysOfWeek.Add(workingDate);
      
              // Jump forward seven days to get the next desired day of week.
              workingDate = workingDate.AddDays(7);
          } while (workingDate.Month == startDate.Month);
      
          return daysOfWeek;
      }
      

      要解决 OPs 问题,您可以这样调用此方法:

      var mondays = GetDaysOfWeek(DateTime.Today, DayOfWeek.Monday);
      

      【讨论】:

      • 为什么不在循环之前放置workingDate.AddDays(offset);
      • @litelite 好地方。更改了代码(并更正了它,因为有一个错误!)
      【解决方案6】:

      您可以在技术上解决整个问题,而无需在自己的代码中进行迭代,只需要输入以外的两条信息:一个月的第一天和一个月的天数。不过,我在回答中选择了一个小循环。

          public List<DateTime> GetDatesInMonthByWeekday(DateTime date, DayOfWeek dayOfWeek) {
              // We know the first of the month falls on, well, the first.
              var first = new DateTime(date.Year, date.Month, 1);
              int daysInMonth = DateTime.DaysInMonth(date.Year, date.Month);
      
              // Find the first day of the week that matches the requested day of week.
              if (first.DayOfWeek != dayOfWeek) {
                  first = first.AddDays(((((int)dayOfWeek - (int)first.DayOfWeek) + 7) % 7));
              }
      
              // A weekday in a 31 day month will only occur five times if it is one of the first three weekdays.
              // A weekday in a 30 day month will only occur five times if it is one of the first two weekdays.
              // A weekday in February will only occur five times if it is the first weekday and it is a leap year.
              // Incidentally, this means that if we subtract the day of the first occurrence of our weekday from the 
              // days in month, then if that results in an integer greater than 27, there will be 5 occurrences.
              int maxOccurrences = (daysInMonth - first.Day) > 27 ? 5 : 4;
              var list = new List<DateTime>(maxOccurrences);
      
              for (int i = 0; i < maxOccurrences; i++) {
                  list.Add(new DateTime(first.Year, first.Month, (first.Day + (7 * i))));
              }
      
              return list;
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-12
        • 1970-01-01
        • 2011-06-16
        • 2022-01-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多