As I mentioned once before, I have read (and since re-read twice) the rather excellent Humble Pi by Matt Parker. Apart from being very enjoyable, it’s a great source of ideas for practicing Linq skills.
Whilst discussing calendars and dates, he mentions a common bit of rubbish that floats around the Internet on a fairly regular basis. This goes something along these lines (with many variations on a theme)…
Follow instructions and wait 4 days!! This year, December (or May, or June, or some other random month) has 5 Saturdays, 5 Sundays and 5 Mondays.
This only happens once every 823 years. The Chinese call it Feng Shui (the Money Bag).
Send this to all your friends – and enemies as well.
According to the legend, you will receive money in 4 days.
According to Feng Shui, if you do not send it you will stay poor.
I in particular, will not let this opportunity pass by.
So I send it to you with my best wishes. In case it is true!
Even if you accept that sending an email to your friends is going to bring you good luck (spoiler: I don’t), there are other reasons why you shouldn’t forward this to all your friends, or anyone else for that matter. Given how often these emails go around, there should be a lot of very rich people around!
If you want to read the full explanation of why this is a pile of codswallop, you can read Humble Pi (a good idea anyway), or search around and read one of the many blogs about it. Briefly, the Gregorian calendar (which is what most of us use) has a fixed cycle of 400 years, so nothing, and I mean absolutely nothing can take longer than that to recur. Therefore, this 823 years (sometimes 824) is an indication that it’s wrong.
Furthermore, there are only two types of year, leap and non-leap, and only seven days on which the 1st January can fall, so only 14 possible combinations. Whilst they might not all come up with equal frequency (haven’t checked that bit), it’s pretty certain that they will come up pretty frequently.
So, with all that aside, if you have a mind like mind (and I pity you if you have, because it’s weird in here sometimes), you might be wondering how often it does actually happen. As ever, Linq comes to the rescue, and as ever, I’m not claiming that it’s the best solution, but it’s a great way to expand your skills.
So, where do we start? First off, let’s pick a range of years and see how often December has 5 Saturdays, 5 Sundays and 5 Mondays in that range. Getting the years is easy…
I picked the last century, just or simplicity, but obviously this can be changed to any range you want.
Having got the years, we want to check the days in December. By using a SelectMany clause that contains another range, this time for the days of the month, we can generate a list of all the dates in each December…
Enumerable.Range(1900, 100) .SelectMany(year => Enumerable.Range(1, 31) .Select(d => (year, new DateTime(year, 12, d))))
This outputs data like this…
Note that instead of just outputting the date, I created a tuple that included the year. I didn’t really need to do this as I could pick up the year from any of the dates, but it makes the code slightly cleaner later on.
I snipped a part of the output that shows the end of one December and the start of the next, so you can see it’s only generating dates for each December.
To check how many times Monday, Saturday and Sunday recur during each December, we need to handle each December individually. I did this by grouping the data on the year…
Enumerable.Range(1900, 100) .SelectMany(year => Enumerable.Range(1, 31) .Select(d => (year, new DateTime(year, 12, d)))) .GroupBy(g => g.year)
I now had a set of groups, the key of each being the year, and the items in the group being the days of December for that year. It was now a simple matter of finding which Decembers had 5 of each of the days…
Enumerable.Range(1900, 100) .SelectMany(year => Enumerable.Range(1, 31) .Select(d => (year, new DateTime(year, 12, d)))) .GroupBy(g => g.year) .Where(g => g.Count(x => x.Item2.DayOfWeek == DayOfWeek.Monday) > 4 && g.Count(x => x.Item2.DayOfWeek == DayOfWeek.Saturday) > 4 && g.Count(x => x.Item2.DayOfWeek == DayOfWeek.Sunday) > 4) .Select(g => g.Key)
The last Select is only really there to clean up the output, so all you see is the year, not the group as well.
This showed that there were 14 years in the last century where December has 5 Saturdays, 5 Sundays and 5 Mondays. By increasing the second parameter to the first line to 400, I could see how many times this happened in a full 400 year Gregorian cycle. It turned out to be 58. A far cry from only happening once in 823 years eh?
The following day…
Well, if there’s one thing I should have learned form this, it’s don’t do these things late at night. I lay in bed thinking about it far too long!
Sometime in the night, a little light bulb went off in my head 💡. December has 31 days, of which the first 28 will consist of exactly 4 of each weekday. Therefore, the only days that can occur 5 times in December are those that fall on the 29th, 30th and 31st. This means that the only way you can have 3 days occurring 5 times each is if they are consecutive days. Duh, I realised that when I read the original bit of rubbish, I missed the fact that the three days were in fact consecutive. Other versions of this that get sent around are more cleverly worded and list them differently, such as “Mondays, Saturdays and Sundays,” fooling your mind into not noticing that they are actually consecutive.
Therefore, finding out when December has 5 of each of the three days mentioned above is actually as simple as checking if the 29th is a Monday, which makes the 30th a Saturday and the 31st a Sunday. This means the whole thing could have been done much more simply…
Enumerable.Range(1900, 100) .Count(y => new DateTime(y, 12, 29).DayOfWeek == DayOfWeek.Monday)
Of course, at this point another light bulb went off, and I realised that any month with 31 days has got to contain three (consecutive) weekdays days that occur 5 times each. As there are 6 such months in every year, this means that this supposedly rare occurrence actually happens every other month! Try it yourself, pick the next (or current) 31-day month and look at what day the 29th falls out on. As I type, it’s January, and the 29th will be a Friday, meaning that this month will have 5 Fridays, Saturdays and Sundays. Similarly, the 29th March is a Monday, meaning that March has 5 Mondays, Tuesdays and Wednesdays.
So, far from being unusual, this is absolutely as common as muck, and about as interesting!
The embarrassing thing about this is that almost four years ago, I thought I had learnt exactly the same lesson, think before you Linq. Like yesterday, I had jumped into the code without thinking enough about the problem.
I’d like to think that I learnt my lesson this time, but considering that I made the same mistake again, I don’t think there’s much hope!