3
votes

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.
4
Why next 15th day after 15th of January should be 30th of March? o.OSoner Gönül
it should be 30/01 right?Sajeetharan
@Sajeetharan That's depends on what OP wants for 15th day of months. It can be the same, of it can be the 15th of the next month.It won't be 30th day for any case, at least I hopeSoner Gönül
Incomplete functional spec. You must describe what is supposed to happen when asking for, say, the 29th in February or the 31st in March.Hans Passant
Perhaps change the title to "Get next DateTime for the nth day of the month" or something along those lines, to make it easier to find?stybl

4 Answers

2
votes

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:

  1. Ensure that the day number provided is valid (greater than zero and smaller than 32).
  2. Enter into a while loop that keeps going forever (until we break).
  3. 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");
}
2
votes

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();
    }     
}
1
votes

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);

}
0
votes

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;
  }
}