Problem Solving and Computing (CSC-103 98S)

[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Journal] [Problems] [Computing] [SamR Docs] [Tutorial] [API]

# Computing Lab Eleven: Dates, Revisited

Assigned: Thursday, April 16, 1998

You do not need to turn in anything for this lab. However, you should build a working date converter and make sure that you understand all of the parts.

In this laboratory session, you will develop a group of classes, `Date` and `DateComputer` that one can use to compute "interesting" information about dates, such as the distance between two dates or the day of week on which a date falls.

Prerequisite skills:

• Basic Java programming
• Classes and objects
• Conditionals and arrays

Contents

## Algorithms

Often, we find it useful to compute the "distance" between two dates. For example, someone might ask "how many days is it until my birthday?" or even "how many days is it until my 21st birthday?". It seems that this is a question that may be best answered with a computer program.

Before writing a program to answer this question, we should develop an algorithm that can answer the question. Our goal in writing the program will then be to translate our algorithm to Java code. So, the question remains, how does one determine the distance between two dates?

Sometimes, it helps to think about particular examples. As we know from our text, this is a case of specializing. In an earlier homework assignment, we did a few exercises based on this type of question. Did we come up with any great ideas?

### Simplifying the Problem

Often, instead of writing a completely general algorithm, we may want to write a simpler but related one. So, instead of computing the distance between two arbitrary dates, we might want to compute the distance between two dates in the same year in which the second date follows the first. How might we do the simpler algorithm?

#### Strategy One: Counting

Here's one strategy, based on simple counting. We keep incrementing the first date until we reach the second date. If we count the number of times we've incremented the first date, we know how far apart they are.

 ```algorithm daysBetween1(firstDate, secondDate) int distance = 0; while (!firstDate.equals(secondDate)) { distance = distance + 1 firstDate.increment(); } return distance; end daysBetween1 ```

Is this a good algorithm? It seems like it should work. However, it's also likely to be relatively slow, as we step by days even when the two dates are months apart.

#### Strategy Two: Using a Fixed Date

Here's another strategy, based on position of the dates relative to a set date. We determine the distance of each date from January 1 and then subtract the two positions. This algorithm is due to Stephanie Wilcox.

 ```algorithm daysBetween2(firstDate, secondDate) return secondDate.daysSinceJanuaryxFirst1() - firstDate.daysSinceJanuaryFirst(); end daysBetween2 ```

Of course, in order for this algorithm to work correctly, we need an easy to to determine the number of days a date is from January first. We'll return to that problem a little later.

#### Strategy Three: Counting Months

Here's yet another strategy. We'll simplify the problem a little bit further and assume that the two days are in the same month. Then the distance is based only on the difference between the position within the month.

 ```algorithm daysBetween3(firstDate, secondDate) return secondDate.getDay() - firstDate.getDay(); end daysBetween3 ```

Now, how do we deal with different months? We can return to a method similar to that we used for `daysBetween1`, we step from month to month, adding the number of days in the month, until we reach the next month. We then have to handle the particulars of where we are within the month. That sounds difficult, but it's not too hard.

 ```algorithm daysBetween4(firstDate, secondDate) return daysBetweenMonths(firstDate,secondDate) + secondDate.getDay() - firstDate.getDay(); end daysBetween4 ```

You may want to think about why we do the subtraction at the end of this algorithm.

#### Reflection

Which is the best strategy? Each has its advantages and disadvantages. For the first to work, we need a way to increment dates. For the second to work, we need to be able to figure out the number of days since the start of the year. For the third to work, we need to be able to compute the number of days between two months. Each subalgorithm may be equally difficult to write. We'll stick with "number of days since start of year", although it seems that all of the subalgorithms are closely related.

#### How many days since January 1?

How do we compute the number of days since the start of the year? In some sense, it seems we've gone back to our original question (the distance between two dates). However, January 1 is a special day, so we might be able to use a special technique for answering this question. In particular, we can compute the number of days the first day of the month falls from the start of the year and then add the "position" of the current day within the month. How do we get the first part of that? Presumably, by using some precomputed information.

 ```algorithm daysSinceJanuaryFirst() int result; if (month is JANUARY) { result = day; // It's the number of days since January 1 } else if (month is FEBRUARY) { result = day + 31; // All of January plus the position } else if (month is MARCH) { result = day + 59; // All of January and February // Assuming it's not a leap year } ... return result; end daysSinceJanuaryFirst ```

Note that I didn't write a parameter for this method. That's because in the object-oriented perspective, it shouldn't need one, we're computing the nubmer of days since January 1 for the current date.

Can we improve the algorithm (e.g., to handle leap years)? Certainly. To handle leap years, we simply test whether the year is a leap year and the month is after February. If so, we simply add one to our result.

Are there other possible improvements? Yes. Rather than writing all those conditionals, we might put the offsets into an array.

 ```algorithm daysSinceJanuaryFirst() // The offset of the first of each month from the beginning of // the year. January is offset by 0, February by 31 (the number // of days in January), March by 59 (the number of days in January // and February), and so on and so forth. int[] monthOffset = { 0, 31, 59, 80, ... }; // Assume that we're using 1 for January, 2 for March, and so on and // so forth. If our arrays are 0-based, then we need to subtract one // from the month. int result = monthOffset[month-1] + day; if (isLeapYear()) { result = result + 1; } return result; end daysSinceJanuaryFirst ```

### Generalizing

Now, you may recall that we were trying to write an algorithm that determines the distance between any two dates, not just any two dates in the same year. What can we do to generalize our algorithms?

• We could certainly just use `daysBetween1` as it makes no real assumptions about the two dates being in the same year. However, it remains slow.
• We could do something similar to `daysBetween4`: compute the distance between the years and then offset by the distances within the years.
• We could generalize Stephanie's idea and represent each date as a distance from some specific date.

We'll do the second. That is, we'll determine the distance between two dates by determining the distance between the two years and then offset by the distance between the years.

 ```algorithm daysBetween(firstDate, secondDate) return daysBetweenYears(firstDate, secondDate) + secondDate.daysSinceJanuaryFirst() - firstDate.daysSinceJanuaryFirst(); end daysBetween ```

### How many days are between two years?

Of course, that means that we need to figure out how many days there are between two years. How do we do that? The easiest way seems to be to step between the two years, adding the number of years in the current year.

 ```algorithm daysBetweenYears(firstYear, secondYear) // Initially, assume that the distance is 0. int distance = 0; // The current year as we advance from firstYear to secondYear int currentYear = firstYear // Keep advancing until we reach secondYear. while (currentYear is not secondYear) { // Add the number of days in the current year if isLeapYear(currentYear) { distance = distance + 366; } else { distance = distance + 365; } } // while return distance; end daysBetweenYears ```

Are we done? We seem pretty close. However, we've ignored the second type of calculation we want to make: "On what day of the week does X fall?" How can we compute this? Pick a date whose day-of-the-week we know. Compute the distance. Determine the remainder of that distance after dividing by seven. Advance by that many days-of-the-week.

## Turning Our Algorithms Into Java

We are now ready to start thinking about how to turn our algorithms in Java. As you know, good Java design requires us to think more precisely about the objects and classes we will need and how they will interrelate. For these problems, I would suggest that we need at least two classes:

• `Date`: dates.
• `DateComputer`: the interface to our algorithms (where the `main` routine goes).

### Our `Date` class

Do we need others? For example, which objects will provide the `daysBetween` method? I'd suggest that most of these methods fit naturally in the `Date` class. Here's the structure we'll use for that class

 ```public class Date { // +-------------------+--------------------------------------- // | Fields/Attributes | // +-------------------+ ... // +--------------+-------------------------------------------- // | Constructors | // +--------------+ ... // +---------+------------------------------------------------- // | Methods | // +---------+ ... } // Date ```

What fields and attributes do we need? For a date, it may be that the year, month, and day within the month are sufficient. It is probably easiest to represent all three as integers.

 ``` /** * The year. Use the "full" format (e.g., 1998 rather than 98). */ int year; /** * The month. 1 for January, 2 for February, ... */ int month; /** * The day within the month. */ int day; ```

What constructors will we need? Certainly one that takes in all three parameters.

 ``` /** * Build a new date given a year, month, and day. */ public Date(int y, int m, int d) { year = y; month = m; date = d; } // Date(int, int, int) ```

What methods do we need and what form should they take? It may be a harder question than you think. Why? Because in the object-oriented paradigm, we really want to think of telling objects to do something. For example, "Hey, January 1, 1998, how many days are there between you and June 17, 2000?"

 ``` /** * Compute the number of days until another date. */ public int daysUntil(Date other) { ... } // daysUntil(Date) ```

If we follow our strategy above, we'll also need `daysSinceJanuaryFirst`, `daysBetweenYears`, and `isLeapYear`. `daysSinceJanuaryFirst` should certainly be a function associated with each date, but the other two seem like they're somewhat independent of the date. It might be reasonable to write a helper class to do those computations but, for now, we'll simply make them part of our `Date` class.

The `daysSinceJanuaryFirst` code is already mostly written. You just need to fill in a few more values.

 ``` /** * Compute the number of days since January 1. */ public int daysSinceJanuaryFirst() { // The offset of the first of each month from the beginning of // the year. January is offset by 0, February by 31 (the number // of days in January), March by 59 (the number of days in January // and February), and so on and so forth. int[] monthOffset = { 0, 31, 59, 80, ... }; // Assume that we're using 1 for January, 2 for March, and so on and // so forth. If our arrays are 0-based, then we need to subtract one // from the month. int result = monthOffset[month-1] + day; if (isLeapYear(year)) { result = result + 1; } return result; } // daysSinceJanuaryFirst() ```

How do we figure out whether a year is a leap year? We know the rule: "a year is a leap year if it's divisible by 4 and not divisible by 400 or if it's divisible by 400". In order to implement this in Java, we need to know how to do:

• and: by using `&&`
• or: by using `||`
• m is divisible by n: by using `m % n == 0` ("when the remainder from dividing m by n is 0").

 ``` /** * Determine if a year is a leap year according to our current * rules for determining leap years. */ public boolean isLeapYear(int yr) { if (...) { return true; } else { return false; } } // isLeapYear(int) ```

Finally, we can mostly reuse our `daysBetweenYears` instructions from above.

### Testing and Using the `Date` class

As we work out each algorithm, we will want to test it. Our `DateComputer` class will serve as our testing mechanism. The following is a very rough sketch of one that tests all of our methods. We will then fill in parts one-at-a-time.

 ```import rebelsky.io.SimpleInput; import rebelsky.io.SimpleOutput; public class DateComputer { public static void main(String[] args) { SimpleInput in = new SimpleInput(); SimpleOutput out = new SimpleOutput(); Date whenever = new Date(1998,4,15); ... } // main() } // DateComputer ```

What is the simplest thing we can test? Presumably, our `isLeapYear` method. How should we test it? Perhaps by determining whether each year between 1890 and 2010 is a leap year. We'll print out all the leap years (rather than the status of the leap years).

 ``` // Print all the leap years between 1890 and 2010 out.print("Some leap years: ") for (int yr = 1890; yr <= 2010; yr=yr+1) { if (whenever.isLeapYear(yr)) { out.print(yr + " "); } // if } //for out.println(); ```

What is the next simplest thing to test? Perhaps the number of days between years. Again, we probably want to try between a variety of years.

 ``` // Print out the distances between all pairs of years from // 1899 to 1904 (a greater range may be better, but would create // much more output to check). for (int yr1=1899; yr1 <= 1904; yr1=yr1+1) { for (int yr2=yr1; yr2 <= 1904; yr2=yr2+1) { out.println("From " + yr1 + " to " + yr2 + ": " + daysBetweenyears(yr1,yr2)); } // for(yr2) } // for(yr1) ```

You may want to consider why yr2 starts at yr1.

Next, we can test the number of days since January 1.

 ``` // Read in a year, month, and date int y; int m; int d; out.print("Enter a year: "); y = in.readInt(); out.print("Enter a month (numerical): "); m = in.readInt(); out.print("Enter the day of the month: "); d = in.readInt(); Date onedate = new Date(y, m, d); out.println("That day falls " + onedate.daysSinceJanuaryFirst() + " days after January First"); ```

Finally, we can compute the number of days between two dates. The precise code for doing that is left up to the reader.

## Tuning Our Algorithms

Can we further improve these algorithms? Certainly. Right now, some of our algorithms assume that the second date follows the first. How do we fix that? By comparing the two dates and if the first follows the second, we reverese them and negate the distance.

Are there other improvements? Yes. There was a huge change in calendars in the 1600's (or around then). We might want to accomodate that change. Then again, we might not really care.

[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Journal] [Problems] [Computing] [SamR Docs] [Tutorial] [API]

Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.