9

I am struggling to find an easy and efficient solution to calculating the week day of a month. For example, if a given date is the first Monday Monday 5th March 2018 then I want to get the date for each first Monday of a month for the next 6 months e.g: Monday 2nd April 2018 and Monday 3rd May 2018 and so on.

I have tried to use the following code from this question. However, the code below only returns the week number I would like it to return the whole date.

static class DateTimeExtensions
{
    static GregorianCalendar _gc = new GregorianCalendar();
    public static int GetWeekOfMonth(this DateTime time)
    {
        DateTime first = new DateTime(time.Year, time.Month, 1);
        return time.GetWeekOfYear() - first.GetWeekOfYear() + 1;
    }

    static int GetWeekOfYear(this DateTime time)
    {
        return _gc.GetWeekOfYear(time, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
    }
}

But I am confused and don't know how I can modify the above code to solve this issue. Any help will be appreciated.

Salman A
  • 262,204
  • 82
  • 430
  • 521
Big Smile
  • 1,103
  • 16
  • 30

6 Answers6

6

Recently Microsoft has released a DateTimeRecognizer project on GitHub which allows you to parse natural language datetime into Plain Old C# DateTime Objects. It's throughly tested, so no monkey work there.

They have also released a nuget package of the same. Which you can install via the nuget package manager.


First install the nuget package:

Microsoft Date Recognizer Nuget Package


While, all of that is out of the way. I have developed a small utility program, which first extracts the datetime from the natural language then finds the first occurrence of subsequent dates of the same day next month.

using Microsoft.Recognizers.Text;
using Microsoft.Recognizers.Text.DateTime;
using System;
using System.Collections.Generic;
using System.Linq;

namespace RecognizerDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                string Query = string.Empty;
                Console.WriteLine("Enter date: ");
                Query = Console.ReadLine();
                DateTime parsedDate = ExtractDateFromNaturalLanguage(Query);
                List<DateTime> futureDates = GetSubsequentDateTimes(parsedDate);


                foreach (var item in futureDates)
                {
                    Console.WriteLine(item.ToLongDateString());
                }

            }
            catch(Exception ex)
            {
                Console.WriteLine($"Failed because: {ex.Message}");
            }
        }

    static List<DateTime> GetSubsequentDateTimes(DateTime date)
    {
        try
        {
            List<DateTime> futureDates = new List<DateTime>();
            DayOfWeek dayOfWeekOriginalDate = date.DayOfWeek;
            int month = date.Month;

            futureDates.Add(date);

            for (int i = month + 1; i <= month + 5; i++)
            {
                DateTime dt = new DateTime(date.Year, i, 1);

                while (dt.DayOfWeek != dayOfWeekOriginalDate)
                {
                    dt = dt.AddDays(1);
                }
                futureDates.Add(dt);
            }

            return futureDates;
        }
        catch(Exception ex)
        {
            throw;
        }
    }

        static DateTime ExtractDateFromNaturalLanguage(string Query)
        {
            try
            {
                DateTimeModel model = DateTimeRecognizer.GetInstance().GetDateTimeModel(Culture.English);
                List<ModelResult> parsedResults = model.Parse(Query);

                Dictionary<string, string> resolvedValue = (parsedResults.SelectMany(x => x.Resolution).FirstOrDefault().Value as List<Dictionary<string, string>>).FirstOrDefault();

                string parsedDate = string.Empty;

                if (resolvedValue["type"] == "daterange")
                    parsedDate = resolvedValue["start"];

                if (resolvedValue["type"] == "date")
                    parsedDate = resolvedValue["value"];

                DateTime parsedDateTimeObject = DateTime.Parse(parsedDate);

                return parsedDateTimeObject;
            }
            catch (Exception ex)
            {
                throw;
            }
        }
    }
}

This, is what you need.

Here's what you need

Kunal Mukherjee
  • 5,775
  • 3
  • 25
  • 53
  • nice.. +1.. i wonder why they called it GetSubsequent"DateTimes".. as i think for this specific scenario GetSubsequentWeekday or something like that wouldve made more sense.. unless there are overloads which would change the behavior. im gonna need to check this out sometime.. seems pretty cool. – Heriberto Lugo Feb 04 '18 at 07:20
  • this doesnt seem to work. try the date new DateTime(2018, 2, 13).. guess i spoke too soon? – Heriberto Lugo Feb 04 '18 at 07:36
  • @Heriberto Lugo its finding out the first occurrence of a day in a particular month. Imo that satisfies the op's query. – Kunal Mukherjee Feb 04 '18 at 07:50
  • @HeribertoLugo what output are you getting for the input `new DateTime(2018, 2, 13)` ?? – Kunal Mukherjee Feb 04 '18 at 07:51
  • Can you check now @HeribertoLugo I have modified the code. – Kunal Mukherjee Feb 04 '18 at 08:04
  • its still wrong.. its not getting the first Monday after the date specified, nor is it getting the subsequent cycle.. you may want to just check the solution i had provided. – Heriberto Lugo Feb 04 '18 at 08:11
4

With Crontab and some clever linq, you can get, not only the first day of any month, but any combination of dates you need, with less than 10 lines of code:

First, install NCronTab via NuGet: Install-Package ncrontab -Version 3.3.0

Second, using Monday as an example, create crontab expression (link above will be useful) that returns only Mondays for all months:

var schedule = CrontabSchedule.Parse("0 0 * * 1");

This says: "zeroeth minute, zeroeth hour (i.e. midnight), any day, any month, on Monday"

That schedule gives you a function that you can invoke to get Mondays for any period.

Finally, for the clever linq: we want to group the Mondays by month, and then select the first one in each month. It will look like this:

var mondays = schedule
    // the occurrences in the date range we want
    .GetNextOccurrences(new DateTime(2018, 3, 1), new DateTime(2018, 9, 30))
    .ToLookup(
        date => (date.Year * 100) + date.Month, // The 'key' becomes a "period". i.e. yyyyMM
        date => date) // this is our grouping by month
    .Select(d => d.First()) // Take the first instance per group
    .ToList(); // you now have 6 Mondays!

That Select can easily be tweaked to find the first, second... week day of that month by using d.ElementAtOrDefault(n). If you want the last month, d.Last().

Balah
  • 2,530
  • 2
  • 16
  • 24
4

Just add 4 weeks to input date, add 7 more days if necessary:

static DateTime GetNextDate(DateTime d1)
{
    System.Diagnostics.Debug.Assert(d1.Day <= 28, "Behavior not described");
    DateTime d2 = d1.AddDays(28);
    // the following evaluates to 1 for 1-7, 2 for 8-14, etc
    int n1 = (d1.Day - 1) / 7 + 1;
    int n2 = (d2.Day - 1) / 7 + 1;
    if (n2 != n1)
    {
        d2 = d2.AddDays(7);
    }
    return d2;
}

Sample input and output:

Thu 2018-Mar-01 > Thu 2018-Apr-05
Fri 2018-Mar-02 > Fri 2018-Apr-06
Sat 2018-Mar-03 > Sat 2018-Apr-07
Sun 2018-Mar-04 > Sun 2018-Apr-01
Mon 2018-Mar-05 > Mon 2018-Apr-02
Tue 2018-Mar-06 > Tue 2018-Apr-03
Wed 2018-Mar-07 > Wed 2018-Apr-04
Thu 2018-Mar-08 > Thu 2018-Apr-12
Fri 2018-Mar-09 > Fri 2018-Apr-13
Sat 2018-Mar-10 > Sat 2018-Apr-14
Sun 2018-Mar-11 > Sun 2018-Apr-08
Mon 2018-Mar-12 > Mon 2018-Apr-09
Tue 2018-Mar-13 > Tue 2018-Apr-10
Wed 2018-Mar-14 > Wed 2018-Apr-11
Thu 2018-Mar-15 > Thu 2018-Apr-19
Fri 2018-Mar-16 > Fri 2018-Apr-20
Sat 2018-Mar-17 > Sat 2018-Apr-21
Sun 2018-Mar-18 > Sun 2018-Apr-15
Mon 2018-Mar-19 > Mon 2018-Apr-16
Tue 2018-Mar-20 > Tue 2018-Apr-17
Wed 2018-Mar-21 > Wed 2018-Apr-18
Thu 2018-Mar-22 > Thu 2018-Apr-26
Fri 2018-Mar-23 > Fri 2018-Apr-27
Sat 2018-Mar-24 > Sat 2018-Apr-28
Sun 2018-Mar-25 > Sun 2018-Apr-22
Mon 2018-Mar-26 > Mon 2018-Apr-23
Tue 2018-Mar-27 > Tue 2018-Apr-24
Wed 2018-Mar-28 > Wed 2018-Apr-25
Salman A
  • 262,204
  • 82
  • 430
  • 521
  • Thank you so much. I have tested this and it worked perfectly. – Big Smile Feb 04 '18 at 20:23
  • +1 Although in some rare cases this approach don't show correct week number in future months --- Consider 2019/March/31 is 5th week and with this approach next month would be 4th week. But its more practical than my approach because probably you don't like to see "not found" in output. – Masoud Keshavarz Feb 05 '18 at 05:20
  • @masoud yes I did not handle these cases in the example. Those dates could be mapped to 4th weekday or last day of month or we could simply assume such dates will not be used (OP did not mention). – Salman A Feb 05 '18 at 05:48
2

I've added myCustomDateChains method to your DateTimeExtensions class. It returns what you are looking for in next 6 months. Note that if you give 5th week of month as input there is a chance in next month there be no monday in 5th week. In this case there would be "not found" string in next month.

static class DateTimeExtensions
{
    static GregorianCalendar _gc = new GregorianCalendar();
    public static string[] myCustomDateChains(this DateTime date)
    {

        string dayName = date.ToString("dddd");
        int weekOfMonth = GetWeekOfMonth(date);

        string[] array = new string[6];

        //loop next 6 months
        for (int i = 1; i < 7; i++)
        {

            DateTime dt = date.AddMonths(i);
            DateTime dtFirstDayOfMonth =  dt.AddDays(-(dt.Day - 1));

            //seek for day in appropriate week
            for (int j = 0; j < 7; j++)
            {

                if (dtFirstDayOfMonth.AddDays(j + (7 * (weekOfMonth - 1))).ToString("dddd") == dayName)
                {
                    array[i - 1] = dtFirstDayOfMonth.AddDays(j + (7 * (weekOfMonth - 1))).ToString();
                    break;
                }

            }

            //5th week of choosen month
            if (array[i - 1] == "")
            {
                array[i - 1] = "doesnt found";
            }

        }


        return array;
    }

    static int GetWeekOfYear(this DateTime time)
    {
        return _gc.GetWeekOfYear(time, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
    }

    public static int GetWeekOfMonth(this DateTime time)
    {
        DateTime first = new DateTime(time.Year, time.Month, 1);
        return time.GetWeekOfYear() - first.GetWeekOfYear() + 1;
    }

}
Masoud Keshavarz
  • 2,166
  • 9
  • 36
  • 48
1

This seems to be what your looking for.

    public static void Main(string[] args)
{
    for (int mth = 1; mth <= 12; mth++)
    {
        DateTime dt = new DateTime(2010, mth, 1);
        while (dt.DayOfWeek != DayOfWeek.Monday)
        {
            dt = dt.AddDays(1);
        }
        Console.WriteLine(dt.ToLongDateString());
    }
    Console.ReadLine();
}

Ref Here

programbanana
  • 48
  • 1
  • 7
1

Try below code snippet

static void Main(string[] args)
{
    int requiredMonths = 6;
    int weekDays = 7;
    DateTime date = new DateTime(2018, 2, 5);

    DateTime[] result = new DateTime[requiredMonths];            

    for (int i = 0; i < requiredMonths; i++)
    {
        DateTime firstDayOfNextMonth = date.AddMonths(i).AddDays(-date.Day + 1);

        for (int j = 0; j < weekDays; j++)
        {                    
            if (firstDayOfNextMonth.AddDays(j).DayOfWeek.Equals(DayOfWeek.Monday))
            {
                result[i] = firstDayOfNextMonth.AddDays(j);
            }                    
        }   
    }

    foreach (var item in result)
    {
        Console.WriteLine(item);
    }
}

demo https://dotnetfiddle.net/E0NAKc

programtreasures
  • 4,250
  • 1
  • 10
  • 29