simple Perpetual Calendar

nyerky
nyerky used Ask the Experts™
on
Hello,

Im new to C and I wanted to create a very simple perpetual calendar that would look like this:

January  2003

sun   mon  tue   wed   thur   fri   sat
                             1       2     3     4      
5        6       7        8       9     10   11
12     13    14      15     16     17   18    
19     20    21      22     23     24   25    
26     27    28      29     30     31

February  2003

sun   mon  tue   wed   thur   fri   sat
                                      1       2     3    
  4       5       6       7       8       9     10  
11     12     13    14      15     16     17  
18     19     20    21      22     23     24  
25     26     27    28      

January  2003

sun   mon  tue   wed   thur   fri   sat
                                                      1      
  2       3      4        5       6       7     8      
  9     10    11      12     13    14    15    
16     17    18      19     20    21    22    
23     24   25       26     27    28    29    
30     31


[P]previous     [N]next      [Skip]

Previous would show previous 3 months, Next, for the next 3 months, and for
the Skip, you can specify the month, day, and year and it will show you the
month BEFORE that and the month AFTER that.

 Linux C code or Visual C code

Thanks in advance for your generous help and support. :)

Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Top Expert 2006

Commented:
sounds like home work

>>Im new to C and I wanted to create a very simple perpetual calendar that would look like this:
Let us know what were the efforts you made for this

We can help you solve the problem, but we cant solve it for you

Author

Commented:

I'm sorry but I am totally new to C. I have just started reading C manuals.

I would be very gald and thankful if anyone would be kind enough to show me a simple code that would produce this output.

Full credits will be given to the author. I would like to use the code for this as my basis for learning C with those mathematical
calculations.

Thanks.
Top Expert 2006

Commented:
I am still not fully convinced ...
You are new to C and want to learn it ... If you want to learn on your own and this is not a home assignment, then you should have tried to do it on your own ...
If this is your home work, then you should be doing it on your own ... This site's membership agreement prevents us from doing others homework ... we can help but we cannot do it for you

If you are new to C, then start learning by doing
>>I would like to use the code for this as my basis for learning C with those mathematical calculations.
Mathematical calculations in C are no different from what you normally see ... it is only a format for putting things

multiply a * b
divide    a / b
add       a + b
subtract a - b

pretty much the same ;-)
Become a CompTIA Certified Healthcare IT Tech

This course will help prep you to earn the CompTIA Healthcare IT Technician certification showing that you have the knowledge and skills needed to succeed in installing, managing, and troubleshooting IT systems in medical and clinical settings.

Hi nyerky,

unix and linux systems come with the program "cal", which is a perpetual calendar.  If you'll peruse the web for the source code to ANY linux distribution you should be able to find it with no trouble.  If you're not a student, this should be adequate to get you started in that you'll acquire the source code to the calendar and you'll be free to make it take on whatever form you desire.  And if you are a student the same logic applies.  :)


Hi sunnycoder,

I still like your style.  :)

Kent

Commented:
looks very much like homework to me.

I can nudge you in the general direction though.

It is actually very simple. First some info about the gregorian calendar.

The gregorian calendar have a cycle in 400 years. This means that the calendar for 2003 is EXACTLY identical to the calendar of 1603 including the weekdays for each date.

Within the 400 year period the calendar may appear to be somewhat irregular but there's a pattern there for those who look ;)

In general once you know the weekday for the 1st of the first month you want to display and you know how many days are in each of the months you want to display you can actually start generate the calendar immediately. Perhaps even add in week numbers if you want.

Also, note that different locale start the week on different days. For example US start the week on sundays while here in Norway we count monday as the first day of the week.

If you want to make your calendar being able to display correctly based on locale you can do that too easily.

Make a matrix of 6 * 7 elements. The rows are one week while the columns are the weekdays starting with the first day of the week (for example sunday or monday) and ending with the last day of the week (saturday or sunday).

Now you want to distribute 28-31 days within this matrix in a proper manner.

If you display each month in one set of rows and then the next month in another set of rows then you can decide to let each month have a varying number of weeks to display so that if for example february in a regular year had the 1st on the 1st day of the week then it would be exactly 4 lines with one line for each week. This is very compact way but it doesn't work well if you want to display three months side by side or if you want to print the month on a standard form. In this case you want a fixed number of weeks and you should set the form to have 6 weeks. It may sound like a lot but there are 4 weeks plus some days in a month with 31 days and if you're unlucky the 1st of the month may start on the last day of a week and then you get 1 day for the first week (actually last day of the week from previous month), 7 days for second week, third, fourth and fifth each that makes a total of 29 days and then finally the remaining days in the sixth week.


As already mentioned, february might occupy only 4 weeks (4 rows) in that format and so two rows are blank. Often one row will be blank and the question is then how should you align the days. Should you leave the first row blank or the last row blank or both?

If you want to "center" the calendar within the matrix you will use the following rule:

If two weeks are blank (only for february if feb 1st is the 1st day of the week) then set first and last row empty and fill in the calendar in row 2 through 5 (both inclusive).

Otherwise If one week is blank fill in from first row until no more days to fill in (last week will be blank).

If no weeks are blank, you fill all the rows.

If you instead want to align it on the top you start filling in at first row and then in the case of february you have two blank rows at the end.

I would say the "center" format looks best if you want to print it on paper ;)

Ok, now the algorithm is simple enough. If the first day of the month is weekday W where W is 0 for the first day of the week and 6 for the last day of the week then on the first row that you want to fill in you place 1 into M{r][W]:

Month[r][W] = 1;

Then you just move on setting M{r][W+1] = 2; etc and when you get to W = 7 you increase r and set W to 0 and continue until all the days of the month has been filled in.

Do it like this:

int r = 0; // r is row number
// d is number of days in current month.
if (d == 28 && W == 0) // we have the special case with two blank weeks.
    r = 1; // start at week 1.

memset(Month,0,sizeof(Month)); // clear the month matrix to start with.
for (int dnum = 1; dnum <= d; ++dnum) {
    Month[r][W++] = dnum++;
    if (W == 7) {
         W = 0;
          ++r;
    }
}

Once you have this matrix filled out it will have the dnum == Month[row][weekday] if the dnum'th day in the current month is on weekday weekday and is to be placed in row "row" of the month.

Next you are ready to display the calendar for this month just walk through the matrix and if the value there is 0 it means you should print some blanks there and if the value is non-zero you print out the value (remember to use 2 places for the value so if the value is less than 10 you should print a space first).

Be aware that W isn't exactly the same value as you get from time_t struct or whatever other data you use to compute the weekday number. Those functions always have 0 == sunday and 6 == saturday. If you're in US this is fine but if you're somewhere else that is not fine, so here is how you do it:

step 1. Get the weekday number w from the function. Here 0 == sunday and 6 == saturday.

step 2. You want to change this value to find W where 0 == first day of week and 6 == last day of week.

If you're in Norway or Germany or some such then you want W == 0 for monday, i..e. when w == 1.

So: W = w - "w for 1st day of week".

If w == the first day of the week this result in 0 which is what we want.

There's one more snag, the subtraction above moves the value outside the range 0-6 and you want to move it back in so if W < 0 you add 7.

if ((W = w - "w for 1st day of week") < 0)
   W += 7;

is the complete formula. Here "w for 1st day of week" can be an integer or a constant value which is 0 for US and 1 for countries where monday is first day of week and is 6 if saturday is the first day of the week.

So, just get the 1st weekday of the month and then move on from there. You can use mktime() to compute the weekday for a given date. mktime() is defined in the C library.

An additional note about weeknumbers. If you want to add in weeknumbers all you have to do is to keep track of the week number for the first week of the month and then display that along with each week. When you move to next row (increase r and set W to 0 above) you also incerase the week number.

One problem with weeknumbers is that they too vary from locale to locale. In US week number 1 is the week that start with the 1st sunday of the year. Any days in january before the first sunday is considered week number 0. This is kinda fake since it really is the same as the last week of december but as far as I know, a week that starts in december and end in early january will suddenly change week number when you change the year in the US.

In Norway (to make an example) we don't do that. Instead if the week that start in december and end in january is week number 52 then it stays week number 52 also in january. Also, this week may even be week number 1 so that week number 1 of year X may actually start in december in the previous year!

The rule is this:

The first week which is such that the majority of days are in the year, this week is given the number 1. This rule translates to the rule that the first thursday of januray is always in week number 1. Thus if january 1st is a monday, tuesday, wednesday or thursday then that week is number 1. If january 1st is on a friday, saturday or sunday then it is week number 52 or 53 from the previous year and week number 1 start on the first monday of january.

How can you configure this in such a way that you can provide simple parameters and compute the correct week number?

It is actually easier than you think. First, we give a name to the weekday number of the first day of a week. Let's call it W0. This value is 0 in the us and it is 1 in Norway. It is the "w of the first day of the week" given above.

Next we need to identify the weekday which is such that the first such weekday is week number 1. We can call this value W1. In the US this value is also 0 since the first sunday is always week number 1 while in norway it would be 4 since it is the first thursday which identifies week number 1.

Now, given a month, day and year how can you find the week number?

First you need to compute the day number within the year (sometimes called yearday): Let's call it U. January 1st is 0, january 2nd is 1 etc january 31st is 30 and february 1st is 31 etc...

Now, given W0 and W1 above you are almost ready to find the week number. We need one more piece of information. When is first day of week number 1 within the year?

Well, let's see... Let's look at U == 0  (January 1st) and get the weekday of it. Let's say this is Wjan1.

Now, clearly if we allow ourselves some freedome and let negative values of U correspond to the last few days of the previous year and similarly high values of U (after december 31st) correspond to the first few days of the following year we then have:

U - W gives a sunday, so clearly -Wjan1 gives the yearday for a sunday at or before january 1st and
W1-Wjan1 gives a W1th day of some week.

If this value is positive then it is the first such day of the year and that W1 day belongs to week number 1. If the value is negative then it is in the previous year and we have to add 7 days to get week number one and.

if W1 >= Wjan1 then the value is positive and it correspond to some date D2 == W1 + 1 - Wjan1 which is the first W1 day of january and it is week number 1. If this value is U2 then that weekday is W1 and so U2-W1 is a sunday and U2-W1+W0 is the first day of week number 1, so:

U2-W1+W0 == W1-Wjan1-W1+W0 == W0-Wjan1

so if W1 >= Wjan1 then W0-Wjan1 gives the yearday for the first day of week number 1.
Clearly then the first day of week number N is given by:   U == W0-Wjan1+(N-1)*7.

And we get: U == W0-Wjan1 + 7N - 7 and we get (U+Wjan1-W0+7)/7

If W1 < Wjan1 then the value is negative and week number one start one week later, so we get:
U == W0-Wjan1+7N and we get (U+Wjan1-W0)/7

Let's see how this works for some cases.

Let's say that W1 == 4 (thursday) and W0 == 1 (monday). This is norwegian rules (almost).

Also, let's say that january 1st is on a monday so Wjan1 == 1.

In this case we get: W1 > Wjan1 and so the rule is (U+Wjan1-W0+7)/7 == (U+1-1+7)/7 = (U+7)/7

U==0 is january 1st (monday) in week number (0+7)/7 == 1
U==1 is january 2nd (tuesday) in week number (1+7)/7 == 1
U==6 is january 7th (sunday) in week number (6+7)/7 == 1
U==7 is january 8th (monday again) in week number (7+7)/7 == 2

ok, what if Wjan1 was a tuesday (2) then we get: U+Wjan1-W0+7)/7 = (U+2-1+7)/7 = (U+8)/7
U==0 is january 1st (tuesday) in week number (0+8)/7 == 1. This week started on monday december 31st but this is the week that contain the first thursday.
U==1 is january 2nd (wednesday) in week number (1+8)/7 == 1.
U==5 is january 6th  (sunday) in week number (5+8)/7 == 1
U== 6 is january 7th (monday again) in week number (6+8)/7 == 2.

We see that if Wjan1 is any weekday from W0 (monday) to W1 (thursday) (both inclusive) this works out. Let's verify with the last day of this range: Wjan1 == thursday (4 same as W1).
Then the week number is (U + Wjan1-W0+7)/7 == (U+4-1+7)/7 == (U+10)/7
U==0 is january 1st (thursday) in week number (0+10)/7 == 1. This week start on monday december 29th but since the first thursday is january 1st this is week number 1.
U==1 is january 2nd (friday) in week number (1+10)/7 == 1.
U==3 is january 4th (sunday) in week number (3+10)/7 == 1.
U==4 is january 5th (monday) in week number (4+10)/7 == 2.

If Wjan1 is greater than W1, or smaller than W0 in this case we use the other formula:
If Wjan1 is a friday (5) then we get week number N == (U+Wjan1-W0)/7 == (U+5-1)/7 == (U+4)/7
U == 0 january 1st (friday) in week number (0+4)/7 == 0. This is actually last week of previous year.
U == 2 january 3rd (sunday) in week number (2+4)/7 == 0.
U == 3 january 4th (monday) in week number (3+4)/7 == 1.
etc...
If Wjan1 is saturday (6) we get week number N == (U+Wjan1-W0)/7 == (U+6-1)/7 == (U+5)/7
U == 0 january 1st (saturday) in week number (0+5)/7 == 0.
U == 1 january 2nd (sunday) in week number (1+5)/7 == 0.
U == 2 january 3rd (monday) in week number (2+5)/7 == 1.

Finally, the last possibility is Wjan1 is sunday (0) we get the formula:
week number N == (U+Wjan1-W0+7)/7 == (U+6)/7
U == 0 january 1st (sunday) in week number 6/7 == 0.
U == 1 january 2nd (monday) in week number (1+6)/7 == 1.

We see that in this latter case we also had to add the 7. The general rule is fairly simple:

step 1. Compute V = Wjan1-W0.

step 2. If (W1 < Wjan1) add 7 to V.

step 3. If the sum is still negative add 7 to V and if V >= 14 subtract 7 from it.

The week number is then given by N = (U + V) / 7. Where U is the yearday number.

If the week number N becomes 0 we need to do special adjustment, similarly if the week value N becomes too large we also need to adjust.

For US rules we do no adjustment. the value N is the week number.

For europe we count 0 as the last week of previous year and for a high value of N (53 or 54) we do the following:

When computing U + V if the value becomes too high it actually indicates that the week is first week of following year. To find out which you can simply subtract the number of days in the current year and use the resulting value to find the week number of following year. If that computation gives a week number of 1 it is week number 1 if it gives 0 then the week really is in current year and we keep it even if it is high.

If the value is 0 it is last week of previous year, add the number of days in previous year to the value and then use that to compute the week number for previous year. The value is never too high, we keep it no matter what it is.

Or you can do a quickie since we know that W1 is thursday and W0 is monday so the rules becomes simply:

If january first is on a monday through thursday then january 1st is always in week number 1.
If january first is a friday then it belongs to previous year. It is same week number as december 31st of that year. Now if january first is a friday then january first of previous year is either a thursday or a wednesday (previous year was a leap year) and this lead to the conclusion that january first of previous year is week number 1 and so 52 weeks later you get week number 53 so january 1st this year is on week number 53. (a year is exactly 52 weeks + 1 or 2 days).
If january first is a sunday then the january first of the previous year was a saturday or friday if previous year was a leap year. In either case january first of previous year was last week of the year two years before and so january first of this year is week number 52.

The problem is if january first fall on a saturday. In this case it depends on whether previous year was a leap year or not. If it was a leap year then january 1st of previous year was a thursday and so is week number 1. Consequently january 1st of this year is then week number 53. If previous year was not a leap year then january 1st of that year was on a friday and so january 1st of this year is week number 52.

Personally I consider the more generic solution to be the best but you can do a quickie if you limit the options for this calculation to only the 1. Week start on monday, 2. Week number 1 is the first week containing a thursday.

Hope this is of help.

Alf

Commented:
Totally off topic. But you brought back memories.
My first ever major contribution to the computing world was a perpetual calendar, written in pascal.
It was 20 pages long. And I still have that print out. Back when I was in grade11 and didn't understand that concept of recursion....

..
Cassandra
1ce

Commented:
geez,  what a nudge Alf, more like a nuke, hehe
Top Expert 2006

Commented:
thanks Kent

and 1ce - :-D LOL ... what a thought
Alf is a high priest of expert-exchange HAIL O PRIEST!

Commented:
hi nyerky,

 i guess alf's input is more than enough to get you going...

as far as learning c is concerned... if you had attended all your classes then its pretty easy to start off coding...

dont wait for someone to help you.. do it your self...  it will help you!!!!!
probably 1 year down the line you could be the Alf of experts-exchange... cryptosid will call you the high priest of EE....

get back to EE if you have any specific problems or trouble!!!

-Jinu

Author

Commented:

Thanks so much Kdo!!!  I've downloaded the tarballs already. :) A special thanks to Salte too.  

More power to you both!!!

Commented:
I can also provide some more numbers on the gregorian calendar which perhaps is of interest.

The calendar has a cycle in 400 years this is exactly 146097 days in the gregorian calendar and since that number is divisible by 7 it shows that the calendar weekdays also match up in 400 years. Also, the leap years cycle in 400 years making the complete cycle 400 years and not 2800 years as it would have been if the number of days had not been divisible by 7.

A period of 100 years is either 36525 days or 36524 days. It depends on whether the 100 year period contain a century leap year or not.

For example any 100 year period which includes februrary 29th 2000 will be 36525 days while any 100 year period which includes february 28th 1900 is 36524 days ;)

These numbers can be dividied by 7 and give  a remainder of 6 or 5 respectively since 36519 is divisible by 7. This means that in a 100 year period the weekday moves backward by one or two days so since january 1st 2000 is a saturday we have that january 1st 1900 is a  monday and january 1st 1800 is a wednesday and january 1st 1700 is a friday and january 1st 1600 is a saturday again. So when you move 100 years forward you move 2 days or 1 day back on the weekday.

The other interesting period is a 4 year period which is 1461 days if the 4 year period includes a leap year (this is the usual case) and is 1460 days if the 4 year period does not contain a leap year (the rare case).

When you divide this number by 7 you get a remainder of 5 or 4 indicating that when you move 4 years forward you move 2 days back on the weekday.  An interesting thing is then the fact that a 12 year period containing 3 leap year days is exactly 1461*3 == 4383 days and when you divide this by 7 you get a remainder of 1 (4382 is divisible by 7) and so when you move 12 years forward you move 1 day forward on the calendar. Thus since january 1st 2000 is a saturday you immediatley know that january 1st 2012 is a sunday. Note that this works because the year 2000 is a leap year, you can't use the same reasoning between january 1st 1900 and january 1st 1912 since the year 1900 was not a leap year. Thus since january 1st 1900 is a monday you have that january 1st 1912 is also a monday since that particular 12 year period is 4382 days.

Lastly, the interesting value is 1 year which is either 365 or 366 days and when you divide that by 7 you get a remainder of 1 or 2 so in regular years you move one day forward on the weekday and in leap years you move 2 days forward.

In fact the reasoning outlined above is then good enough to let you find a weekday for a particular day in any year if you know the weekday for that same day in any other year. To make the math simple it is best to make that other year a year that is divisible by 400 such as 1600 or 2000. Since the calendar has a cycle in 400 years it doesn't matter which such year we pick so let's say we use 1600. Also, for reasons I will explain a bit later the particular day to pick is april 4th or 4/4/1600. This day was a tuesday. It is the one single day you have to memorize.

So, how can we find what is april 4th in any other year? Since we starts from a 400 year cycle and we start after the leap year day (which may or may not be present in a century year but which is present in a century year divisible by 400) the rule is simple:

For every 400 year you move forward or backward, the calendar is unchanged.

This rule let us move the calendar date into a date in the range from april 4th 1600 to april 4th 1999 (or april 4th 2000 to april 4th 2399). I use 1600 as starting point in this explanation so I make my range into 1600-1999 but it doesn't really matter.

For every 100 year we move forward, move 2 days back on the weekday.

So 4/4/1600 is tuesday, 4/4/1700 is sunday, 4/4/1800 is friday and 4/4/1900 is wednesday.

Similarly, 4/4/2000 is tuesday, 4/4/2100 is sunday, 4/4/2200 is friday and 4/4/2300 is wednesday.

The last 100 year cycle contain an extra leap year so you would move only 1 day back but you never need to do that since 2000 or 2400 is just like 1600.

Thus we have found the weekday for the century. How do we get from there to the year we want? Let's say we want to find april 4th 1937?

The easy way is to use the 12 year rule and so take the 37 years after 1900 and divide by 12 we get 3 12 year periods and 1 year. Since we move one day forward for every 12 year period and we also move 1 day forward for the year so you move 4 days forward, since 4/4/1900 is a wednesday we then have that 4/4/1937 is wednesday + 4 days = sunday.

If you find it hard to divide a number in the range 0-99 by 12 you can use other rules. Also, if the remainder contain 1 or 2 4-year periods we have to take those leap days into account. Because of this the rule is like this:

Take the value 0-99 and divide by 12 and you get a quotient q and remainder r then adjust the weekday from the century by q + r + (r/4) where the last division is integer division giving 0 if r is 0-3, 1 if r is 4-7, 2 if r is 8-11. r is never 12 or higher.

Ok, so we know how to find the weekday of april 4th in any year. What about any other day?

Well, here's the reason why april 4th was chosen. If you have any year and the weekday of april 4th is W then the weekday of june 6th, august 8th, october 10th and december 12th are all W as well. The point is that 4/4, 6/6, 8/8, 10/10 and 12/12 are all on the same weekday in any year.

So since april 4th on 1937 is a sunday you automatically know that june 6th or december 12th are also sunday. This cover all the even months 4, 6, 8, 10 and 12 starting from april.

What about the other months? Well, try to memorize this sentence:

"I work from 9 to 5 at 7-11"

Easy sentence to remember, the idea is to make the connection between 9 and 5 and between 7 and 11. Then be ready for the good news:

If april 4th fall on weekday W then 9/5, 5/9, 7/11 and 11/7 also fall on the same weekday W. I.e. may 9th, september 5th, july 11th and november 7th all fall on the same weekday as april 4th, june 6th, august 8th, october 10th and december 12th.

This actually cover april, may, june, july, august, september, october, november and december.

What about the other months? We still have january, february and march left.

Well, do the easy one first - march. Just remember that the 0th of march is on the same weekday W. Now, you may argue that there is no 0th of march but it is of course the day before march 1st and march 7th which certainly exist is also on the same weekday W.

Also, since we remember it as march 0th we also immediately have the last day of february is on the same weekday W. In regular years this is february 28th and in leap years it is february 29th which fall on W. This means that 0th of february fall on W in regular years and february 1st fall on W in leap years. This means that january 31st in regular years also fall on W and thus january 3rd fall on W while in leap years it is january 32nd or january 4th which fall on W.

To summarize: In regular years the following dates all fall on same weekday as april 4th:
jan 3rd, february 7th (just to get a real proper day, it is easier to remember it as february 0th), march 7th (same as for february - easier to remember march 0th), april 4th, may 9th, june 6th, july 11th, august 8th, september 5th, october 10th, november 7th, december 12th.

In leap years it is the same from march but january have the 4th and february have the 1st instead.

So, if you want to find out which weekday october 24th 1937 is, then we first find that april 4th 1937 is a sunday and then we know that october 10th is a sunday and so october 24th 1937 being exactly 2 weeks later is also a sunday.

There you go ;) You don't need any program to give you a perpetual calendar, you can have it in your head ;)

You can find more info about this algorithm by searching on the "doomsday algorithm" in google. The weekday which april 4th falls on in any given year is sometimes referred to as "doomsday" so a way to remember these daates is that in any regular year or leap years as indicated above those dates give dates for doomsday and so the problem is just to find out which day doomsday is for the given year and then count from there.

Alf
HAIL ALF

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial