0

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);
      }
   }
}
BeginnedCSharp
  • 39
  • 1
  • 2
  • 10
  • Are you using multiple threads? – Reed Copsey Sep 30 '13 at 21:52
  • 1
    You should show how you are populating your dictionary. – Justin Helgerson Sep 30 '13 at 21:52
  • is that all of your code, or are there parts missing inbetween – Sam I am says Reinstate Monica Sep 30 '13 at 21:54
  • 2
    You appear to use `dDate` in both the check and the loop for the first part, but in the 2nd part you use `dDate1` in the `if` and `while` checks but you use `dDate2` in the math. Was this intended? – Scott Chamberlain Sep 30 '13 at 21:54
  • 1
    I am not sure if your entire logic is valid but this is different issue. If you using date as key to dictionary, normally is not a good idea. Obviously, you trying to find this key but "datetime" is such slick data type that you will run into issues. Use strings as keys to dictionaries. – T.S. Sep 30 '13 at 22:12
  • I don't think a dictionary is the best tool for this. You may be much better of just storing the holiday dates in a hashset or somthing and if it is not contained in the set then it is a non holiday date. – Scott Chamberlain Sep 30 '13 at 23:00
  • @ReedCopsey, this program has multiple cs files. But I think it is not multiple-threads. – BeginnedCSharp Oct 01 '13 at 12:47
  • @Ek0nomik I have edited the code to show how I am populating the dictionary. It is in another cs file but not the same one as calculating the dDate, dDate1, dDate2, or dDate3 – BeginnedCSharp Oct 01 '13 at 12:48
  • @SamIam I added more lines. Thank you – BeginnedCSharp Oct 01 '13 at 12:48
  • @ScottChamberlain Thank you for pointing that out. The 2nd part should all be dDate1 both in if&while and in the math. I've made changes in the code. – BeginnedCSharp Oct 01 '13 at 12:50
  • @T.S.The reason I use DateTime is that I wanted to modify it so that they are not holidays or weekend. I've learned it is a bad idea to use 'DateTime' as key though. However, using string in the dictionary I may not be able to make changes on dDate, dDate1. Could you help on this? – BeginnedCSharp Oct 01 '13 at 12:51
  • @ScottChamberlain Thanks a lot. I am working on it. Will let you know how it goes. I might be slow testing though :) – BeginnedCSharp Oct 01 '13 at 12:54
  • Hi All, thanks for all your reply and suggestion. I have solved this problem. It is essentially very easy. What I did was to store all holiday dates in a List variable and set a while loop. While dDate is in the List, dDate = dDate.AddDays(-1). Thank you all again! – BeginnedCSharp Oct 02 '13 at 13:18

1 Answers1

1

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)

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • This is really fancy code! Thank you very much! I am testing it and modifying it to my own needs. Will let you know :) – BeginnedCSharp Oct 01 '13 at 12:55
  • Hi Scott, I have solved this problem. It is essentially very easy. What I did was to store all holiday dates in a List variable and set a while loop. While dDate is in the List, dDate = dDate.AddDays(-1). Thank you again! I will next try to integrate your code to my program to make the process more automatic and intelligent! – BeginnedCSharp Oct 02 '13 at 13:20
  • @BeginnedCSharp You may want to use a `HashSet` instead of a `List`, the lookups will be much faster. – Scott Chamberlain Oct 04 '13 at 15:17