(* Check if a given year is a leap year *)
let isLeapYear y =
    y > 1582 && y mod 4 = 0 && (y mod 100 != 0 || y mod 400 = 0)
;;

(* Test the isLeapYear function with a number of known leap years *)
let test_isLeapYear y expect =
    let yields = isLeapYear y in
    Printf.printf "%d -> %s, (should be %b, yields %b)\n"
        y (if yields = expect then "success" else "failure") expect yields
;;
test_isLeapYear 1500 false;;
test_isLeapYear 1900 false;;
test_isLeapYear 1904 true;;

(* Give a proper English character string representation of a date *)
let date2str day month year =
    if day < 1 || day > 31 || month < 1 || month > 12 || year < 0 then
        raise (Failure "invalid date")
    else
        (* Get the textual postfix of a day number *)
        let getDayPostfix day =
            match day with
              1 | 21 | 31 -> "st"
            | 2 | 22 -> "nd"
            | 3 | 23 -> "rd"
            | _ -> "th" in
        let months_str = ["January"; "February"; "March"; "April"; "May";
            "June"; "July"; "August"; "September"; "October"; "November";
            "December"] in
        let month_str = List.nth months_str (month - 1) in
        Printf.sprintf "%s %d%s, %4d" month_str day (getDayPostfix day) year
;;

(* Test the date2str function with a few common and exceptional cases *)
let test_date2str d m y expect =
    let str = (date2str d m y) in
    Printf.printf "%d %d %d -> %s, (should be %s, yields %s)\n"
        d m y (if str = expect then "success" else "failure") expect str
;;
test_date2str 1 1 2010 "January 1st, 2010";;
test_date2str 9 2 2010 "February 9th, 2010";;
test_date2str 3 3 2011 "March 3rd, 2011";;
test_date2str 22 12 2012 "December 22nd, 2012";;

(* Sum all digits of a natural number *)
let rec sum_digits n =
    if n < 0 then raise (Failure "cannot sum digits in integers below 0") else
    let str = string_of_int n in
    let l = String.length str in
    if l = 1 then
        int_of_string str
    else
        int_of_char str.[0] - 48
            + sum_digits (int_of_string (String.sub str 1 (l - 1)))
;;
(* Get the digital root of a natural number n *)
let rec digitRoot n =
    let sum = sum_digits n in
    if (String.length (string_of_int sum) = 1) then sum else (digitRoot sum)
;;

(* Test the digitRoot function for a few known solutions *)
let test_digitRoot n expect =
    let root = digitRoot n in
    Printf.printf "%d -> %s, (should be %d, yields %d)\n"
        n (if root = expect then "success" else "failure") expect root
;;
test_digitRoot 123 6;;
test_digitRoot 65536 7;;

(* Check if a given string is a palindrome *)
let rec isPalindrome str =
    let l = (String.length str) in
    l < 2 ||
    (str.[0] = str.[l - 1] && (isPalindrome (String.sub str 1 (l - 2))))
;;

(* Test the isPalindrome function with a few known palindromes *)
let test_isPalindrome str expect =
    let result = (isPalindrome str) in
    Printf.printf "%s -> %s, (should be %b, yields %b)\n"
        str (if result = expect then "success" else "failure") expect result
;;
test_isPalindrome "foo" false;; (* non-palindrome of odd length *)
test_isPalindrome "foobar" false;; (* non-palindrome of even length *)
test_isPalindrome "lepel" true;; (* palindrome of odd length *)
test_isPalindrome "foof" true;; (* palindrome of even length *)
test_isPalindrome "" true;; (* The empty string is a palindrome *)
