Use DateTime to disprove internet calendar memes

There’s a popular internet meme that says a July will only ever have five complete weekends, a Friday-Saturday-Sunday triplet, only every 823 years. To programmers, that might seem just absurd, but a lot of people believe it without thinking about it: that’s how it becomes the popular internet meme.

It’s almost trivial to disprove the 823 year claim with a quick, back-of-the-envelope sketch. You can quickly figure out that any month that has 31 days and starts on a Friday will have five such triplets. If your weekend starts on another day, you just have to adjust the triplet (although I don’t know if all calendars will have months with 31 days). Since there are only seven different days of the week, it’s unbelievable that you’d have to wait 823 years for a July to start on one of those possible days.

How often does a month have 31 days and start on a Friday? You could just go through all of your old calendars yourself, but not only would that be slow, but people might wonder why you kept all of your old calendars.

Instead of doing it by brute force, use a computer program to automate that work and save yourself a lot of time. Almost any time that you have a question about calendars, the DateTime module can probably solve it for you quickly. Many people think it’s easier to code up their own solutions, but most people also don’t know about all of the odd calendaric issues that their code needs to take into account. DateTime takes care of all of that for you. When you’re working with calendars, you’ll save yourself a lot of hassle and many more errors by not doing it yourself.

You can quickly code up the brute force approach by checking the first day of July for as many years as you like:

use 5.010;

use DateTime;

my $start_year  = 1900;
my $end_year    = 2012;

say "Checking $start_year to $end_year for 5 weekends in July";

foreach my $year ( $start_year .. $end_year ) {
	my $first_day = DateTime->new( 
		month => 7,
		year  => $year,
		day   => 1,
		);

	next unless $first_day->day_name eq 'Friday';
	say $year;
	}

Going from 1900 to 2012, you see that in the 113 year range there are 16 years that have Julys with five complete weekends:

Checking 1900 to 2012 for 5 weekends in July
1904 
1910 
1921 
1927 
1932 
1938 
1949 
1955 
1960 
1966 
1977 
1983 
1988 
1994 
2005 
2011 

When you extend that back 823 years, you find 120 years that had five weekends in July. That is, roughly 14% of the years have that special July instead of the 0.12% the meme claims. That’s two orders of magnitude!

What might be more interesting is the cycle of the pattern.

Now that you know that for July, though, you might want to figure it out for other months. There are seven months (January, March, May, July, August, October, December) that have 31 days, so they each might have five weekends too. It’s easy to check each month, which is something you may have never considered doing if DateTime didn’t make it so easy:

use 5.010;

use DateTime;

my $start_year  = 2011 - 823 + 1;
my $end_year    = 2011;

say "Checking $start_year to $end_year for 5 weekends";

foreach my $year ( $start_year .. $end_year ) {
	foreach my $month ( 1 .. 12 ) {
		my $last_day = DateTime->last_day_of_month( 
			month => $month,
			year  => $year
			);
		next unless $last_day->day == 31;

		my $first_day = DateTime->new( 
			month => $month,
			year  => $year,
			day   => 1,
			);

		next unless $first_day->day_name eq 'Friday';
		say "$year $month";
		}
	}

This range covers 2011 back for 823 years. There’s an interesting result that you might only discover accidently. In those 823 years, there are 823 months that have five weekends:

1189 12
1191 3
1192 5
1193 1
1193 10
1194 7
1195 12
1196 3
1197 8
...
2010 1
2010 10
2011 7

On average, there is a five-weekend month every year. Does it strike you as odd that every week has seven days and that every year has seven months with 31 days and that, on average, one of those months starts with one of those days? It might be more perfect than that but you have to remember that in the cycle of calendars, leap days put the progression of first days out of wack every so often (and that in 823 years, there’s also the 400-year non-leap-year anomaly). Would you have ever noticed this if DateTime hadn’t made it easy to count the months?

You can go a bit further with this. Which years don’t have any month with five weekends? Collect the years with weekends in a hash then look at the hash keys to see which years aren’t keys:

use 5.010;

use DateTime;

my $start_year  = 2011 - 823 + 1;
my $end_year    = 2011;

say "Checking $start_year to $end_year for 5 weekends";

my %hash = ();

foreach my $year ( $start_year .. $end_year ) {
	$years++;
	foreach my $month ( 1 .. 12 ) {
		my $last_day = DateTime->last_day_of_month( 
			month => $month,
			year  => $year
			);
		next unless $last_day->day == 31;

		my $first_day = DateTime->new( 
			month => $month,
			year  => $year,
			day   => 1,
			);

		next unless $first_day->day_name eq 'Friday';
		$hash{$year}++;
		}
	}

say "Years without a five-weekend month";
foreach my $year ( $start_year .. $end_year ) {
	next if exists $hash{$year};
	say $year;
	}

This gives you 119 years that don’t have any month with five weekends. However, remember that seven months of every year have five triplets of days, and it’s whatever day the month starts on.

Although out of scope for this problem, you could also do some more work to discover the periodicity of the pattern of five weekend Julys. How many years are between one year with a five weekend July to the next one? Here’s a short program to get the gaps:

use 5.010;

use DateTime;

my $start_year  = 2011 - 823;
my $end_year    = 3000;

my $last_year;

foreach my $year ( $start_year .. $end_year ) {
	my $first_day = DateTime->new( 
		month => 7,
		year  => $year,
		day   => 1,
		);

	next unless $first_day->day_name eq 'Friday';
	push @periods, $year - $last_year;
	$last_year = $year;
	}

shift @periods; # the first one is bad because $last_year was undef

say join '-', @periods;

That gets you a long string in which there are some repeating parts, whose length would be the period of the pattern:

6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-6-6-5-6-11-6-5-6-11-6-5-6-11-6-5-7-5-6-11-6-5-6-11-6-5-6-11-6-5-6-6-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-6-6-5-6-11-6-5-6-11-6-5-6-11-6-5-7-5-6-11-6-5-6-11-6-5-6-11-6-5-6-6-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-6-6-5-6-11-6-5-6-11-6-5-6-11-6-5-7-5-6-11-6-5-6-11-6-5-6-11-6-5-6-6-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-6-6-5-6-11-6-5-6-11-6-5-6-11-6-5-7-5-6-11-6-5-6-11-6-5-6-11-6-5-6-6-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-5-6-11-6-6-6-5-6-11-6-5-6-11-6-5-6-11-6-5

What’s the shortest consecutively repeating string in that? That’s a bit out of the scope of this Item though, but with a little manual intervention and a regular expression, the pattern in gaps turns out to be ( 7 5 6 11 6 5 6 11 6 5 6 11 6 5 6 6 6 11 6 5 6 11 6 5 6 11 6 5 6 11 6 5 6 11 6 5 6 11 6 5 6 11 6 6 6 5 6 11 6 5 6 11 6 5 6 11 6 5 ). There’s a micro-pattern of ( 11 6 5 6 ), but every so often that’s disrupted, inserting some extra years to re√ęstablish the sequence, sometimes throwing in a 7. If you sum those gaps, you get 400, the same number as period for repeating a calendar cycle with a skip in the divisible-by-100 leap year rule. If you look at it more closely, you’ll quickly realize that the deviations depend on how the pattern fell across a leap year. The deviations get the sequence back on track, although it takes 12 years to do it, in gaps of either ( 6 6 ) or ( 7 5 ). There’s no mysticism here. What did you expect from a calendar that does the same thing every 400 years?

If you want to read more about calendars, there are plenty of websites that will do a much better job than I could do, so I’ll leave you to your googling.

Things to remember

  • Use DateTime to handle any calendar details
  • Use DateTime to disprove calender memes