A day calculator |

Your task is to write a script days.kts that can perform three different tasks:

- When called with a single argument, it prints the day of the
week:
$ kts days.kts 2017/03/20 2017/03/20 is a Monday $ kts days.kts 2012/02/10 2012/02/10 is a Friday $ kts days.kts 1992/03/21 1992/03/21 is a Saturday

- When called with two arguments, it prints the number of days
between the two dates:
$ kts days.kts 1992/03/21 2017/03/20 There are 9130 days between 1992/03/21 and 2017/03/20 $ kts days.kts 2017/03/20 2015/12/24 There are -452 days between 2017/03/20 and 2015/12/24

- When called with three arguments, the first one must be a date,
the second one either plus or minus, and the last one
an integer. The script then calculates the date at the given
distance:
$ kts days.kts 2017/03/20 minus 100 2017/03/20 minus 100 days = 2016/12/10 $ kts days.kts 2017/03/20 plus 100 2017/03/20 plus 100 days = 2017/06/28

All dates have to be in the format YYYY/MM/DD. If not, the script should just print "Illegal Date" and do nothing else:

$ kts days.kts 1992/8/21 plus 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 = arrayOf(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) val leapMonthLength = arrayOf(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 fun 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 in 0 until monthn) total += ml[m] total += dayn return total }Copy it (or write your own :-) into a file days.kts and test it in the interactive mode:

$ ktc Welcome to Kotlin version 1.0.4 (JRE 1.8.0_91) Type :help for help, :quit for quit >>> :load days.kts >>> date2num(1901, 1, 1) 0 >>> date2num(1901, 2, 1) 31 >>> date2num(1901, 3, 1) 59 >>> date2num(1902, 3, 1) 424 >>> date2num(1902, 1, 1) 365 >>> date2num(2012, 2, 13) 40585 >>> date2num(2015, 3, 20) 41716 >>> date2num(2012, 2, 30) -1 >>> date2num(2012, 13, 1) -1 >>> date2num(2100, 4, 12) -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 fun string2date(s: String): Triple<Int, Int, Int> { // ... }

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().

Test your function before you continue. Use the interactive mode for testing:

>>> string2date("2010/02/12") (2010, 2, 12) >>> string2date("1492/02/31") (1492, 2, 31) >>> string2date("1492/2/31") (0, 0, 0) >>> string2date("14a2/2/31") (0, 0, 0) >>> string2date("14a2/02/31") (0, 0, 0)

Then you need a function that converts a day number back to a date:

fun num2date(n: Int): Triple<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:

fun testAll() { for (year in 1901 .. 2099) { for (month in 1 .. 12) { for (day in 1 .. 28) { val n = date2num(year, month, day) val (y,m,d) = num2date(n) if (year != y || month != m || day != d) println("Incorrect conversion day number $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, improve your script as follows:

When there is only one argument, print a calendar of the month containing the given date:

$ kts days.kts 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

A day calculator |