(defpackage :simple-date-tests
  (:use :common-lisp :fiveam :simple-date))

(in-package :simple-date-tests)

;; After loading the file, run the tests with (fiveam:run! :simple-date)

(def-suite :simple-date)
(in-suite :simple-date)

(test days-in-month
  ;; Note: internal date numbers, so 0 is March
  (is (= 31 (simple-date::days-in-month 0 2000)))
  (is (= 30 (simple-date::days-in-month 1 2000)))
  (is (= 31 (simple-date::days-in-month 2 2000)))
  (is (= 30 (simple-date::days-in-month 3 2000)))
  (is (= 31 (simple-date::days-in-month 4 2000)))
  (is (= 31 (simple-date::days-in-month 5 2000)))
  (is (= 30 (simple-date::days-in-month 6 2000)))
  (is (= 31 (simple-date::days-in-month 7 2000)))
  (is (= 30 (simple-date::days-in-month 8 2000)))
  (is (= 31 (simple-date::days-in-month 9 2000)))
  (is (= 31 (simple-date::days-in-month 10 2000)))
  (is (= 29 (simple-date::days-in-month 11 2000)))
  (is (= 28 (simple-date::days-in-month 11 2001))))

(defmacro with-random-dates (amount &body body)
  (let ((i (gensym)))
    `(dotimes (,i ,amount)
      (let ((year (+ 1900 (random 300)))
            (month (1+ (random 12)))
            (day (1+ (random 28)))
            (hour (random 24))
            (min (random 60))
            (sec (random 60))
            (millisec (random 1000)))
        ,@body))))

(test encode-date
  (with-random-dates 100
    (declare (ignore hour min sec millisec))
    (multiple-value-bind (year* month* day*) (decode-date (encode-date year month day))
      (is (and (= year* year)
               (= month* month)
               (= day* day))))))

(test leap-year
  (flet ((test-date (y m d)
           (multiple-value-bind (y2 m2 d2) (decode-date (encode-date y m d))
             (and (= y y2) (= m m2) (= d d2)))))
    (is (test-date 2000 2 29))
    (is (test-date 2004 2 29))
    (is (test-date 2108 2 29))
    (is (test-date 1992 2 29))))

(test encode-timestamp
  (with-random-dates 100
    (multiple-value-bind (year* month* day* hour* min* sec* millisec*)
        (decode-timestamp (encode-timestamp year month day hour min sec millisec))
      (is (and (= year* year)
               (= month* month)
               (= day* day)
               (= hour* hour)
               (= min* min)
               (= sec* sec)
               (= millisec* millisec))))))
  
(test timestamp-universal-times
  (with-random-dates 100
    (declare (ignore millisec))
    (let ((stamp (encode-timestamp year month day hour min sec 0))
          (utime (encode-universal-time sec min hour day month year 0)))
      (is (= (timestamp-to-universal-time stamp) utime))
      (is (time= (universal-time-to-timestamp utime) stamp)))))

(test add-month
  (with-random-dates 100
    (multiple-value-bind (year* month* day* hour* min* sec* millisec*)
        (decode-timestamp (time-add (encode-timestamp year month day hour min sec millisec)
                                    (encode-interval :month 1)))
      (is (and (or (and (= year* year) (= month* (1+ month)))
                   (and (= year* (1+ year)) (= month* 1)))
               (= day* day)
               (= hour* hour)
               (= min* min)
               (= sec* sec)
               (= millisec* millisec))))))

(test subtract-month
  (with-random-dates 100
    (multiple-value-bind (year* month* day* hour* min* sec* millisec*)
        (decode-timestamp (time-add (encode-timestamp year month day hour min sec millisec)
                                    (encode-interval :month -1)))
      (is (and (or (and (= year* year) (= month* (1- month)))
                   (and (= year* (1- year)) (= month* 12)))
               (= day* day)
               (= hour* hour)
               (= min* min)
               (= sec* sec)
               (= millisec* millisec))))))

(test add-hour
  (with-random-dates 100
    (declare (ignore millisec))
    (is (= (- (timestamp-to-universal-time (time-add (encode-timestamp year month day hour min sec 0)
                                                     (encode-interval :hour 1)))
              (encode-universal-time sec min hour day month year 0))
           3600))))

(test time<
  (with-random-dates 100
    (is (time< (encode-date year month day)
               (encode-date (1+ year) month day)))
    (is (time< (encode-timestamp year month day hour min sec millisec)
               (encode-timestamp year month day hour min (1+ sec) millisec)))
    (is (time< (encode-interval :month month :hour hour)
               (encode-interval :month month :hour hour :minute 30)))))
