Find real date based on "DayOfWeek, Day/Month" - c#

Context:
In a weird Csv import we have some miss formated Date that needs to some search. The Csv can't be fix, no control on it, we have to import it.
In Csv line :
"FooBarName ;;Mer 30/05 ;Kbr5 08h00-13h00 (-00h10) Kbr5 13h15-16h55;e 07h59 S 13h00 e 13h12 s 16h02;08h00-13h00 13h15-16h55;6:30;6:30;;6:30;;;;"
We have a date in a weird format: It look like a handcrafed R/r Format, that get cut to 12 char like if it was a dd/MM/yyyy.
"ddd dd/MM "
I know that the last date are recent . And are ordered.
Sample date:
new string[] {
"Mer 15/06","Jeu 16/06","Ven 17/06","Sam 18/06","Dim 19/06","Lun 20/06","Mar 21/06",
"Jeu 23/06","Ven 24/06","Sam 25/06","Dim 26/06","Lun 27/06","Mar 28/06","Mer 29/06",
"Jeu 30/06","Ven 01/07","Sam 02/07","Dim 03/07","Lun 04/07","Mar 05/07","Mer 06/07"
}
How do I convert a string like "ddd dd/MM" into a List<DateTime>?
For now to find the date that can give this output I compute all date on a range and compare them to the said format.
CultureInfo fr = new CultureInfo("fr-FR");
var endDate = DateTime.Now;
var startDate = new DateTime(DateTime.Now.Year-15,1,1);
var allDates = Enumerable.Range(0, 1 + endDate.Subtract(startDate).Days)
.Select(x => startDate.AddDays(x))
.Select(x => new { key = x, strDay = x.ToString("ddd"), strOther = x.ToString(" dd/MM", fr) })
// French format for ddd has a dot at the end. Let's remove it.
.Select(x =>new { key = x.key, str = x.strDay.Remove(x.strDay.Length - 1) + x.strOther})
.ToArray();
foreach (var wDate in datesToFind)
{
var results = dates.Where(x => x.str == wDate);
// etc..
}

What happens to the year in the datetime? Do you have any specific year or do you try to find for example "In which year was the 1st of July a Sunday"? If the latter is the case that's not reliable as for example the 1st of July was on Sunday in 2018, 2012 etc. Can you do something like:
var dateStrings = new string[] {
"Mer 15/06","Jeu 16/06","Ven 17/06","Sam 18/06","Dim 19/06","Lun 20/06","Mar 21/06",
"Jeu 23/06","Ven 24/06","Sam 25/06","Dim 26/06","Lun 27/06","Mar 28/06","Mer 29/06",
"Jeu 30/06","Ven 01/07","Sam 02/07","Dim 03/07","Lun 04/07","Mar 05/07","Mer 06/07"
};
var year = GetYear(dateStrings[0]);
var listDates = dateStrings
.Select(x => {
var input = x.Split(' ')[1].Split('/');
return new DateTime(year, Convert.ToInt32(input[1]), Convert.ToInt32(input[0]));
}).ToList();
Technically you need just one day to get the year (if you know that the list of days is from the same year):
private int GetYear(string dateString){
var year = DateTime.Today.Year;
var tempString = dateString.Split(' ');
var weekDay = tempString[0];
var monthDate = tempString[1].Split('/');
var month = Convert.ToInt32(monthDate[1]);
var date = Convert.ToInt32(monthDate[0]);
DayOfWeek dayOfWeek;
switch (weekDay) {
case "Lun":
dayOfWeek = DayOfWeek.Monday;
break;
case "Mar":
dayOfWeek = DayOfWeek.Tuesday;
break;
case "Mer":
dayOfWeek = DayOfWeek.Wednesday;
break;
case "Jeu":
dayOfWeek = DayOfWeek.Thursday;
break;
case "Ven":
dayOfWeek = DayOfWeek.Friday;
break;
case "Sam":
dayOfWeek = DayOfWeek.Saturday;
break;
default:
dayOfWeek = DayOfWeek.Sunday;
break;
}
while (year > 2003) {
var temp = new DateTime(year, month, date);
if (temp.DayOfWeek == dayOfWeek) {
return year;
}
year--;
}
return year;
}
I hope this helps

First, it looks like you need to handle the concept of a "day in year" (localized in the french culture). This concept of "day in year" is year independant and should have the capability to yield every possible DateTime which are valid (since a starting year ?).
You can come up with something like that to implement this concept:
sealed class FrenchDayInYear
{
private readonly string _dayOfYear;
private readonly DateTimeFormatInfo _fr;
public FrenchDayInYear(string dayOfYear)
{
_dayOfYear = dayOfYear;
_fr = new CultureInfo("fr-FR").DateTimeFormat;
_fr.AbbreviatedDayNames = new[] { "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim" };
}
public IReadOnlyList<DateTime> PossibleDates(int startYear)
{
return Enumerable.Range(startYear, DateTime.Now.Year - startYear)
.Select(WithYear)
.OfType<DateTime>()
.ToList();
}
private DateTime? WithYear(int year)
{
if (DateTime.TryParseExact(_dayOfYear + year, "ddd dd/MMyyyy", _fr, DateTimeStyles.None, out var result))
{
return result;
}
else
{
return null;
}
}
}
A few notes about this code:
I had to define custom AbbreviatedDayNames since the standard is expecting a dot a the end and yours don't contain it
I requested the startYear since you didn't explicitely stated at which year one should try to find a possible date. I also assumed that the maximal year is the current year
The trick to be able to parse an incomplete DateTime without getting exceptions is to defer the parsing until you have enough information (in this case in WithYear the missing year information is provided)
OfType filters out null values
I returned an IReadOnlyCollection in PossibleDates() to make clear that the result set has been computed and is finished
Possible usage:
var inputs = new string[]
{
"Mer 15/06","Jeu 16/06","Ven 17/06","Sam 18/06","Dim 19/06","Lun 20/06","Mar 21/06",
"Jeu 23/06","Ven 24/06","Sam 25/06","Dim 26/06","Lun 27/06","Mar 28/06","Mer 29/06",
"Jeu 30/06","Ven 01/07","Sam 02/07","Dim 03/07","Lun 04/07","Mar 05/07","Mer 06/07"
};
var output = inputs.ToDictionary(input => input, input => new FrenchDayInYear(input).PossibleDates(2000));
foreach (var kv in output)
{
Console.WriteLine("{0}=[{1}]", kv.Key, string.Join(",", kv.Value));
}
The above code will yield dates from the year 2004 and 2010, the two possible years between 2000 and now (2018) where these DayInYear were possible.

I went about this in a manner very similar to Svetoslav Petrov, but instead of assuming that all of the given dates fall within the same year, I work through the set of date strings in reverse order and in each iteration of the loop determine the most recent year (a) that is equal to or earlier than the year of the most recently converted date and (b) in which the given day of the week is valid. This approach should be valid as long as there isn't a many-year gap between any two consecutive dates in the series, or between the last date in the series and the current date.
void Test()
{
var dateStrings = new string[] {
"Mer 15/06","Jeu 16/06","Ven 17/06","Sam 18/06","Dim 19/06","Lun 20/06","Mar 21/06",
"Jeu 23/06","Ven 24/06","Sam 25/06","Dim 26/06","Lun 27/06","Mar 28/06","Mer 29/06",
"Jeu 30/06","Ven 01/07","Sam 02/07","Dim 03/07","Lun 04/07","Mar 05/07","Mer 06/07"
};
var parsedDates = ParseDateStrings(dateStrings);
foreach (var date in parsedDates)
Console.WriteLine(date);
}
// Takes a set of date strings in the format described by the question and returns
// the analogous set of DateTime objects. This method assumes that the supplied
// dates are in chronological order.
List<DateTime> ParseDateStrings(IEnumerable<string> dateStrings)
{
var year = DateTime.Today.Year;
var parsedDates = new List<DateTime>();
// Since we can't know at first how many years are represented in the given
// data set, we can't really make any assumptions about the year in which the
// data begins. Instead we assume that the most recent date occurs in either
// the current year or the latest previous year in which that date was valid,
// and work through the set backwards.
foreach (var dateString in dateStrings.Reverse())
{
var dayOfWeek = GetDayOfWeek(dateString.Substring(0, 3));
var day = int.Parse(dateString.Substring(4, 2));
var month = int.Parse(dateString.Substring(7, 2));
year = GetMostRecentValidYear(year, month, day, dayOfWeek);
parsedDates.Add(new DateTime(year, month, day));
}
// Reversing our output again at this point puts the results back into the
// same order as the inputs.
parsedDates.Reverse();
return parsedDates;
}
// Gets the appropriate DayOfWeek value for the given three-character abbreviation.
DayOfWeek GetDayOfWeek(string abbreviation)
{
switch (abbreviation.ToLower())
{
case "dim": return DayOfWeek.Sunday;
case "lun": return DayOfWeek.Monday;
case "mar": return DayOfWeek.Tuesday;
case "mer": return DayOfWeek.Wednesday;
case "jeu": return DayOfWeek.Thursday;
case "ven": return DayOfWeek.Friday;
case "sam": return DayOfWeek.Saturday;
default: throw new ArgumentException();
}
}
// Gets the latest year that is equal to or earlier than the given year, and in
// which the given day of the given month fell on the given day of the week.
int GetMostRecentValidYear(int year, int month, int day, DayOfWeek dayOfWeek)
{
while (!YearIsValid(year, month, day, dayOfWeek))
--year;
return year;
}
// Returns a flag indicating whether the given day of the given month fell on the
// given day of the week in the given year.
bool YearIsValid(int year, int month, int day, DayOfWeek dayOfWeek) =>
(month != 2 || day != 29 || IsLeapYear(year)) &&
new DateTime(year, month, day).DayOfWeek == dayOfWeek;
// Returns a flag indicating whether the given year was a leap year.
bool IsLeapYear(int year) =>
(year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
Output:
2016-06-15 00:00:00
2016-06-16 00:00:00
2016-06-17 00:00:00
2016-06-18 00:00:00
2016-06-19 00:00:00
2016-06-20 00:00:00
2016-06-21 00:00:00
2016-06-23 00:00:00
2016-06-24 00:00:00
2016-06-25 00:00:00
2016-06-26 00:00:00
2016-06-27 00:00:00
2016-06-28 00:00:00
2016-06-29 00:00:00
2016-06-30 00:00:00
2016-07-01 00:00:00
2016-07-02 00:00:00
2016-07-03 00:00:00
2016-07-04 00:00:00
2016-07-05 00:00:00
2016-07-06 00:00:00
Edit: I was looking at this again and spotted a bug in my original implementation of YearIsValid: trying to construct a DateTime for a February 29th in a non-leap year will cause the constructor to throw. I added a test for leap years to work around this problem. YearIsValid will still throw if you give it an input that is not valid in any year, like February 30th, but in that case an exception is the intended behavior.

Related

Get DateTime of the next nth day of the month

If given a date and a variable n, how can I calculate the DateTime for which the day of the month will be the nth Date?
For example, Today is the 17th of June.
I would like a function that when provided 15 would return the DateTime for July 15.
A few more examples:
Today is Feb. 26: Function would return March 30 when provided 30.
Today is Dec. 28. Function would return Jan 4 when provided 4.
Today is Feb. 28. Function would return March 29 when provided 29, unless it was a leap year, in which case it would return Feb 29.
Why not just do?
private DateTime GetNextDate(DateTime dt, int DesiredDay)
{
if (DesiredDay >= 1 && DesiredDay <= 31)
{
do
{
dt = dt.AddDays(1);
} while (dt.Day != DesiredDay);
return dt.Date;
}
else
{
throw new ArgumentOutOfRangeException();
}
}
After many, many edits, corrections and re-writes, here is my final answer:
The method that follows returns a a DateTime representing the next time the day of number day comes up in the calendar. It does so using an iterative approach, and is written in the form of an extension method for DateTime objects, and thus isn't bound to today's date but will work with any date.
The code executes the following steps to get the desired result:
Ensure that the day number provided is valid (greater than zero and smaller than 32).
Enter into a while loop that keeps going forever (until we break).
Check if cDate's month works (the day must not have passed, and the month must have enough days in it).
If so, return.
If not, increase the month by one, set the day to one, set includeToday to true so that the first day of the new month is included, and execute the loop again.
The code:
static DateTime GetNextDate3(this DateTime cDate, int day, bool includeToday = false)
{
// Make sure provided day is valid
if (day > 0 && day <= 31)
{
while (true)
{
// See if day has passed in current month or is not contained in it at all
if ((includeToday && day > cDate.Day || (includeToday && day >= cDate.Day)) && day <= DateTime.DaysInMonth(cDate.Year, cDate.Month))
{
// If so, break and return
break;
}
// Advance month by one and set day to one
// FIXED BUG HERE (note the order of the two calls)
cDate = cDate.AddDays(1 - cDate.Day).AddMonths(1);
// Set includeToday to true so that the first of every month is taken into account
includeToday = true;
}
// Return if the cDate's month contains day and it hasn't passed
return new DateTime(cDate.Year, cDate.Month, day);
}
// Day provided wasn't a valid one
throw new ArgumentOutOfRangeException("day", "Day isn't valid");
}
The spec is a little bit unclear about to do when today is the dayOfMonth. I assumed it was it to return the same. Otherwise it would just be to change to <= today.Day
public DateTime FindNextDate(int dayOfMonth, DateTime today)
{
var nextMonth = new DateTime(today.Year, today.Month, 1).AddMonths(1);
if(dayOfMonth < today.Day){
nextMonth = nextMonth.AddMonths(1);
}
while(nextMonth.AddDays(-1).Day < dayOfMonth){
nextMonth = nextMonth.AddMonths(1);
}
var month = nextMonth.AddMonths(-1);
return new DateTime(month.Year, month.Month, dayOfMonth);
}
Stumbled upon this thread today while trying to figure out this same problem.
From my testing, the following seems to work well and the loop only needs two goes (I think? Maybe 3 max(?)) to get to the answer:
public static DateTime GetNearestSpecificDay(DateTime start, int dayNum)
{
if (dayNum >= 1 && dayNum <= 31)
{
DateTime result = start;
while (result.Day != dayNum)
result = dayNum > result.Day ? result.AddDays(dayNum - result.Day) : new DateTime(result.Month == 12 ? result.Year + 1 : result.Year, (result.Month % 12) + 1, 1);
return result;
}
else
return DateTime.Today;
}
Edit: As requested, here's a less compact version that walks through the logic step by step. I've also updated the original code to account for a required year change when we reach December.
public static DateTime GetNearestSpecificDay(DateTime start, int dayNum)
{
// Check if target day is valid in the Gregorian calendar
if (dayNum >= 1 && dayNum <= 31)
{
// Declare a variable which will hold our result & temporary results
DateTime result = start;
// While the current result's day is not the desired day
while (result.Day != dayNum)
{
// If the desired day is greater than the current day
if (dayNum > result.Day)
{
// Add the difference to try and skip to the target day (if we can, if the current month does not have enough days, we will be pushed into the next month and repeat)
result = result.AddDays(dayNum - result.Day);
}
// Else, get the first day of the next month, then try the first step again (which should get us where we want to be)
else
{
// If the desired day is less than the current result day, it means our result day must be in the next month (it obviously can't be in the current)
// Get the next month by adding 1 to the current month mod 12 (so when we hit december, we go to january instead of trying to use a not real 13th month)
// If result.Month is November, 11%12 = 11; 11 + 1 = 12, which rolls us into December
// If result.Month is December, 12%12 = 0; 0 + 1 = 1, which rolls us into January
var month = (result.Month % 12) + 1;
// Get current/next year.
// Since we are adding 1 to the current month, we can assume if the previous month was 12 that we must be entering into January of next year
// Another way to do this would be to check if the new month is 1. It accomplishes the same thing but I chose 12 since it doesn't require an extra variable in the original code.
// Below can be translated as "If last result month is 12, use current year + 1, else, use current year"
var year = result.Month == 12 ? result.Year + 1 : result.Year;
// Set result to the start of the next month in the current/next year
result = new DateTime(year, month, 1);
}
}
// Return result
return result;
}
else
// If our desired day is invalid, just return Today. This can be an exception or something as well, just using Today fit my use case better.
return DateTime.Today;
}
Fun little puzzle. I generated 100 DateTimes which represent the starting day of each month, then checked each month to see if it had the date we want. It's lazy so we stop when we find a good one.
public DateTime FindNextDate(int dayOfMonth, DateTime today)
{
DateTime yesterday = today.AddDays(-1);
DateTime currentMonthStart = new DateTime(today.Year, today.Month, 1);
var query = Enumerable.Range(0, 100)
.Select(i => currentMonthStart.AddMonths(i))
.Select(monthStart => MakeDateOrDefault(
monthStart.Year, monthStart.Month, dayOfMonth,
yesterday)
.Where(date => today <= date)
.Take(1);
List<DateTime> results = query.ToList();
if (!results.Any())
{
throw new ArgumentOutOfRangeException(nameof(dayOfMonth))
}
return results.Single();
}
public DateTime MakeDateOrDefault(
int year, int month, int dayOfMonth,
DateTime defaultDate)
{
try
{
return new DateTime(year, month, dayOfMonth);
}
catch
{
return defaultDate;
}
}

How to get 3 days back of current date and should not be weekends

How can I get 3 days back of current date and should not fall on weekends (Sat/Sun)
Let's say if date i select date as 03/28/2017. It should display date as 03/23/2017. It should not take sat/sun.
dto.ProcessEndDate.AddDays(-3);
This code assumes that if new date falls on a weekend, it will instead return the Friday before.
public static DateTime GetDateExcludeWeekends(DateTime date, int index)
{
var newDate = date.AddDays(-index);
if(newDate.DayOfWeek == DayOfWeek.Sunday)
{
return newDate.AddDays(-2);
}
if(newDate.DayOfWeek == DayOfWeek.Saturday)
{
return newDate.AddDays(-1);
}
return DateTime.Now;
}
You can tweak the logic, but the main thing is to look at the DayOfWeek enum property of the DateTime class.

How to get date after N months with same day and same week of a given date

I am looking for some logic to get the date after N months having same day(Ex:Wednesday) and same week(ex: first or second...) of a given date.
ex: 12-06-2013(Wednesday & 3rd week of June) is the given date.
here I am adding 3 months to the given date.
the result should be is 14-Aug-2013(Wednesday & 3rd week of Aug).
please let me know if you need more clarification.
Thanks In advance.
Okay, so I'd personally use my Noda Time library to do this. It's entirely possible to do this with DateTime, but I'd personally find it harder. I'd also encourage you to use Noda Time in general, of course, as a better date/time API. So I'd have something like:
static LocalDate AddMonthsPreserveWeekDayAndWeek(LocalDate start, int months)
{
// This isn't the week of month in the "normal" sense; it's the nth
// occurrence of this weekday.
int week = ((start.DayOfMonth - 1) / 7) + 1;
// This will usually give the same day of month, but truncating where
// necessary
LocalDate monthsAdded = start.AddMonths(months);
LocalDate endOfPreviousMonth = monthsAdded.AddDays(-monthsAdded.Day);
// Get to the first occurrence of the right day-of-week
LocalDate firstRightDay = endOfPreviousMonth.Next(start.IsoDayOfWeek);
// Usually this will be right - but it might overflow to the next month,
// in which case we can just rewind by a week.
LocalDate candidate = firstRightDay.PlusWeeks(week - 1);
return candidate.Month == firstRightDay.Month ? candidate
: candidate.PlusWeeks(-1);
}
This is completely untested though - you should absolutely have a bunch of unit tests (ideally which you write before even including this code) which test all kinds of edge cases you're interested in.
Using standard MDSN year = 2013 month = 06 date = 12
1) Get day of the week from the specific date (Sunday is 0)
DateTime dateValue = new DateTime(year, month, date);
Console.WriteLine((int) dateValue.DayOfWeek); // Displays 3 implying it is Wed
2) Get the week of the month from the specific date
DayofWeek = 3 (from previous calculation)
Day = 12
EndOfWeek = Day + (6 - DayOfWeek) = 12 + 4 = 16
NoWeek = 0
while (EndOfWeek > 0)
{
EndOfWeek -= 7;
NoWeek++;
}
=> NoWeek = 3
3) Get first date after N month
DateTime newDate = new DateTime(year, month, 1)
newDate.AddMonths(N); // Let it be 2 => August 1, 2013
4) Get the day of the week for the new date
newDay = newDate.DayOfWeek // Return 4 implying Thursday
5) Get the last day after NoWeek
newDate.AddDays(6-newDay) => newDate.AddDays (6-4) => August 3,2013
NoWeek--;
while (NoWeek > 1)
{
newDate.AddDays(7);
NoWeek--;
}
=> newDate will be Augus 10,2013
6) Calculte required date
newDate.AddDays(DayofWeek) =>newDate will be August 14,2013

Get Date of every alternate Friday

I am wondering if i can get the date of every alternate friday starting with 13th of April, 2012 to give it as a parameter to a stored procedure using c#, asp.net?
It should also be most recently passed date. Thank you!
Just set a DateTime with the date you want to start at, and then keep adding 14 days:
So to get every other Friday after 4/13 until the end of the year:
DateTime dt = new DateTime(2012, 04, 13);
while (dt.Year == 2012)
{
Console.WriteLine(dt.ToString());
dt = dt.AddDays(14);
}
More info after comment:
If you want the most recent alternate Friday since 2012/04/13, you can compute the number of days between now and 2012/04/13, take the remainder of that divided by 14, and subtract that many days from today's date:
DateTime baseDate = new DateTime(2012, 04, 13);
DateTime today = DateTime.Today;
int days = (int)(today - baseDate).TotalDays;
int rem = days % 14;
DateTime mostRecentAlternateFriday = today.AddDays(-rem);
You can easily make a generator method that would give you the set of fridays:
public IEnumerable<DateTime> GetAlternatingFridaysStartingFrom(DateTime startDate)
{
DateTime tempDate = new DateTime(startDate.year, startDate.Month, startDate.Day);
if(tempDate.DayOfWeek != DayOfWeek.Friday)
{
// Math may be off, do some testing
tempDate = tempDate.AddDays((7 - ((int)DayOfWeek.Friday - (int)tempDate.DayOfWeek) % 7);
}
while(true)
{
yield return tempDate;
tempDate = tempDate.AddDays(14);
}
}
Then, simply use some LINQ to determine how much you want:
var numberOfFridays = GetAlternatingFridaysStartingFrom(DateTime.Today).Take(10);
Why do you need a stored proc?
If you have a date that is Friday, why not just use AddDays(14) in a loop?
If you want to find the nearest Friday from a start date, just use this:
while(date.DayOfWeek != DayOfWeek.Friday)
{
date.AddDays(1);
}
Then use the 14 day loop to get every other Friday.
You can create simple method that will enumerate them like so:
public static IEnumerable<DateTime> GetAlternatingWeekDay(DateTime startingDate)
{
for (int i = 1; ; i++)
{
yield return startingDate.AddDays(14*i);
}
}
Which you can call like this:
DateTime startingDate = DateTime.Parse("2012-04-13");
foreach (var date in GetAlternatingWeekDay(startingDate).Take(10))
{
Console.WriteLine(date.ToString("R"));
}
Alternately, if you need to know the date for a given number of weeks out, you could use code like this:
DateTime date = DateTime.Parse("2012-04-13").AddDays(7 * numberOfWeeks);

How can I determine if a date is the nth List<WeekDay> in a month?

I've seen this question, which looks for the Nth weekday in a specific month, but I want something that will look for the Nth [weekday list] in a given month. Is there an easy way to get that without looping through every day in the month?
For example, if I say I want the 3rd [Mon, Wed, Fri] in a month, and the month starts on a Wednesday, it should return the 1st Monday (1 = Wed, 2 = Fri, 3 = Mon). If I say I want the 2nd last [Mon, Tue, Wed, Thurs] and the last day of the month is a Sunday, it should return the previous Wednesday.
Meh I mean you might be able to come up with something ugly and incomprehensible that is formula-based and avoids looping, but it will be ugly and incomprehensible so bug-prone and a maintenance disaster, and not at all fun to try to come up with.
Instead, just come up with what is easy to write using loops, and then hide the loops by using LINQ:
static class DateTimeExtensions {
private static IEnumerable<DateTime> DaysOfMonth(int year, int month) {
return Enumerable.Range(1, DateTime.DaysInMonth(year, month))
.Select(day => new DateTime(year, month, day));
}
public static DateTime NthDayOfMonthFrom(
this HashSet<DayOfWeek> daysOfWeek,
int year,
int month,
int nth
) {
return
DateTimeExtensions.DaysOfMonth(year, month)
.Where(
date => daysOfWeek.Contains(date.DayOfWeek)
)
.Skip(nth - 1)
.First();
}
}
Usage:
var daysOfWeek = new HashSet<DayOfWeek> {
DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Friday
};
DateTime date = daysOfWeek.NthDayOfMonthFrom(2011, 6, 3));
Assert.Equal(date, new DateTime(2011, 6, 6));
Similarly, using Reverse, you can easily write NthLastDayOfWeekFrom. I'll leave it to you to come up with a better name.
I think I'm accepting Jason's answer because I like his solution, however here's what I had come up with before I saw his post. I thought I'd post it because I like the idea of not looping thorough every date, especially if N is a higher value and I'd like to get a date outside the current month.
Instead of looping through all days, I fast-forwarded (or rewind if I'm going backwards) to the closest week, then looped through the next couple of days to find the Nth instance the weekdays specified.
public static DateTime? GetNthWeekDayInMonth(DateTime month, int nth, List<DayOfWeek> weekdays)
{
var multiplier = (nth < 0 ? -1 : 1);
var day = new DateTime(month.Year, month.Month, 1);
// If we're looking backwards, start at end of month
if (multiplier == -1) day = day.AddMonths(1).AddDays(-1);
var dayCount = weekdays.Count;
if (dayCount == 0)
return null;
// Fast forward (or rewind) a few days to appropriate week
var weeks = ((nth - (1 * multiplier)) / dayCount);
day = day.AddDays(weeks * 7);
// Current Nth value
var currentNth = (1 * multiplier) + (weeks * dayCount);
// Loop through next week looking for Nth value
for (var x = 0; x <= 7; x++)
{
if (weekdays.Contains(day.DayOfWeek))
{
if (currentNth == nth)
{
// Verify the nth instance is still in the current month
if (day.Month == month.Month)
return day;
else
return null;
}
currentNth += multiplier;
}
day = day.AddDays(multiplier);
}
return null;
}

Categories

Resources