Custom (Extended) Date Functions

Would you like to have easily accessible Extended Methods for DateTime variables like the below ?
  1. //calculates the age as of today, where DOB is a Date;  
  2. DOB.AgeAsOfToday();  
  3. //checks if myDate falls in a leap year  
  4. myDate.IsLeapYear();  
  5. //Returns the Full Month Name, which is not an out-of-box property of DateTime  
  6. myDate.MonthFullName();  
  7. //Checks whether your date falls within Range  
  8. myDate.DateBetween(Convert.ToDateTime("12-December-1992"), Convert.ToDateTime("31-December-1994")  
Sure, you can implement it inline; but when you are trying to define a cleaner code structure and want your team to apply a common code base and reuse utility functions, you may benifit from the approach I suggest below.
 
Dates are quite an interesting data type in the programming language. Though the DOT NET framework provides many functions out of the box, typically the business validations require some more functional checks OR capabilities like whether it's a leap year, what's the age from the current date, whether it falls within a range, etc.
 
We will use the capability of Extention Methods to achieve our objective. We cannot directly extend the base DateTime type as it's a sealed class.
 
Therefore we will define our own class that we can extend.
  1. public class Date  
  2. {  
  3.   
  4.     private DateTime? newDate;  
  5.     //Default constructor  
  6.     public Date()  
  7.     {}  
  8.     //overridden constructor  
  9.     public Date(DateTime d)  
  10.     {  
  11.         this.newDate = d;  
  12.     }  
  13.     /// <summary>  
  14.     /// If no value is set, the default value returned is Today's Date.  
  15.     /// </summary>  
  16.     public DateTime? DateValueDefault  
  17.     { get  
  18.         {  
  19.             if (newDate == null)  
  20.                 newDate = DateTime.Today;  
  21.             return newDate;  
  22.         }  
  23.         set  
  24.         {  
  25.             newDate = value;  
  26.         }  
  27.     }  
  28. }  
Now we will define our custom functions in the Extended Class. We will try to implement it in a way so that all properties and methods that are available on a DateTime type variable are also applied in our extended class. So we will use reflection to call any method or property that is required. GetPropertyValue, InvokeMethodWithoutArguments, and InvokeMethodWithArguments methods are defined to access the default properties and methods on a DateTime type. All other methods are extended methods.
  1. //you will be needing the following namespaces  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Globalization;  
  5. using System.Linq;  
  6. //include them in your class  
  7.     
  8.   public static class ExtendedDateFunctions  
  9.     {  
  10.  
  11.         #region default properties of DateTime  
  12.         /// <summary>  
  13.         /// Invokes any property of DateTime data type  
  14.         /// </summary>  
  15.         /// <param name="d">The object on which the property is to be invoked</param>  
  16.         /// <param name="propertyName">name of the property invoked. Make sure that you spell it with correct casing as the Property Name of DateTime types</param>  
  17.         /// <returns></returns>  
  18.         public static object GetPropertyValue(this Date d, string propertyName)  
  19.         {  
  20.             DateTime date = d.DateValueDefault ?? DateTime.Today;  
  21.             return date.GetType().GetProperties()  
  22.                .Single(pi => pi.Name == propertyName)  
  23.                .GetValue(date, null);  
  24.         }  
  25.   
  26.         /// <summary>  
  27.         /// Invokes any method of DateTime data type  
  28.         /// </summary>  
  29.         /// <param name="d">The object on which the method is to be invoked</param>  
  30.         /// <param name="methodName">name of the property invoked. Make sure that you spell it with correct casing as the Method Name of DateTime types</param>  
  31.         /// <returns></returns>  
  32.         public static object InvokeMethodWithoutArguments(this Date d, string methodName)  
  33.         {  
  34.             DateTime date = d.DateValueDefault ?? DateTime.Today;  
  35.             return date.GetType().GetMethod(methodName).Invoke(date, null);  
  36.         }  
  37.         /// <summary>  
  38.         /// Invokes any method of DateTime data type  
  39.         /// </summary>  
  40.         /// <param name="d">The object on which the method is to be invoked</param>  
  41.         /// <param name="methodName">name of the property invoked. Make sure that you spell it with correct casing as the Method Name of DateTime types</param>  
  42.         /// <param name="args"> List of arguments to be used in the method. Ensure the correct order</param>  
  43.         /// <returns></returns>  
  44.         public static object InvokeMethodWithArguments(this Date d, string methodName, List<object> args)  
  45.         {  
  46.             DateTime date = d.DateValueDefault ?? DateTime.Today;  
  47.             return date.GetType().GetMethod(methodName).Invoke(date, args.ToArray());  
  48.         }  
  49.         #endregion  
  50.  
  51.  
  52.         #region extended Methods  
  53.   
  54.         public static bool DateLessThan(this Date d, DateTime endDate)  
  55.         {  
  56.             return (d.DateValueDefault < endDate);  
  57.         }  
  58.         public static bool DateLessThanEqual(this Date d, DateTime endDate)  
  59.         {  
  60.             return (d.DateValueDefault <= endDate);  
  61.         }  
  62.         public static bool DateGreaterThan(this Date d, DateTime startDate)  
  63.         {  
  64.             return (d.DateValueDefault > startDate);  
  65.         }  
  66.         public static bool DateGreaterEqual(this Date d, DateTime startDate)  
  67.         {  
  68.             return (d.DateValueDefault >= startDate);  
  69.         }  
  70.         public static bool DateBetween(this Date d, DateTime startDate, DateTime endDate)  
  71.         {  
  72.             return (d.DateValueDefault > startDate && d.DateValueDefault < endDate);  
  73.         }  
  74.         public static bool DateBetweenIncludeEndDates(this Date d, DateTime startDate, DateTime endDate)  
  75.         {  
  76.             return (d.DateValueDefault >= startDate && d.DateValueDefault <= endDate);  
  77.         }  
  78.   
  79.         public static string MonthFullName(this Date d)  
  80.         {  
  81.             var month = (d.DateValueDefault ?? DateTime.Today).Month;  
  82.             return CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(month);  
  83.         }  
  84.   
  85.         public static string MonthShortName(this Date d)  
  86.         {  
  87.             var month = (d.DateValueDefault ?? DateTime.Today).Month;  
  88.             return CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedMonthName(month);  
  89.         }  
  90.   
  91.   
  92.         public static bool IsLeapYear(this Date d)  
  93.         {  
  94.             var y = (d.DateValueDefault ?? DateTime.Today).Year;  
  95.             bool _check4 = CheckDivisibility(y, 4, true);   //if divisible by 4; (if yes, then only proceed)  
  96.             bool _check100 = CheckDivisibility(y, 100, _check4); //if divisible in 100; (if yes, then only proceed)  
  97.             bool _check400 = CheckDivisibility(y, 400, _check100);//if divisible by 400  
  98.             //The year is a leap year if it is multiple of 4 and 400, but not of 100;  
  99.             return (_check4 && !(_check100)) || (_check400);  
  100.         }  
  101.   
  102.         public static int AgeAsOfToday(this Date d)  
  103.         {  
  104.             // Calculate the age.    
  105.             var age = DateTime.Today.Year -  (d.DateValueDefault ?? DateTime.Today).Year;  
  106.             // Go back to the year in which the person was born in case of a leap year    
  107.             if (d.DateValueDefault?.Date > DateTime.Today.AddYears(-age)) age--;  
  108.             return age;  
  109.         }  
  110.   
  111.         private static bool CheckDivisibility(int year, int divBy, bool cont)  
  112.         {  
  113.             bool response = false;  
  114.             //the value of cont is used to determine whether the previous condition is TRUE.    
  115.             //perform the current divisibility check only if the last condition is passed.    
  116.             //What we have done here is instead of making the check in the calling function we have moved the    
  117.             //check in this function and made this more like a single responsibility function.    
  118.             if (cont)  
  119.             {  
  120.                 int rem = year % divBy;  
  121.                 if (rem == 0)  
  122.                     response = true;  
  123.             }  
  124.             return response;  
  125.         }  
  126.         #endregion  
  127.     }  
On the client program, call your extended class to check your functions,
  1. static void Main(string[] args)  
  2. {  
  3.   
  4.     //Demo   
  5.     Date myDate = new Date(Convert.ToDateTime("12-December-1994"));  
  6.     Console.WriteLine("Date: {0}", myDate.DateValueDefault?.ToString());  
  7.     Console.WriteLine("---------------");  
  8.     Console.WriteLine("Some default date properties --");  
  9.     Console.WriteLine("Year (invoking a property): {0}", myDate.GetPropertyValue("Year"));  
  10.     List<object> arguments = new List<object>() { 400 };  
  11.     Console.WriteLine("adding 400 days (invoking a method): {0}", myDate.InvokeMethodWithArguments("AddDays", arguments)); //AddDays  
  12.     Console.WriteLine("---------------");  
  13.     Console.WriteLine("Some Extended methods --");  
  14.     Console.WriteLine("Age as of Today: {0}", myDate.AgeAsOfToday());  
  15.     Console.WriteLine("Is it leap year: {0}", myDate.IsLeapYear());  
  16.     Console.WriteLine("Month Full Name: {0}", myDate.MonthFullName());  
  17.     Console.WriteLine("Month Short Name: {0}", myDate.MonthShortName());  
  18.     Console.WriteLine("Lies between {0} and {1}: {2}",   
  19.         Convert.ToDateTime("12-December-1992"),   
  20.         Convert.ToDateTime("31-December-1994"),   
  21.         myDate.DateBetween(Convert.ToDateTime("12-December-1992"), Convert.ToDateTime("31-December-1994"))  
  22.         );  
  23.     Console.ReadLine();  
  24.   
  25. }  
Here is the Console output,
  1. Date: 12-Dec-94 12:00:00 AM  
  2. ---------------  
  3. Some default date properties --  
  4. Year (invoking a property): 1994  
  5. adding 400 days (invoking a method): 16-Jan-96 12:00:00 AM  
  6. ---------------  
  7. Some Extended methods --  
  8. Age as of Today: 26  
  9. Is it leap year: False  
  10. Month Full Name: December  
  11. Month Short Name: Dec  
  12. Lies between 12-Dec-92 12:00:00 AM and 31-Dec-94 12:00:00 AM: True  

Conclusion

  1. Allows your typical validations on your parameters that are derived from base types.
  2. You can implement reusable utility functions that are string types, integer types, etc.
  3. You can copy the above code and extend your own functions as required.