Reputation: 3483
How to get every "monday" day of given month ?
An example;
Input: 11.July.2017 (11.07.2017)
Output: ( 3,10,17,24,31 )
3.7.2017 Monday
10.7.2017 Monday
17.7.2017 Monday
24.7.2017 Monday
31.7.2017
I could get number of days of given month(For july 2017, it's 31 days). Then write an iteration(for loop a.e.) if dayOfWeek equals Monday, add to a list. But this is not good code because for loop will work 31 times. There should be a better algorithm to archive the goal.
I'm using C# .net framework 4.6
UPDATE
Thanks for all for your helps, after some answers I've got so far; I tested all codes with a simple & dirty benchmark codes to find faster algorithm.
Here is my benchmark code;
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;
}
}
}
And this is the results table;
I can remove any code and picture-name if you want
I marked Jonathan's answer. Simple, clean and faster (interestingly).
Upvotes: 5
Views: 12196
Reputation: 303
You can technically solve the whole problem without iterating at all in your own code with only two pieces of information other than the input: first day in month, and number of days in month. Nevertheless, I have opted for a single minor loop in my answer.
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;
}
Upvotes: 0
Reputation: 29207
Non-scientifically, this runs a little bit faster over a few thousand iterations of checking getting a given weekday for random months in a two-year period.
The difference is trivial. It's milliseconds. So I'd do whatever is easier to read. I find this a little easier to read, although in the other answer the function name makes it clear enough. If the function name is clear and it's unit tested then I wouldn't split hairs over the rest.
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);
}
}
}
Upvotes: 1
Reputation: 883
My version achieves the same result but avoids looping from the first of the month until the first Monday (or whatever day of the week you desire) by calculating the offset from the first day of the month to the first occurrence of the desired day.
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;
}
To solve the OPs question you would call this method like so:
var mondays = GetDaysOfWeek(DateTime.Today, DayOfWeek.Monday);
Upvotes: 0
Reputation: 5018
Other answers work, but I would prefer to make use of Jon Skeet's AllDaysInMonth function from foreach day in month
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);
}
}
And then you can call with LINQ like so:
var mondays = AllDatesInMonth(2017, 7).Where(i => i.DayOfWeek == DayOfWeek.Monday);
But I guess it depends on how many times you're going to use it as to wherther or not it's worth breaking out into a separate function.
Upvotes: 16
Reputation: 10818
Try something like this:
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;
}
Demo fiddle here
Upvotes: 3
Reputation: 314
Here is it:
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;
}
Upvotes: 0