Lab 3 is due Thursday, October 12 at 11:59pm.

This week, you will practice working with conditional expressions, structures and program design by implementing some common useful operations on calendar dates.

We present a formula for computing the day of the week (below). The formula only works for the 20th and 21st centuries (that is, years, between 1900 and 2099 inclusive), so you will restrict dates to that range.

First, copy the following data definitions from this page.

(define-struct Date
  ([m : Integer] ;; 1 for Jan, 2 for Feb, ..., 12 for Dec
   [d : Integer]
   [y : Integer]))

(define-type Day
  (U 'Sun 'Mon 'Tue 'Wed 'Thu 'Fri 'Sat))

Write a function leap? of type (Integer -> Boolean) to compute whether or not a year is a leap year. Leap years are not simply every fourth year. The precise rule is as follows:

A year is a leap year if it is divisible by 4 and not divisible by 100, or if it is divisible by 400.
Please raise an error if the integer argument to leap? is negative. Otherwise, go ahead and crunch the number.

The constructor Date induced by the Date structure definition builds a value, uncritically, out of any three integers. For example, you could construct the 49th day of December or the 2nd day of the 82nd month, or, for that matter, the -10th month. A constructor that considers whether or not its arguments are reasonable is called a smart constructor. Write a smart constructor to reject any dates that are either not within the 20th or 21st century, or are just nonsensical (like the examples above).

(: smart-construct-date (Integer Integer Integer -> Date))
The arguments to this function are the month, day, and year, in that order. The function should return a Date if the arguments are valid, or raise an error otherwise.

Now write two comparison functions, one to test whether two dates are exactly the same, and one to see if, among two dates, the first occurs before the other. These functions should have the following names and types:

  (: date=? (Date Date -> Boolean))
  (: date<? (Date Date -> Boolean))

We now give the formula for computing the days of the week. We first define a "month adjustment" as follows:

MonthAdjustment
Jan0 for leap years, 1 otherwise
Feb3 for leap years, 4 otherwise
Mar4
Apr0
May2
Jun5
Jul0
Aug3
Sep6
Oct1
Nov4
Dec6

The day of the week formula is this. Let m, d, and y be the month, day and year of a given date, and let j be the "month adjustment" (per the table above) for the given month and year. Then let n be the following:

n = (y - 1900) + j + d + floor(y/4)
where floor(y/4) is the integer quotient of y divided by 4. (For example, floor(401/4) is 100). Use the Typed Racket operation exact-floor in your code, or quotient if you prefer. Let w be the remainder of n divided by 7. Then if w = 0, the day is Sunday; if w = 1, the day is Monday; ...; if w = 6, the day is Saturday.

Given this formula, now write

(: day-of-week (Date -> Day))

Explore the terminal command cal (man cal describes the command in detail). When you construct tests for this function, you can use cal to check your answers easily. You should test at least one day from every month of the year.

Every function must be preceded by a contract (type ascription) and a purpose and be followed by check tests. Remember to write (test) below your definitions to run all the tests.

Please submit one file, lab3.rkt, in a lab3 directory in your repository.