Write a program that will calculate the consecutive date number of any day in the year.
This is a tedious calculation by hand. It could be a component of a program that, say, computes interest on periods of time less than a year.
The program is to count January 1 as day number 1, and December 31 as either 365 or 366, depending on whether it is a leap year. The input will be given as three cardinals representing the year, month, and date within the month.
Input:
1990 4 15
Output:
04 15 is day number 105 in 1990.
The SWholeIO library will be employed to read and write the cardinal data.
The program DateCalc will:
1. Read data in the form of three cardinals, representing the number of the year, of the month, and of the date in the month, respectively. 2. The consecutive date in the month will be calculated. 3. The result will be printed in the form mm dd is day number nnn in yyyy. 4. The program will exit.
1.1 print a prompt to the screen for the year data read the year 1.2 print a prompt to the screen for the month data read the month 1.3 print a prompt to the screen for the date data read the date 2.0 Set the result to zero 2.1 Add to the result the number of days to the end of the previous month If month is 12 add 334 Else if month is 11 add 304 Else if month is 10 add 273 Else if month is 9 add 243 Else if month is 8 add 212 Else if month is 7 add 181 Else if month is 6 add 151 Else if month is 5 add 120 Else if month is 4 add 90 Else if month is 3 add 59 Else if month is 2 add 31 2.2 Add the date in the month to the result 2.3 Adjust for leap years If the month is greater than 2 then If the year is divisible by 4 but not by 100 (unless also by 400) add one to the result
1. Imports required From STextIO: WriteString, WriteLn, ReadChar, SkipLine From SWholeIO :ReadCard, WriteCard 2. Variables Required: year, month, day, result : Cardinals response : Char
DateCalc is a simple utility designed to compute the consecutive number in the year of any date entered. DateCalc runs on any Macintosh computer.
To use DateCalc, either type its name, followed by the <enter> key from the MPW worksheet, or double-click on its icon from the Macintosh desktop.
You will be prompted to enter the year, the month and the date. These should all be whole numbers; that is, use 2 for February, 12 for December, etc.
At this point, the result will be printed on the screen, followed by the message:
Press a key to continue ====>
After noting the information, press a key and the program will exit.
Errors:
DateCalc will not check to see if you have entered a valid date; the responsibility is the user's to avoid such things as 1990 02 34.
Possible future enhancements:
Add the option of reading the data from a disk file
Add checking for the validity of the date.
Improve the efficiency of the computation by devising a formula.
(* Name: Daniella Christian Student Number: 052001 CMPT 141 Fall 2008 Assignment #2 Calculating date numbers *) MODULE DateCalc; FROM STextIO IMPORT WriteString, WriteLn, ReadChar, SkipLine; FROM SWholeIO IMPORT ReadCard, WriteCard; VAR day, month, year, result : CARDINAL; usingFile, again : BOOLEAN; response : CHAR; BEGIN (* write out header information *) WriteString ("Name: Daniella Christian"); WriteLn; WriteString ("Student Number: 052001"); WriteLn; WriteString ("CMPT 141 Fall 2008"); WriteLn; WriteString ("Assignment #2"); WriteLn; WriteString ("Calculating consecutive date numbers"); WriteLn; WriteString ("This program calculates the consecutive "); WriteString ("date in the year "); WriteLn; WriteString ("from user supplied information"); WriteLn; WriteLn; REPEAT (* main repeat loop *) WriteString ("Enter the year number here ====>"); ReadCard (year); SkipLine; WriteLn; WriteString ("Enter the month number (1 - 12) here ====>"); ReadCard (month); SkipLine; WriteLn; WriteString ("Enter the day number here ====>"); ReadCard (day); SkipLine; WriteLn; (* do the calculation *) result := 0; (* initialize the result *) (* add on right number of days to end of last month *) IF month = 12 THEN result := result + 334 ELSIF month = 11 THEN result := result + 304 ELSIF month = 10 THEN result := result + 273 ELSIF month = 9 THEN result := result + 243 ELSIF month = 8 THEN result := result + 212 ELSIF month = 7 THEN result := result + 181 ELSIF month = 6 THEN result := result + 151 ELSIF month = 5 THEN result := result + 120 ELSIF month = 4 THEN result := result + 90 ELSIF month = 3 THEN result := result + 59 ELSIF month = 2 THEN result := result + 31 END; (* now add the day in the month *) result := result + day; (* finally, adjust for leap years *) IF (month > 2) AND ((year MOD 400 = 0) OR ((year MOD 4 = 0) AND (year MOD 100 # 0)) ) THEN INC (result) END; (* Output the result in the required form *) WriteCard (year, 4); WriteCard (month, 3); (* ensure one space between *) WriteCard (day, 3); WriteString (" is day number "); WriteCard (result, 4); WriteString (" in that year."); WriteLn; WriteLn; WriteString ( "Do you wish to do another? Y or N ==> "); ReadChar (response); again := CAP (response) = "Y"; SkipLine; WriteLn; UNTIL NOT again; END DateCalc.
This module was run with the following result:
Name: Daniella Christian Student Number: 052001 CMPT 141 Fall 2008 Assignment #2 Calculating consecutive date numbers This program calculates the consecutive date in the year from user supplied information Enter the year number here ====>1992 Enter the month number (1 - 12) here ====>3 Enter the day number here ====>1 1992 3 1 is day number 61 in that year. Do you wish to do another? Y or N ==> y Enter the year number here ====>1993 Enter the month number (1 - 12) here ====>12 Enter the day number here ====>31 1993 12 31 is day number 365 in that year. Do you wish to do another? Y or N ==> n
There is an algorithm that allows the calculation to be done in a formula rather than by using the IF ... THEN construction. To see how this works, consider first the "rough estimate" that each month has 30 days, and consider how the 31 day months affect the total number of days to be added for the following month. We ignore February for the moment.
Mon: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec No.: 1 2 3 4 5 6 7 8 9 10 11 12 Adj: 0 1 1 2 2 3 3 4 5 5 6 6
Each time there is a 31 day month, one must adjust the total by 1. Now, up to August, the adjustment formula is: month DIV 2, but after that it is: (month +1) DIV 2. This can be expressed in a single adjustment as: (month + month DIV 9) DIV 2.
From this one must subtract 2 if the month is after February, and this quantity can be expressed in a formula as: 2*((month + 9) DIV 12). Try it!
The leap year adjustment changes the "2" in front of this expression to: 2 - (4 - year MOD 4) DIV 4) + (100 - year MOD 100) DIV 100) - (400 - year MOD 400) DIV 400) [The first term of this produces a 1 when the year is divisible by 4, the second removes it when the year is divisible by 100; and the third adds it back in when it is divisible by 400. Thus the two day adjustment for an ordinary February is altered as appropriate to a one day adjustment.]
Combining all these gives the result in a single Modula-2 expression:
result := 30 * (month -1) + day + (month + (month DIV 9)) DIV 2 - (2 - (4 - (year MOD 4)) DIV 4 + (100 - (year MOD 100)) DIV 100 - (400 - (year MOD 400)) DIV 400) * ((month + 9) DIV 12);
and this does the entire calculation, eliminating the two IF..THEN constructions in the original code.
The formula used in the second version is rather cumbersome, and may be difficult to understand, especially if the code is examined without the analysis leading up to it (and perhaps even then). Perhaps a compromise ought to be struck between the easy low-technology method with its long IF .. THEN statement, and the high-technology method with its complex formula. Consider a further analysis of the problem. Starting in March, and going through to the following February, the total number of days and the average number of days from March first to the end of the previous month is given by:
Mon: Mar Apr May Jun Jul Aug Sep Oct Nov Dec Jan Feb No.: 0 31 61 92 122 153 184 214 245 275 306 337 Av: 0 31 30.5 30.7 30.5 30.6 30.7 30.6 30.6 30.6 30.6 30.6
This chart suggests numbering the months starting in March and multiplying this number by 30.6 to get the number of days elapsed to the first of the current month. Using a method similar to that above, the month number is adjusted by adding 12 if it starts out at 1 or 2. This initial adjustment changes the range of months from 1 .. 12 to 3 .. 14.
month := month + 12 * ( 12 - month) DIV 10
The expression
(month - 3)
is needed in the final formula to change this range to 0 .. 12, counting from March to February.
The chart below of the revised month numbers gives the result of doing this, first as a raw answer, (*Av) then rounded off to the nearest day (#). The latter are exactly what are desired.
Mon: Mar Apr May Jun Jul Aug Sep Oct Nov Dec Jan Feb MNo.: 0 1 2 3 4 5 6 7 8 9 10 11 *Av: 0 30.6 61.2 91.8 122.4 153 183.6 214.2 244.8 275.4 306 336.6 # 0 31 61 92 122 153 184 214 245 275 306 337
Rounding a number off to the nearest whole number can be expressed as:
cardNum := TRUNC (realNum + 0.5)
This gives, for the number of days from last March 1:
daysSinceMarch := TRUNC (30.6 * (month -3) + 0.5); (* with revised month *)
Adding 59 to the result for the first two months, and then removing the excess days (January and February have, in effect been moved to the following year), produces:
result = (59 + daysSinceMarch + day)
followed by a reduction if greater than 365 (because January and February are not really in the next year, it just made it an easier formula to consider them that way for the moment) and an increase if a leap year.
The code can be revised to have three more variables, adjMonth, daysSinceMarch, and leap and then modularized into a series of steps, instead of rolling everything into a single formula. That is, the algorithm can be expressed on several lines instead of one:
leap:= (year MOD 400 = 0) OR ((year MOD 4 = 0) AND (year MOD 100 # 0)); adjMonth := month + 12 * (( 12 - month) DIV 10); daysSinceMarch := TRUNC (30.6 * FLOAT (adjMonth - 3) + 0.5); result := (59 + daysSinceMarch + day); IF result > 365 THEN DEC (result, 365) END; IF leap AND (month > 2) THEN INC (result); END;
NOTE: DEC is a standard identifier similar to INC.
There are other ways of doing this as well. It would be nice, for instance, to have a procedure for rounding real numbers off to the nearest integer or cardinal. No such procedure is built in to Modula-2, though there are some in the RealMath and LongMath libraries, and in the next chapter, directions will be given for writing one.