Print the Month and Day
This function, when passed a day within a year (January 1 is 1, February 1 is 32, and so on) prints the month name and day within the month.
This function takes an extra parameter to specify if the year is a leap year (where February has 29 days).
The function should raise the ValueError exception if the day number is invalid.
Remember that a class with an empty definition functions like a struct in C, allowing data to be accessed by name.
This excerpt from the Python Reference Manual helps explain one potentially confusing expression in the code:
The expression x and y first evaluates x; if x is false, its value is returned; otherwise, y is evaluated and the resulting value is returned.
The expression x or y first evaluates x; if x is true, its value is returned; otherwise, y is evaluated and the resulting value is returned.
Source Code
1. # Month will have two members, name and days.
2.
3. class Month:
4. pass
5.
6. def showday( daynumber, isleapyear ):
7. """ Shows the month and day for a given day number.
8.
9. daynumber: a day number within the year.
10. isleapyear: True if the year is a leap year
11.
12. Prints the month and day, raises ValueError if
13. daynumber is invalid.
14. """
15.
16. months = [ "January", "February", "March",
17. "April", "May", "June",
18. "July", "August", "September",
19. "October", "November", "December" ]
20.
21. days = [ 31 for x in months ]
22.
23. # Let's see, 30 days hath September...
24.
25. thirtylist = ( "April", "June",
26. "September", "November" )
27.
28. for j in [ months.index(k) for k in thirtylist ]:
29. days[j] = 30
30.
31. # Fix up February also
32.
33. days[months.index("February")] = \
34. 28 + ((isleapyear and 1) or 0)
35.
36. """ daymap consists of 12 Month objects, each of which
37. has a name/days pair in it.
38. """
39.
40. daymap = [ ]
41.
42. for i in range(len(months)):
43. newMonth = Month()
44. newMonth.name = months[i]
45. newMonth.days = days[i]
46. daymap.append(newMonth)
47.
48. if daynumber > 0:
49. for el in daymap:
50. if daynumber < el.days:
51. print el.name, daynumber
52. return
53. daynumber = daynumber - el.days
54.
55. raise ValueError, "daynumber"
Suggestions
Determine the goal of the code up to line 47. How many of the variables used prior to that line remain important after line 47? What are the goals of those variables? Are they properly initialized to meet this goal? Verify that the expression on lines 33 and 34 works properly. What are the inputs to this line of code? How many different values are needed to test it? The months list is the kind of declaration that the eye easily skips. Is it actually correct, meaning that the months are spelled correctly and listed in the correct order? One test that can be done on the function is to assume that daymap is initialized correctly and just test from line 48 on. Assuming that you decide to do this with days in January (the daynumber parameter is between 1 and 31), what is a good set of values to test with?
Hints
Walk through the code with the following inputs:
First day of the year: daynumber = 1, isleapyear = False First day of a month other than January: daynumber = 32, isleapyear = False February 29: daynumber = 60, isleapyear = True Last day of a leap year: daynumber = 366, isleapyear = True
Explanation of the Bug
The code on line 50
if daynumber < el.days:
checks if daynumber is small enough that the day falls within the month to which el refers. Because daynumber is 1-based, not 0-based, the code uses the incorrect comparison operator. Instead, the code should read as follows:
if daynumber <= el.days:
This A.off-by-one error manifests itself on the last day of a month. As currently written, the code reports, for example, that day 31 is "February 0" instead of "January 31". In that example, the very first time line 50 is executed el will be the element representing January, so both daynumber and el.days will be 31. The comparison on line 50 will be false, and daynumber will be reduced to 0 on line 53. During the next iteration of the for loop that starts on line 49, el will be the element representing February. At line 50 daynumber will be 0, and el.days will be 28 (or 29 in a leap year), so the program will print "February 0" on line 51 and then return. Similarly, in the case of the third hint-where day 60 (in a leap year) is February 29-the code reports it as "March 0".
|