Your task is to write a Scala script days.scala that can perform three different tasks:
> scala days.scala 2015/03/20 2015/03/20 is a Friday > scala days.scala 2012/02/10 2012/02/10 is a Friday > scala days.scala 1992/03/21 1992/03/21 is a Saturday
> scala days.scala 1995/12/01 2015/03/20 There are 7049 days between 1995/12/01 and 2015/03/20 > scala days.scala 2015/03/20 2014/08/24 There are -208 days between 2015/03/20 and 2014/08/24
> scala days.scala 1995/12/01 + 100 1995/12/01 + 100 days = 1996/03/10 > scala days.scala 2015/03/20 - 1000 2015/03/20 - 1000 days = 2012/06/23
All dates have to be in the format YYYY/MM/DD. If not, the script should just print "Illegal Date" and do nothing else:
> scala days.scala 1992/8/21 + 100 Illegal date
Your script needs to handle dates between 1901/01/01 and 2099/12/31 only. This simplifies the problem, since leap years are exactly the years divisible by four: 1904, 1908, ..., 1996, 2000, 2004, 2008, ...
The right way of implementing this task is to create two functions that convert between dates and a day number. Let 1901/01/01 be day number 0, 1901/01/02 day number 1, and so on. Then all computations can be done by taking the day number modulo 7 (to compute the weekday), taking the difference of the day numbers, or adding an integer to the day number and converting back to a date.
I suggest you start by implementing a function that converts a date (year, month, day) into a day number. To make your life easy, I've written this function for you:
val monthLength = Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) val leapMonthLength = Array(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) val fourYears = (365 + 365 + 365 + 366) // returns the number of days since 1901/01/01 (day 0) // returns -1 if illegal, or out of range 1901~2099 def date2num(year: Int, month: Int, day: Int): Int = { if (year < 1901 || year > 2099 || month < 1 || month > 12) return -1 val is_leap = (year % 4 == 0) val ml = if (is_leap) leapMonthLength else monthLength if (day < 1 || day > ml(month-1)) return -1 val yearn = year - 1901 val monthn = month - 1 val dayn = day - 1 var total = 0 total += fourYears * (yearn / 4) total += 365 * (yearn % 4) for (m <- 0 until monthn) total += ml(m) total += dayn total }Copy it (or write your own :-) into a file days.scala and test it in Scala's interactive mode:
> scala -i days.scala Loading days.scala... monthLength: Array[Int] = Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) leapMonthLength: Array[Int] = Array(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) fourYears: Int = 1461 date2num: (year: Int, month: Int, day: Int)Int Welcome to Scala version 2.11.5 (OpenJDK 64-Bit Server VM, Java 1.7.0_75). Type in expressions to have them evaluated. Type :help for more information. scala> date2num(1901, 1, 1) res1: Int = 0 scala> date2num(1901, 2, 1) res2: Int = 31 scala> date2num(1901, 3, 1) res3: Int = 59 scala> date2num(1902, 3, 1) res4: Int = 424 scala> date2num(1902, 1, 1) res5: Int = 365 scala> date2num(2012, 2, 13) res6: Int = 40585 scala> date2num(2015, 3, 20) res8: Int = 41716 scala> date2num(2012, 2, 30) res8: Int = -1 scala> date2num(2012, 13, 1) res9: Int = -1 scala> date2num(2100, 4, 12) res10: Int = -1As you can see, when the date is not correct or outside the range 1901..2099, the function returns -1.
Next, you need a function to convert a command line argument (a string) into a date, that is, a triple of year, month, and day:
// decode a string date // returns (0,0,0) if not in "YYYY/MM/DD" format def string2date(s: String): (Int, Int, Int) = { // ... }
Again, test this before you continue using the interactive mode:
scala> string2date("2010/02/12") res11: (Int, Int, Int) = (2010,2,12) scala> string2date("1492/02/31") res12: (Int, Int, Int) = (1492,2,31) scala> string2date("1492/2/31") res13: (Int, Int, Int) = (0,0,0) scala> string2date("14a2/2/31") res14: (Int, Int, Int) = (0,0,0) scala> string2date("14a2/02/31") res15: (Int, Int, Int) = (0,0,0)
The function checks that the string has exactly 10 characters, that characters 4 and 7 are slashes, and that all other characters are digits. Then it converts each piece to an integer using .toInt.
Then you need a function that converts a day number back to a date:
def num2date(n: Int): (Int, Int, Int) = { ... }
Again, you should test this function before you continue.
After doing some quick manual tests, at this point it makes sense to write a separate testing function that checks that num2date and date2num implement the inverse function. Here is such a test function:
def testAll() { for (year <- 1901 to 2099) { for (month <- 1 to 12) { for (day <- 1 to 28) { val n = date2num(year, month, day) val (y,m,d) = num2date(n) if (year != y || month != m || day != d) println(n, y, m, d) } } } }
Now all you need to do is to check the number of command line arguments and handle each case.
When you are done and you still have time left in the lab, do the following:
First, when there is only one argument, print a calendar of the month containing the given date:
> scala days.scala 2012/02/13 2012/02/13 is a Monday February 2012 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
Second, modify your script so that it works correctly for all dates starting with 1600/01/01. Remember that a year is a leap year if it is divisible by 400, or if it is divisible by 4 but not by 100. So 1600, 2000, 2400 are leap years, but 1700, 1800, 1900, 2100, 2200 are not.