date.ss
(module date mzscheme
  
  (require
   (lib "etc.ss")
   (file "base.ss")
   )
  
  (provide 
   (all-defined)
   )
  
  (define (leap-year? year)
    (if (= (remainder year 4) 0)
        (if (= (remainder year 100) 0)
            (if (= (remainder year 400) 0)
                #t
                #f)
            #t)
        #f))
  
  (define days-in-month
    (opt-lambda (month [year 2001]) ; non-leap-year by default
      (case month
        [(9 4 6 11) 30]
        [(2) (if (leap-year? year) 29 28)]
        [(1 3 5 7 8 9 10 12) 31]
        [else (raise-exn 
               exn:fail:unlib
               (format "Month out of range: ~a" month))])))
  
  ;; timestamp->ago-string : integer [integer] -> string
  ;;
  ;; Takes a seconds timestamp (and, optionally, the current timestamp) and
  ;; returns a string like:
  ;;
  ;;   n second(s) ago
  ;;   n minute(s) ago
  ;;   n hour(s) ago
  ;;   n day(s) ago
  (define timestamp->ago-string
    (opt-lambda (then [now (current-seconds)])
      (define (make-answer number unit)
        (cond
          [(and (= number 1) (equal? unit "day"))
           "yesterday"]
          [(= number 1)
           (format "~a ~a ago" number unit)]
          [else
           (format "~a ~as ago" number unit)]))
      (let ([difference (- now then)])
        (when (< difference 0)
          (raise-exn/format exn:fail:unlib
            "Expected first argument to be less than second), received ~a ~a." then now))
        (cond
          [(< difference 60)
           (make-answer difference "second")]
          [(< difference 3600)
           (make-answer (floor (/ difference 60)) "minute")]
          [(< difference 86400)
           (make-answer (floor (/ difference 3600)) "hour")]
          [else
           (make-answer (floor (/ difference 86400)) "day")]))))
  
  )