Not so long ago, I was given a job interview task. I was to write a function which deduces the day of the standard 7-day week of an imaginary calendar, provided I know how often leap years happen, if at all, how many months their year has, and how many days each month has.

This is a fairly common introductory job-interview task, and in this article I'll be solving and explaining the math behind it. I'm no Rainman so feel free to throw simplifications and corrections at me – I'm sure my way is one of the needlessly complex ones. This article will be presenting two ways of approaching the problem – one that allows you to mentally do this on-the-fly (impress your friends, I guess?), and one that is more computer friendly (fewer lines of code, larger numbers).

The definition of the calendar I was given went as follows:

• each year has 13 months
• each even month has 21 days, and each odd month has 22
• the 13th month lacks a day every leap year
• a leap year is any year divisible by 5
• each week has 7 days: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday

Given that the first day of the year 1900 was Monday, write a function that will print the day of the week for a given date. Example, for input {day: 17, month: 11, year: 2013} the output is "Saturday".

Throughout the rest of the article, I will be using the following date format: dd.mm.yyyy, because it's what makes sense.

## Preparation

Before starting out on any brainy venture, it's important to have a proper environment set up in order to avoid wasting time on things that could have been prepared in advance. I always recommend you venture into coding job interview tasks with a revved up development environment, ready to test your code at a moment's notice.

Create a new folder containing two subfolders: `classes`, and `public`. Yes, this is a throwaway task that can be solved in a simple procedural function, but I like to be thorough. You'll see why.

In the `classes` subfolder, create an empty PHP class called `CalendarCalc.php`. In the `public` subfolder, create a file called `index.php` with the following content:

``````<?php

require_once '../classes/CalendarCalc.php';
echo "Hello";``````

If you can open this in your browser and "Hello" is displayed, you're ready to get started.

## CalendarCalc initialization

To make things easier to verify and visualize, I've created a demo method which prints out the entire calendar from 1.1.1900. to 22.13.2013. This will allow us to easily check if our calculation function works. First, though, initialize the class like so:

``````<?php

class CalendarCalc {

/** @var array */
protected \$aDays = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');

/** @var int Cached number of days in a week, to avoid recounts. This way, we can alter the week array at will */
protected \$iNumDays;

/** @var int Array index of starting day (1.1.1900.), i.e. Monday = 1*/
protected \$iStartDayIndex;

/** @var int */
protected \$startYear = 1900;

/** @var int */
protected \$leapInterval = 5;

/** @var array Array gets populated on instantiation with date params. This is to make params accessible to every alternative calculation method. */
protected \$aInput = array();

public function __construct(\$day, \$month, \$year) {
\$this->aInput = array('d' => \$day, 'm' => \$month, 'y' => \$year);
}

public function demo() {
}
}``````

Let's explain the protected properties.

`\$aDays` is an array of days. Defining it made sure each day of the week has a numeric index assigned to it – something of utmost importance when determining the day of the week later on. We cache its length with the `\$iNumDays` property. This allows us to expand the days array later on, if we so choose – another task might ask for the same calculation, but might mention that the week has more or less than 7 days.

`\$iStartDayIndex` is the index of Monday (in this case), because the start day (1.1.1900.) is defined as Monday in the task description. When we have the index of the start day, we can use it in tandem with a calculated offset to get the real day of the week. You'll understand what I mean in a bit.

`\$aInput` is an array to hold the input values. When we instantiate the CalendarCalc, we pass in the date values for which we want to know the day of the week. This property stores those values, making them available for every alternative calc method we think up, thus making sure we don't need to forward them along, or even worse, repeat them in another function call. The logic of `\$aInput`, `\$iStartDayIndex` and `\$iNumDays` is in the `__construct` method.

The other properties are self-explanatory.

Now, populate the `demo()` method with the following content:

``````    public function demo() {

\$demoYear = \$this->startYear;
\$totalDays = 0;

while (\$demoYear < 2014) {

echo "<h2>\$demoYear</h2><table>";
\$demoMonth = 1;
while (\$demoMonth < 14) {
echo "<tr><td colspan='7'><b>Month \$demoMonth</b></td></tr>";
echo "<tr><td>Monday</td><td>Tuesday</td><td>Wednesday</td><td>Thursday</td><td>Friday</td><td>Saturday</td><td>Sunday</td></tr>";

\$dayCount = (\$demoMonth % 2 == 1) ? 22 : 21;
\$dayCount = (\$demoMonth == 13 && \$demoYear % 5 == 0) ? 21 : \$dayCount;

\$demoDay = 1;

echo "<tr>";
while (\$demoDay <= \$dayCount) {
\$index = ++\$totalDays % 7;
if (\$demoDay == 1) {
for (\$i = 0; \$i < \$index-1; \$i++) {
echo "<td></td>";
}
if (\$index == 0 || \$index == 7) {
\$i = 6;
while (\$i--) {
echo "<td></td>";
}
}
}
echo "<td>\$demoDay</td>";
if (\$index == 0) {
echo "</tr><tr>";
}
\$demoDay++;
}
echo "</tr>";
\$demoMonth++;
}
echo "</table><hr />";
\$demoYear++;
}
}``````

Don't bother trying to understand this method – it's entirely unimportant. It's only there to help us verify our work, and is in fact partially based on the second solution we'll be presenting in this article.

Change the contents of the index.php file to:

``````<?php

require_once '../classes/CalendarCalc.php';

\$cc = new CalendarCalc(17, 11, 2013);
\$cc->demo();``````

…and open it in the browser. You should see a calendar output not unlike the one in the image below:

We now have a way to check the results against the truth (notice that the date 17.11.2013. is indeed Saturday).

## The mental way

The mental way to do this calculation is actually quite simple. First, we need the number of leap years between the base date, and our given date. 1900 is divisible by 5 and is itself a leap year. The number of leaps is thus the difference in years between the input date and the base date, divided by 5, rounded down (only fully elapsed years count, naturally), plus one for 1900. Create a new method in `CalendarCalc` called `calcFuture` and give it this content:

``\$iLeaps = floor((\$this->aInput['y'] - \$this->startYear) / \$this->leapInterval + 1);``

We were also told that each even month has 21 days, and each odd month has 22:

1 => 22
2 => 21
3 => 22
4 => 21
5 => 22
6 => 21
7 => 22
8 => 21
9 => 22
10 => 21
11 => 22
12 => 21
13 => 22 (or 21 on leap years)

The total number of days in their year is, thus, 280, or 279 on leap years. If we take the modulo of 280 % 7, we get 0, because 280 is divisible by 7. On leap years, the modulo is 6.

This means that every year of that calendar begins on the same day, except on leap years, when it begins on the day that precedes the previous year's first day. Thus, if 1.1.1900. was Monday:

• 1.1.1901. is Monday
• 1.1.1902. is Sunday
• 1.1.1903. is Sunday
• 1.1.1904. is Sunday
• 1.1.1905. is Saturday
• 1.1.1906. is Saturday
• etc…

According to this, we can calculate the number of day moves until our input year. Seeing as we know we have 23 leaps until the input date (2013), we moved back a day 23 times. The modulo of 23 % 7 is 2, meaning we came full circle 3 times and then two more days (this is the offset) – 1.1.2013. was Saturday. Check on the demo calendar and see for yourself.

Let's put this into code. After the "leaps" line above, add the following:

``````\$iOffsetFromCurrent = \$iLeaps % \$this->iNumDays;

\$iNewIndex = \$this->iStartDayIndex - \$iOffsetFromCurrent;

if (\$iNewIndex < 0) {
\$iFirstDayInputYearIndex = \$this->iStartDayIndex + \$this->iNumDays - \$iOffsetFromCurrent;
} else {
\$iFirstDayInputYearIndex = \$iNewIndex;
}``````

First, we calculate the offset. Then, we calculate the new index of the days array, which varies depending on whether or not the new index is positive. This gives us the day of the week on which our input year starts.

We also know that each month X with 21 days makes the next month Y start on the same day as month X did, because 21 % 7 = 0. During odd months, however, the starting day moves ahead by one (22 % 7 = 1). So if Month 1 started with Saturday, Month 2 starts with Sunday, Month 3 with Sunday, Month 4 with Monday, and so on. We conclude that every odd month that passed since the start of the year until our input date's month has advanced the day index by 1. We're in month 11, so there were 5 odd months. The new offset is +5 or in our case, the 11th month of 2013 begins on Thursday. Let's put it into code immediately under the previous lines.

``````\$iOddMonthsPassed = floor(\$this->aInput['m'] / 2);

\$iFirstDayInputMonthIndex = (\$iFirstDayInputYearIndex + \$iOddMonthsPassed) % \$this->iNumDays;``````

All that's left now is to see how far removed from the beginning of the month our input date's day is.

``````\$iTargetIndex = (\$iFirstDayInputMonthIndex + \$this->aInput['d']-1) % \$this->iNumDays;

We add the day number minus one (because the day hasn't passed yet!) and modulo it with 7, the number of days. The number we get is our target index, reliably giving us Saturday.

From the top now, the whole `calcFuture` method of `CalendarCalc` goes like this:

``````    /**
* A more "mental" way of calculating the day of the week
* @return mixed
*/
public function calcFuture() {
\$iLeaps = floor((\$this->aInput['y'] - \$this->startYear) / \$this->leapInterval + 1);
\$iOffsetFromCurrent = \$iLeaps % \$this->iNumDays;

\$iNewIndex = \$this->iStartDayIndex - \$iOffsetFromCurrent;

if (\$iNewIndex < 0) {
\$iFirstDayInputYearIndex = \$this->iStartDayIndex + \$this->iNumDays - \$iOffsetFromCurrent;
} else {
\$iFirstDayInputYearIndex = \$iNewIndex;
}

\$iOddMonthsPassed = floor(\$this->aInput['m'] / 2);

\$iFirstDayInputMonthIndex = (\$iFirstDayInputYearIndex + \$iOddMonthsPassed) % \$this->iNumDays;

\$iTargetIndex = (\$iFirstDayInputMonthIndex + \$this->aInput['d']-1) % \$this->iNumDays;

}``````

## The machine-friendly way

A perhaps simpler approach is just calculating the number of days that have elapsed since the base date, modulo that by 7 and get the offset that way. There's not many people who can calculate numbers of this magnitude on the fly, though, and that's why it's more machine-friendly.

Again, we need leaps:

``````public function calcFuture2() {
\$iTotalDays = 0;

\$iLeaps = floor((\$this->aInput['y'] - \$this->startYear) /    \$this->leapInterval + 1);
}``````

Then, take years into account first. That's 280 times number of elapsed years, minus number of leaps to account for lost days, plus one because the current year is still ongoing.

``        \$iTotalDays = (280 * (\$this->aInput['y'] - \$this->startYear)) - \$iLeaps + 1;``

Then, we add in the days by summing up all the elapsed months.

``        \$iTotalDays += floor(\$this->aInput['m'] / 2) * 21 + floor(\$this->aInput['m'] / 2) * 22;``

Finally, we add the days of the input date, again minus one because the current day hasn't passed yet:

``````        \$iTotalDays += \$this->aInput['d'] - 1;

## Conclusion

To see a live example of this calculation, please check here. You can browse the containing directory at that URL to see the files, or you can download the complete source code of that demo site, along with the final `CalendarCalc` class, from Github. The repo/demo has slightly more code than presented in this article – some html5boilerplate was used to make it more organized and to enable ajax requests to check the dates as you enter them, so you don't need to reload the screen and regenerate the calendar every time you check for a date.

If you have alternative solutions or suggestions for improvement, please leave them in the comments below – like I said I'm no math wiz and I welcome the opportunity to learn more. For example, one should take corner cases into account – edge dates, or dates in the past require more modifications to the original algorithm. I'll leave that up to you. Feel free to submit a pull request and you'll get a shout out in the article!

Hope you enjoyed this and learned something new! Good luck in your interviews!

The format yyyymmdd makes more sense than ddmmyyyy, since with the former one can sort the dates lexicographically. Digits worth more should come to the left, just as for ordinary numbers.

• http://www.bitfalls.com/ Bruno Skvorc

That’s true. The ddmmyyyy format in this article was only used for human consumption, though.

I don’t consume dates in that format. Unfortunately things that I do consume sometimes have such dates on their packages. I some cases that makes it difficult to know if it really is consumable or not.

• http://www.bitfalls.com/ Bruno Skvorc

Ba-dum-tssss

• jamal

Really, seems a bit daunting for me. Granted, I just started programming in 2013, but I am bit curious is should this be excepted for someone looking for an entry level type of job or is it for someone with intermediate to advanced skills.

• http://www.bitfalls.com/ Bruno Skvorc

This is a senior level position intro task. It’s the kind of task you get before getting a real technical interview for a senior position.

• Walter

The use of the built in Datetime class (injected into your class) would have reduced a lot the code base. In your example specifically, I would have built a single big string containing the whole table, by issuing only one single echo statement at the end of the process. I have performed a similar task to a job interview and I’ve learnt how valuable is using Datetime and others built in Php classes.
Nice exercise

• http://www.bitfalls.com/ Bruno Skvorc

You’re right, it could have been optimized that way. I didn’t really put much effort into the table – it was just for the demo function, and is only generated once, the rest of the site is demo page is ajaxed.

• Mike Mx Kowalski

You actually did not create an algorithm to solve ANY CALENDAR. You created an application to solve that particular Calendar which is described in the foreword. Sorry.

• http://www.bitfalls.com/ Bruno Skvorc

Like I said in the article, I welcome alternative solutions. Please submit yours so your comment can be considered constructive criticism instead of being just a childish attempt to sow discord.

• https://www.phpcontext.com/wordpress Mike Mx

Didn’t mean to be rude although it seems I was anyway lol. I posted the solution to the Gist, you can access it at https://gist.github.com/mikemix/8342861

• David Neilsen

I love this inconsistent Hungarian notation used…

• http://www.synet.sk/ lubosdz

So did you get the job? :-)))

• http://www.bitfalls.com/ Bruno Skvorc

I didn’t actively participate in the interview, I just solved one particular question from it here.

• http://github.com/mihaeu Michael Haeuslmann

Thanks for the article. How long did the whole exercise take you?

I solved the problem on paper first (15min) and then transfered the solution to the screen (5min) using comment driven development and refactored (2-3min) afterwards. That is probably not fast, but I’m on holiday :)

The solution I came up with is similar to your second attempt, because it appeared to me less error prone.

1. days reset every month, months reset every year, but the week days do not reset, if one year ends on Tuesday, the next starts on Wednesday

2. thus if we know the number of days since 1900-01-01 we can divide that number by 7 (number of days in a week) and the rest will indicate the day of the week (= modulo e.g. \$daysSinceOffset % 7)

3. days in full year (not counting the current year, because it is not finished) * days in a normal year – leap years (every fifth year one day so \$yearsSinceOffset / 5)

4. days in full months

5 and finally add the days from the current months

That way the steps are small and thus easier to debug under stress and in a short time. This attempt will work for calendars similar to the one that was given, but if the leap years are less regular (e.g. in the Gregorian calendar leap years have a day more in February, not the end of the year) it won’t.

Here’s my solution https://gist.github.com/mihaeu/b71c6f80cc9fb9a90b24

• Walter

That ugly Datetime class..no one wants to meet her ;)

• http://www.bitfalls.com/ Bruno Skvorc

Half an hour or less, I can’t say precisely. Thanks for submitting your approach!

• http://www.bitfalls.com/ Bruno Skvorc

That’s a nice approach, well done, but in a live interview might be a bit of an overkill for most, considering the time allotted (30 mins). Still, very well done

• Walter

Personally I don’t agree with interviews in which tasks has to be performed in a fixed amount time, 30 minutes then, worse. Although might be good strategy to test how fast a developer is in writing code, the downside would be to end with code that (maybe) works, but is crap code.
Better give the task within a wider time frame, I mean: days. This way, a developer would show up his/her skills in OOD, use of DPs, understanding and appliance of SOLID principles and so forth. The code would talk from itself. The code talks, ever.

• http://www.bitfalls.com/ Bruno Skvorc

I understand where you’re coming from, and to a point I agree, but I disagree with interviews that take days. I have better and more lucrative things to do than code for someone for free. A day is what I would sacrifice at most, but no more.

• John Nickell

I think this works. Might not be the most efficient way.

``` class DayCalc { protected static \$weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];```

``` public static function getWeekDay(\$year, \$month, \$day) { \$total = static::getTotalDays(\$year, \$month, \$day); \$i = \$total % 7; return static::\$weekdays[\$i]; } protected static function getTotalDays(\$year, \$month, \$day) { \$years = \$year - 1900; \$total = 0; // years for (\$i = 0; \$i 1) { for (\$m = 1; \$m < \$month; \$m++) { \$total += static::daysInMonth(\$year, \$m); } } // days \$total += \$day; return \$total; } protected static function daysInMonth(\$year, \$month) { if ((\$month % 2 === 0) || (\$month === 13 && static::isLeapYear(\$year))) { return 21; } return 22; } protected static function daysInYear(\$year) { \$normal = (22 * 7) + (21 * 6); if (static::isLeapYear(\$year)) { return \$normal - 1; } return \$normal; } ```

``` protected static function isLeapYear(\$year) { return \$year % 5 === 0; } } ```

• http://www.bitfalls.com/ Bruno Skvorc

Nice, cheers!

• Jonathan Ramsey

Very interesting challenge, Bruno.

The year before a leap year has 280 days, just as all other non-leap years, so, the days of the week –including 01.01- should move in the year *following* a leap year:

1900: 01/01 Monday
1901: 01/01 Sunday

1905: 01/01 Sunday
1906: 01/01 Saturday

1910: 01/01 Saturday
1911: 01/01 Friday

• Brian

Maybe I missed the point of the question..I did it with: echo date(‘l’,strtotime(‘7/1/1901’));

• Rod McLaughlin

The correct approach is to start with a test, using PHPUnit or similar. assert( dayOfWeek(day: 17, month: 11, year: 2013) === “Saturday”), watch it fail, and go from there.

• Nicolae Stelian Astefanoaie

Ending Soon