Sunday, August 9, 2015

Convert existing event handler invocations to C#6 Null Propagation

Search for:

if \(handler \!= null\)\s*\r?\n\s*\{\r?\n\s*handler\((?<args>.*?)\);\r?\n\s*\}
and replace using:
handler?.Invoke(${args});

For example, this will be matched and replaced

if (handler != null)
{
    handler(this, new DialogResponseEventArgs(id, true));
}

with this
handler?.Invoke(this, new DialogResponseEventArgs(this.dialogCorrelationId, message.Response == ShellDialogButton.Cancel));

Converting existing code to use C#6 Expression Bodied Properties

Converting all types of Simple Properties

Search for

[^\S\r\n]+(?<accessor>\w+)(?<override> override | )(?<typename>\w+) (?<pname>.*?)\r?\n\s*\{\r?\n\s*get(\r?\n)*\s*\{\s*return (?<returnexpr>.*?);\s*\}\s*\}
and replace with
${accessor} ${override}${typename} ${pname} => ${returnexpr};

For example the following properties will be matched and converted:

public override string Description 
{
    get 
    {
        return "The quick brown fox jumped over the lazy dog.";
    }
}
public string Description 
{
    get 
    {
        return "The quick brown fox jumped over the lazy dog.";
    }
}
public string Description 
{
    get { return "The quick brown fox jumped over the lazy dog."; }
}
public bool HasValue 
{
    get { return this.field; }
}
public virtual IEnumerable<BudgetBucket> Buckets
{
    get { return this.lookupTable.Values.OrderBy(b => b.Code).ToList(); }
}

All the above are changed to the new syntax:
public override string Description => "The quick brown fox jumped over the lazy dog.";

Converting existing code to use nameof() in C# 6

Now with the new C#6 nameof operator, the use of ArgumentNullExceptions and ArgumentExceptions become more flexible and resistant to variable refactoring and renaming.

ArgumentNullException

Here's a regex to search and replace in Visual Studio usage of ArgumentNullException: Search for:
throw new ArgumentNullException("(?<message>.*?)
Replace with:
throw new ArgumentNullException(nameof(${varname})

For example this will replace this
throw new ArgumentNullException("variable", "This variable cannot be null.");
with
throw new ArgumentNullException(nameof(variable), "This variable cannot be null.");

ArgumentException

Here's another regex to search and replace usage of ArgumentException: Search for:
throw new ArgumentException\("(?<message>.*?)", "(?<varname>.*?)"
Replace with:
throw new ArgumentException("${message}", nameof(${varname})
For example this will replace this
throw new ArgumentException("This argument is invalid for some weird reason.", "variable");
with
throw new ArgumentException("This argument is invalid for some weird reason.", nameof(variable));

Tuesday, January 6, 2015

Calculating NZ Public Holidays


I had to write a class to calculate the exact date of statutory holidays in New Zealand recently.  It might not be the most efficient way, but it all works.  The most up to date code is in github.

    public class NewZealandPublicHolidays
    {
        private static readonly List<Holiday> HolidayTemplates = new List<Holiday>
        {
            new FixedDateHoliday { Name = "New Years Day", Day = 1, Month = 1, PushOutIfOnWeekend = true },
            new FixedDateHoliday { Name = "New Years Holiday", Day = 2, Month = 1, PushOutIfOnWeekend = true },
            new FixedDateHoliday { Name = "Waitangi Day", Day = 6, Month = 2, PushOutIfOnWeekend = true },
            new EasterHoliday { Name = "Good Friday", Day = DayOfWeek.Friday },
            new EasterHoliday { Name = "Easter Monday", Day = DayOfWeek.Monday },
            new FixedDateHoliday { Name = "ANZAC Day", Day = 25, Month = 4, PushOutIfOnWeekend = true },
            new FixedDateHoliday { Name = "Christmas Day", Day = 25, Month = 12, PushOutIfOnWeekend = true },
            new FixedDateHoliday { Name = "Boxing Day", Day = 26, Month = 12, PushOutIfOnWeekend = true },
            new IndexDayHoliday { Name = "Queen's Birthday", Day = DayOfWeek.Monday, Month = 6, Index = 0 },
            new IndexDayHoliday { Name = "Labor Day", Day = DayOfWeek.Monday, Month = 10, Index = 3 },
            new DayClosestToHoliday { Name = "Auckland Anniversary", DesiredDay = DayOfWeek.Monday, Month = 1, CloseToDate = 29 }
        };

        public static IEnumerable<Tuple<string, DateTime>> CalcuateHolidaysVerbose(DateTime start, DateTime end)
        {
            var holidays = new Dictionary<DateTime, string>();
            foreach (var holidayTemplate in HolidayTemplates)
            {
                var proposedDate = holidayTemplate.CalculateDate(start, end);

                if (holidayTemplate.PushOutIfOnWeekend && (proposedDate.DayOfWeek == DayOfWeek.Saturday || proposedDate.DayOfWeek == DayOfWeek.Sunday))
                {
                    do
                    {
                        proposedDate = proposedDate.AddDays(1);
                    } while (proposedDate.DayOfWeek != DayOfWeek.Monday);
                }

                if (holidays.ContainsKey(proposedDate))
                {
                    holidays.Add(proposedDate.AddDays(1), holidayTemplate.Name);
                }
                else
                {
                    holidays.Add(proposedDate, holidayTemplate.Name);
                }
            }

            return holidays.Select(h => new Tuple<string, DateTime>(h.Value, h.Key)).OrderBy(d => d.Item2);
        }

        public static IEnumerable<DateTime> CalculateHolidays(DateTime start, DateTime end)
        {
            return CalcuateHolidaysVerbose(start, end).Select(t => t.Item2);
        }

        private abstract class Holiday
        {
            public string Name { get; set; }
            public bool PushOutIfOnWeekend { get; set; }
            public abstract DateTime CalculateDate(DateTime start, DateTime end);
        }

        private class FixedDateHoliday : Holiday
        {
            public int Day { get; set; }
            public int Month { get; set; }

            public override DateTime CalculateDate(DateTime start, DateTime end)
            {
                for (var year = start.Year; year <= end.Year; year++)
                {
                    var proposed = new DateTime(year, Month, Day);
                    if (proposed >= start && proposed <= end)
                    {
                        return proposed;
                    }
                }

                throw new InvalidOperationException(string.Format("Cannot find a suitable date between {0} and {1}", start, end));
            }
        }

        private class IndexDayHoliday : Holiday
        {
            public DayOfWeek Day { get; set; }
            public int Index { get; set; }
            public int Month { get; set; }

            public override DateTime CalculateDate(DateTime start, DateTime end)
            {
                for (var year = start.Year; year <= end.Year; year++)
                {
                    var proposed = ProposeDate(year);
                    if (proposed >= start && proposed <= end)
                    {
                        return proposed;
                    }
                }

                throw new InvalidOperationException(string.Format("Cannot find a suitable date between {0} and {1}", start, end));
            }

            private DateTime ProposeDate(int year)
            {
                var proposed = new DateTime(year, Month, 1);
                while (proposed.DayOfWeek != Day)
                {
                    proposed = proposed.AddDays(1);
                }

                var count = 0;
                while (count != Index)
                {
                    proposed = proposed.AddDays(7);
                    count++;
                }
                return proposed;
            }
        }

        private class DayClosestToHoliday : Holiday
        {
            public int CloseToDate { get; set; }
            public DayOfWeek DesiredDay { get; set; }
            public int Month { get; set; }

            public override DateTime CalculateDate(DateTime start, DateTime end)
            {
                for (var year = start.Year; year <= end.Year; year++)
                {
                    var proposed = new DateTime(year, Month, CloseToDate);
                    switch (proposed.DayOfWeek)
                    {
                        case DayOfWeek.Sunday:
                            proposed = proposed.AddDays(1);
                            break;
                        case DayOfWeek.Monday:
                            break;
                        case DayOfWeek.Tuesday:
                            proposed = proposed.AddDays(-1);
                            break;
                        case DayOfWeek.Wednesday:
                            proposed = proposed.AddDays(-2);
                            break;
                        case DayOfWeek.Thursday:
                            proposed = proposed.AddDays(-3);
                            break;
                        case DayOfWeek.Friday:
                            proposed = proposed.AddDays(3);
                            break;
                        case DayOfWeek.Saturday:
                            proposed = proposed.AddDays(2);
                            break;
                    }

                    if (proposed >= start && proposed <= end)
                    {
                        return proposed;
                    }
                }

                throw new InvalidOperationException(string.Format("Cannot find a suitable date between {0} and {1}", start, end));
            }
        }

        private class EasterHoliday : Holiday
        {
            public DayOfWeek Day { get; set; }

            public override DateTime CalculateDate(DateTime start, DateTime end)
            {
                for (var year = start.Year; year <= end.Year; year++)
                {
                    // first calculate Easter Sunday
                    var day = 0;
                    var month = 0;

                    var goldenNumber = year % 19;
                    var century = year / 100;
                    var h = (century - century / 4 - (8 * century + 13) / 25 + 19 * goldenNumber + 15) % 30;
                    var i = h - h / 28 * (1 - h / 28 * (29 / (h + 1)) * ((21 - goldenNumber) / 11));

                    day = i - ((year + year / 4 + i + 2 - century + century / 4) % 7) + 28;
                    month = 3;

                    if (day > 31)
                    {
                        month++;
                        day -= 31;
                    }

                    var proposed = new DateTime(year, month, day);

                    switch (Day)
                    {
                        case DayOfWeek.Friday:
                            proposed = proposed.AddDays(-2);
                            break;
                        case DayOfWeek.Sunday:
                            break;
                        case DayOfWeek.Monday:
                            proposed = proposed.AddDays(1);
                            break;
                        case DayOfWeek.Tuesday:
                            proposed = proposed.AddDays(2);
                            break;
                        default:
                            throw new NotSupportedException("Only Easter Friday, Monday, and Tuesday are supported.");
                    }

                    if (proposed >= start && proposed <= end)
                    {
                        return proposed;
                    }
                }

                throw new InvalidOperationException(string.Format("Cannot find a suitable date between {0} and {1}", start, end));
            }
        }
    }