Introduction to Programming (CPSC 124)
—Hobart & William Smith Colleges, Spring 2015
Class Notes—February 12, 2015
Home | Syllabus | Calendar | Class Notes | Labs and Projects | General Notes |

Topics

Date Validation Problem

Lab #3 consisted of a single project:

Write a program, validDate, which takes as command-line arguments a year, month, and day (in that order). The program should print true if the specified date is a valid one according to the Gregorian calendar, and false otherwise.

Least Interesting Part

You all did nice work teasing out the logic of this surprisingly subtle problem, and most of you wrote excellent expositions of the development stages, realized as increasingly-refined, working programs.

In light of this week's introduction of conditional and loop control-flow constructs, here's an alternative version, developed with branch statements:

public class validDate { public static void main(String[] args) { int y = Integer.parseInt(args[0]); int m = Integer.parseInt(args[1]); int d = Integer.parseInt(args[2]); if (y < 1582) { System.out.println(false); } else { boolean goodMonth = (1 <= m) && (m <=12); int max = 31; boolean leapYear = (y%4 == 0) && ((y%100 != 0) || (y%400 == 0)); if (m == 4 || m == 6 || m == 9 || m ==11) { max = 30; } else if (m == 2) { if (leapYear) { max = 29; } else { max = 28; } } else { max = 31; } boolean goodDay = (1 <= d) && (d <= max); boolean isValid = goodMonth && goodDay; System.out.println(isValid); } } }

More Interesting: How to solve this, a sermon

The key to understanding the development of the program above is that, despite having typed it in real time at the end of class, this was neither memorized nor discovered all at once. Experience helps in developing correct program logic, but the point of today's exercise—indeed the point of this entire class—is that this ability is learned, not ingrained.

Most interesting: How to solve this, details

Boilerplate first!

The very first thing to do is to get the boilerplate and input/output structure down. We know we're going to need to read three integers, and we'll need to print a boolean value at the end, so our initial, highly-simplified "solution" is:

public class validDate { public static void main(String[] args) { int y = Integer.parseInt(args[0]); int m = Integer.parseInt(args[1]); int d = Integer.parseInt(args[2]); boolean isValid = true; System.out.println(isValid); // Obviously wrong, but it's enough the get the structure down } } }

Obviously, this prints the wrong answer most of the time, but that's not important just yet. This step is important because once these details are out of the way, you have that much less distraction from the real problem at the heart of the program. In fact, we've just transformed the challenge of our problem to one which, given values for y, m, and d, sets the value of isValid correctly.

Sketching the logic

At this point, we should probably step back from the program, get a sheet of scrap paper, and write down what we understand about the problem, without worrying about expressing the ideas in code. The incomplete list we had on the blackboard was this:

Implementing the logic, bottom up.

There is more than one technique for developing correct program logic. The one we used today worked in a "bottom upwards" fashion, starting with the end goal and adding in calculations as we needed them. At all times, the guiding idea was this: keep everything as simple as possible, and build on that. We began by knocking off the easiest case of early year values:

public class validDate { public static void main(String[] args) { int y = Integer.parseInt(args[0]); int m = Integer.parseInt(args[1]); int d = Integer.parseInt(args[2]); if (y < 1582) { System.out.println(false); } else { boolean isValid = true; System.out.println(isValid); } } }

This leaves us free to focus in the else clause on the problems of valid m and d values only (because inside that block, we know that y must be at least 1582). In other words, we have a valid date if we have a valid day and a valid month!

public class validDate { public static void main(String[] args) { int y = Integer.parseInt(args[0]); int m = Integer.parseInt(args[1]); int d = Integer.parseInt(args[2]); if (y < 1582) { System.out.println(false); } else { boolean goodMonth = true; boolean goodDay = true; boolean isValid = = goodMonth && goodDay; System.out.println(isValid); } } }

Again, notice how we just put in prototype calculations for goodMonth and goodDay, just enough to get our program to build. I can't stress this technique enough: by making sure you have syntactically & semantically correct code (i.e. no compiler errors) at every stage of your implementation, you are more free to concentrate on the hard parts, since the other concerns have been taken care of already.

The one thing here that does take some experience before it's easy is the question of what types to choose for variables. I knew that goodMonth and goodDay had to be boolean values because I'm using them in a formula to calculate isValid, which is boolean. The principle to keep in mind here is this: think about what the value type is for each variable you declare and each expression you write. Getting comfortable with this will also prevent a number of semantic mistakes and program bugs, such as trying to perform arithmetic on boolean values or using integer division when you should be using floating point (or vice-versa).

More solution growth: finishing

We've now got everything down except for the problems of validating m and d. Since we've declared boolean variables to record these two things, another way of viewing our remaining problems is the development of appropriate boolean formulae. The validation of m is the easiest, so let's do that one:

public class validDate { public static void main(String[] args) { int y = Integer.parseInt(args[0]); int m = Integer.parseInt(args[1]); int d = Integer.parseInt(args[2]); if (y < 1582) { System.out.println(false); } else { boolean goodMonth = (1 <= m) && (m < 12); boolean goodDay = true; boolean isValid = = goodMonth && goodDay; System.out.println(isValid); } } }

Finally, there's the "goodDay problem". The low part of this one is also easy (1 <= d); it's the maximum day that's tricky. We'll simplify that one by introducing another variable, max, which we can use to complete the goodDay value:

int max = 31; boolean goodDay = (1 <= d) && (d <= max);

Our last problem is getting max right. For this one, it'll help to go back to our informal notes about the three cases. Only February is tricky, and we can delay that concern briefly, as well. Here's an almost-finished version, working bottom-up to the new boolean variable leapYear:

public class validDate { public static void main(String[] args) { int y = Integer.parseInt(args[0]); int m = Integer.parseInt(args[1]); int d = Integer.parseInt(args[2]); if (y < 1582) { System.out.println(false); } else { boolean goodMonth = (1 <= m) && (m <=12); int max = 31; boolean leapYear = false; // prototype value: need to fix if (m == 4 || m == 6 || m == 9 || m ==11) { max = 30; } else if (m == 2) { if (leapYear) { max = 29; } else { max = 28; } } else { max = 31; // This isn't necessary, but it enhances clarity. } boolean goodDay = (1 <= d) && (d <= max); boolean isValid = goodMonth && goodDay; System.out.println(isValid); } } }

So our very, very last problem is an expression that will set the value of leapYear to true if and only if y corresponds to a leap year. And that one we've already done in another problem!


John H.E. Lasseter