Question

I am a very beginner of C# and programming. I am trying to calculate a few DateTime variables. The first one is called dDate and second dDate1 (the previous day of dDate), third dDate2 (the second previous day of dDate, i.e., the previous day of dDate1), the fourth dDate3 (the third previous day of dDate, i.e., the second previous day of dDate1 and the previous day of dDate2). They must be not holidays or weekends!

I've had all holidays and weekends stored in a dictionary called nd<DateTime, string>. The key DateTime has a series of date from 2011-01-01 to 2013-01-01, step by one day and the value string is either TR or NT, a string variable but not boolean. If it is weekend or holiday, string is NT, otherwise TR.

What I am trying to do is when dDate is weekend or holiday, minus one day. For example, dDate is 2012-01-02 which is a holiday, change dDate to 2012-01-01, and because it is weekend (Sunday), change it to 2011-12-31, and it is weekend again, change dDate to 2011-12-30. Same to dDate1, dDate2 and dDate3.

The problem here is my code works fine for dDate. But it gives an error:

the given key was not present in the dictionary

when I am doing the same thing for dDate1, dDate2 or dDate3. The code is attached below:

 private Dictionary<DateTime, string> noDates;
 ...
 noDates = new Dictionary<DateTime, string>();

 public void ImportNoDate()
 {
      string str;
      string[] line = new string[0];
      while ((str = reader.ReadLine()) != null) 
      {
         line = str.Split(',');
         String date = line[1];
         String flag = line[2];//flag is "NT" or "TR"
         String[] tmp = date.Split('-');
         date = Convert.ToInt32(tmp[0]) + "-" + Convert.ToInt32(tmp[1]) + "-" + Convert.ToInt32(tmp[2]);

         DateTime noDate = DateTime.Parse(date);
         noDates.Add(noDate, flag);
     }
  }

public void ImportdDate()
{
    ...
    DDates dd = new DDates(dDate, noDates); //dDate is defined similar to noDate, it is just another //series of date
}

    //DDates is an auxiliary cs file called DDates.cs
    public DDates(DateTime dd, Dictionary<DateTime, string> nd)
    {
         dDate1 = dDate.AddDays(-1);
         dDate1 = dDate.AddDays(-2);
         dDate3 = dDate.AddDays(-3);

       // dDate is imported from data file and has been Parse
      // to DateTime and it is something like
      // 2012-01-01 12:00:00 AM

     if (nd.ContainsKey(dDate))
     {
        while (nd[dDate].Contains("NT"))
       {
          dDate = dDate.AddDays(-1);
       }
    }

   //It works fine till here:
   if (nd.ContainsKey(dDate1))
   {
      //It gives "the given key was not present in the dictionary" here:
      while (nd[dDate1].Contains("NT"))
      {
        dDate1 = dDate1.AddDays(-1);
      }
   }
}
Was it helpful?

Solution

From your description it looks like what you are trying to do is for a given date find the first non holiday date.

Using a dictionary and storing every possible date is not the correct solution for this.

Personally I think a HashSet<DateTime> plus a little math would be the best solution. In fact I was bored so I wrote it up

static class HolidayTester
{
    private static HashSet<DateTime> fixedHolidays = new HashSet<DateTime>(new DayOnlyComparer())
        {
            new DateTime(1900,1,1), //New Years
            new DateTime(1900,7,4), //4th of july
            new DateTime(1900,12, 25) //Christmas
        };


    /// <summary>
    /// Finds the most recent workday from a given date.
    /// </summary>
    /// <param name="date">The date to test.</param>
    /// <returns>The most recent workday.</returns>
    public static DateTime GetLastWorkday(DateTime date)
    {
        //Test for a non working day
        if (IsDayOff(date))
        {
            //We hit a non working day, recursively call this function again on yesterday.
            return GetLastWorkday(date.AddDays(-1));
        }

        //Not a holiday or a weekend, return the current date.
        return date;
    }


    /// <summary>
    /// Returns if the date is work day or not.
    /// </summary>
    /// <param name="testDate">Date to test</param>
    /// <returns>True if the date is a holiday or weekend</returns>
    public static bool IsDayOff(DateTime testDate)
    {
      return date.DayOfWeek == DayOfWeek.Saturday ||
             date.DayOfWeek == DayOfWeek.Sunday || //Test for weekend
             IsMovingHolidy(testDate) || //Test for a moving holiday
             fixedHolidays.Contains(testDate); //Test for a fixed holiday
    }


    /// <summary>
    /// Tests for each of the "dynamic" holidays that do not fall on the same date every year.
    /// </summary>
    private static bool IsMovingHolidy(DateTime testDate)
    {
        //Memoral day is the last Monday in May
        if (testDate.Month == 5 && //The month is May 
                testDate.DayOfWeek == DayOfWeek.Monday && //It is a Monday
                testDate.Day > (31 - 7)) //It lands within the last week of the month.
            return true;

        //Labor day is the first Monday in September
        if (testDate.Month == 9 && //The month is september
                testDate.DayOfWeek == DayOfWeek.Monday &&
                testDate.Day <= 7) //It lands within the first week of the month
            return true;


        //Thanksgiving is the 4th Thursday in November
        if (testDate.Month == 11 && //The month of November
            testDate.DayOfWeek == DayOfWeek.Thursday &&
            testDate.Day > (7*3) && testDate.Day <= (7*4)) //Only durning the 4th week
            return true;

        return false;
    }


    /// <summary>
    /// This comparer only tests the day and month of a date time for equality
    /// </summary>
    private class DayOnlyComparer : IEqualityComparer<DateTime>
    {
        public bool Equals(DateTime x, DateTime y)
        {
            return x.Day == y.Day && x.Month == y.Month;
        }

        public int GetHashCode(DateTime obj)
        {
            return obj.Month + (obj.Day * 12);
        }
    }
}

Now it does not follow your rules exactly, this code tests if a day is a work day and keeps walking backwards till it hits the first non work day. It would be easy enough to modify, however I did not want to solve your problem exactly so you could learn a little (Unless I misunderstood the algorithm and I did solve the problem, in that case... your welcome)

The way you would use it is simply put in a date and then use that to decide if you are going to return TR or NT

public static string GetDateLabel(DateTime testDate)
{
    if(HolidayTester.IsDayOff(testDate))
        return "NT";
    else
        return "TR";
}

If you want to know the last working day you can call that directly from HolidayTester.GetLastWorkday(DateTime)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top