Driving out a correct implementation of ISO week numbers using TDD #4

  1. Abstract 
  2. Refactoring
  3. Test coverage 

Proceeding with a solution there is no way of changing the implementation of the GregorianCalendar class to get the correct behavior so we have to go about this another way. One is to subclass the GregorianCalendar and override the GetWeekOfYear method. Another is to implement the GetWeekOfYear in a class totally separate from the .NET framework. I think that the first one is the way to go since the error that we're trying to correct is in the framework itself.

So, first we alter the help method in the test case to use our sub classed IsoCalendar (which we haven't written yet).

   1: private static int GetIsoWeek(DateTime dateTime) 
   2: {
   3:     Calendar calendar = new IsoCalendar();
   4:     DateTime date = new DateTime(
   5:         dateTime.Year, dateTime.Month, dateTime.Day, calendar);
   6:     return calendar.GetWeekOfYear(date,
   7:             CalendarWeekRule.firstFourDayWeek,
   8:             DayOfWeek.Monday);
   9: }

So, we are not in a compile state yet so lets do the simplest thing to get this running. I decided to subclass the GregorianCalendar class since what is wrong is the GetWeekOfYTear method of that class. I basically override that method like below.

   1: public class IsoCalendar : GregorianCalendar
   2: {
   3:     public override int GetWeekOfYear(DateTime time,
   4:                                       CalendarWeekRule rule,
   5:                                       DayOfWeek firstDayOfWeek)
   6:     {
   7:         return -1;
   8:     }
   9: }

We run the test a get a red bar. Fine, but now lets go ahead and implement the algorithm. However, this has been done over and over with varying elegantness. My favorite is the work of programmer and mathematician Julian Bucknall. His original implementation can be found at his blog. So, instead of returning -1 I call the GetIsoWeek method from Bucknall's implementation and take the modulus of 100 (since the result from the algorithm is of the form YYYYWW). We run the test and we get a green bar!

   1: public class IsoCalendar : GregorianCalendar
   2: {
   3:     public override int GetWeekOfYear(DateTime time,
   4:                                       CalendarWeekRule rule,
   5:                                       DayOfWeek firstDayOfWeek)
   6:     {
   7:         if (rule != CalendarWeekRule.FirstFourDayWeek &&
   8:                 firstDayWeek != DayOfWeek.Monday)
   9:         {
  10:             return base.GetWeekOfYear(time, rule, firstDayOfWeek);
  11:         }
  12:          
  13:         return GetIsoWeek(time) % 100;
  14:     }
  15:     
  16:     private static int GetIsoWeek(DateTime time) //...
  17:     private static DateTime GetIsoWeekOne(int year) //...
  18: }

Now when we get a green bar there's just one more thing to add. The ISO 8601 week should only be returned when the parameters rule and firstDayOfWeek is set properly according to the documentation. In all other cases the GregorianCalendar implementation works just fine. We fix this with a guard clause at the top and we're done.

Summary

During this series we covered test coverage for the faulty implementation of weeks according to ISO 8601. We used Test-Driven Development to provide a fix using Julian Bucknall's really smart implementation. Next we should take a look at refactoring to design patterns.

Loading
Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.