Saturday, April 28, 2012

How to Determine the Day of the Week

Do you know what day of the week was the day you were born? Monday or maybe Saturday? Well, perhaps you know that. Everybody knows the day he’s born on, but do you know what day was the 31st of January in 1883? No? Well, there must be some method to determine any day in any century.
We know that 2012 started at Sunday. After we know that, it’s easy to determine what day is the 2nd of January. It should be Monday. But things get a little more complex if we try to guess some date distant from January the 1st. Indeed 1st of Jan was on Sunday, but what day is 9th of May the same year. This is far more difficult to say. Of course we can go with a brute force approach and count from 1/Jan till 9/May, but that is quite slow and error prone.



So what would we do if we had to code a program that answers this question? The easiest way is to use a library. Almost every major library has built-in functions that can answer what day is on a given date. Such are date() in PHP or getDate() in JavaScript. But the question remains: How these library functions know the answer and how can we code such library functions if our library doesn’t support such functionality?
There must be some algorithm to help us.

Overview

Because months have different number of days, and most of them aren’t divisible by 7 without a remainder, months begin on different days of the week. Thus, if January begins on Sunday, the month of February the same year will begin on Wednesday. Of course, in common years February has 28 days, which fortunately is divisible by 7 and thus February and March both begin on the same day, which is great, but isn’t true for leap years.

What Do We Know About the Calendar

First thing to know is that each week has exactly 7 days. We also know that a common year has 365 days, while a leap year has one day more – 366. Most of the months have 30 or 31 days, but February has only 28 days in common years and 29 in leap years.
Because 365 mod 7 = 1 in a common year each year begins exactly on the next day of the preceding year. Thus if 2011 started on Saturday, 2012 starts on Sunday. And yet again, that is because 2011 is not a leap year.




What else do we know? Because a week has exactly seven days only February (with its 28 days in a common year) is divisible by 7 (28 mod 7 = 0) and has exactly four weeks in it. Thus in a common year February and March start on a same day. Unfortunately that is not true about the other months.
All these things we know about the calendar are great, so we can make some conclusions. Although eleven of the months have either 30 or 31 days they don’t start on a same day, but some of the months do appear to start on a same day just because the number of days between them is divisible by 7 without a remainder.
Let’s take a look on some examples. For instance September has 30 days, as does November, while October, which is in between them has 31 days. Thus 30+30+31 makes 91. Fortunately 91 mod 7 = 0. So for each year September and December start on the same day (as they are after February they don’t depend on leap years). The same thing occurs to April and July and the good news is that in leap years even January starts on the same day as April and July.




Now we know that there are some relations between months. Thus, if we know somehow that the 13th of April is Monday, we’ll be sure that 13th of July is also Monday. Let’s see now a summary of these observations.




We can also refer to the following diagram.


For leap years there are other corresponding months. Let’s take a look at the following image.




Another way to get the same information is the following table.


We also know that leap years happen to occur once every four years. However, if there is a common year like the year 2001, which will be the next year that is common and starts and corresponds exactly on 2001? Because of leap years we can have a year starting on one of the seven days of the week and to be either leap or common. This means just 14 combinations.
Following these observations we can refer to the following table.


You can clearly see the pattern “6 4 2 0”
Here’s the month table.



Columns 2 and 3 differs only for January and February.
Clearly the day table is as follows:



Now let’s go back to the algorithm.
Using these tables and applying a simple formula, we can calculate what day was on some given date. Here are the steps of this algorithm.
  1. Get the number for the corresponding century from the centuries table;
  2. Get the last two digits from the year;
  3. Divide the number from step 2 by 4 and get it without the remainder;
  4. Get the month number from the month table;
  5. Sum the numbers from steps 1 to 4;
  6. Divide it by 7 and take the remainder;
  7. Find the result of step 6 in the days table;


Implementation

First let’s take a look at a simple and practical example of the example above and then the code. Let’s answer the question from the first paragraph of this post.
What day was on January 31st, 1883?
  1. Take a look at the centuries table: for 1800 – 1899 this is 2.
  2. Get the last two digits from the year: 83.
  3. Divide 83 by 4 without a remainder: 83/4 = 20
  4. Get the month number from the month table: Jan = 0.
  5. Sum the numbers from steps 1 to 4: 2 + 83 + 20 + 0 = 105.
  6. Divide it by 7 and take the remainder: 105 mod 7 = 0
  7. Find the result of step 6 in the days table: Sunday = 0.
The following code in PHP implements the algorithm above.


001.function get_century_code($century)
002.{
003.// XVIII
004.if (1700 <= $century && $century <= 1799)
005.return 4;
006. 
007.// XIX
008.if (1800 <= $century && $century <= 1899)
009.return 2;
010. 
011.// XX
012.if (1900 <= $century && $century <= 1999)
013.return 0;
014. 
015.// XXI
016.if (2000 <= $century && $century <= 2099)
017.return 6;
018. 
019.// XXII
020.if (2100 <= $century && $century <= 2199)
021.return 4;
022. 
023.// XXIII
024.if (2200 <= $century && $century <= 2299)
025.return 2;
026. 
027.// XXIV
028.if (2300 <= $century && $century <= 2399)
029.return 0;
030. 
031.// XXV
032.if (2400 <= $century && $century <= 2499)
033.return 6;
034. 
035.// XXVI
036.if (2500 <= $century && $century <= 2599)
037.return 4;
038. 
039.// XXVII
040.if (2600 <= $century && $century <= 2699)
041.return 2;
042.}
043. 
044./**
045.* Get the day of a given date
046.*
047.* @param $date
048.*/
049.function get_day_from_date($date)
050.{
051.$months array(
052.1 => 0,      // January
053.2 => 3,      // February
054.3 => 3,      // March
055.4 => 6,      // April
056.5 => 1,      // May
057.6 => 4,      // June
058.7 => 6,      // July
059.8 => 2,      // August
060.9 => 5,      // September
061.10 => 0, // October
062.11 => 3, // November
063.12 => 5, // December
064.);
065. 
066.$days array(
067.0 => 'Sunday',
068.1 => 'Monday',
069.2 => 'Tuesday',
070.3 => 'Wednesday',
071.4 => 'Thursday',
072.5 => 'Friday',
073.6 => 'Saturday',
074.);
075. 
076.// calculate the date
077.$dateParts explode('-'$date);
078.$century substr($dateParts[2], 0, 2);
079.$year substr($dateParts[2], 2);
080. 
081.// 1. Get the number for the corresponding century from the centuries table
082.$a = get_century_code($dateParts[2]);
083. 
084.// 2. Get the last two digits from the year
085.$b $year;
086. 
087.// 3. Divide the number from step 2 by 4 and get it without the remainder
088.$c floor($year / 4);
089. 
090.// 4. Get the month number from the month table
091.$d $months[$dateParts[1]];
092. 
093.// 5. Sum the numbers from steps 1 to 4
094.$e $a $b $c $d;
095. 
096.// 6. Divide it by 7 and take the remainder
097.$f $e % 7;
098. 
099.// 7. Find the result of step 6 in the days table
100.return $days[$f];
101.}
102. 
103.// Sunday
104.echo get_day_from_date('31-1-1883');

Application

This algorithm can be applied in many different cases although most of the libraries have built-in functions that can do that. The only problem besides that is that there are much more efficient algorithms that don’t need additional space (tables) of data. However this algorithm isn’t difficult to implement and it gives a good outlook of some facts in the calendar.

Of course you can implement it in the most complex way possible, but the following few lines will do the same for years 1700 to at least 4000 and are much easier to understand (in Java):
view sourceprint
?
1.public static int getWeekday(int y, int m, int d) {
2.// 1700-1-1 = 5 (Friday)
3.int[] mo = new int[] { 0315990120151181212,243273304334 }; // monthly offset
4.int af = m > 2 0 1// after february
5.// every fourth year is a leap year, unless it is divisible by 100 unless it is divisible by 400
6.int w = 5 + (y-1700)*365 + (y-1700-af)/4 - (y-1700-af)/100+ (y-1600-af)/400 + mo[m-1] + (d-1);
7.return w % 7;
8.}

No comments:

Post a Comment