Back

on (clj)

(source)

protocol

(on time date)
Set time be ON a date

Examples

tick
(ns tick.api-test
  (:require
    [clojure.test
     :refer [deftest is testing run-tests]
     :refer-macros [deftest is testing run-tests]]
    [tick.core :as t]
    [tick.locale-en-us]
    [tick.protocols :as p]
    [cljc.java-time.clock]
    [cljc.java-time.instant]
    [cljc.java-time.day-of-week]
    [cljc.java-time.month]
    [cljc.java-time.year]
    #?@(:cljs [[java.time :refer [Instant]]]))
  #?(:clj
     (:import [java.time Instant])))

(deftest time-construction-test
  (testing "(time)"
    (is (t/time? (t/time))))
  (testing "(midnight)"
    (is (t/time? (t/midnight)))
    (is (= "00:00" (str (t/midnight)))))
  (testing "(noon)"
    (is (t/time? (t/noon)))
    (is (= "12:00" (str (t/noon))))))

(deftest date-construction-test
  (is (= (t/date "2018-01-11")
        (t/date (t/instant 1515691416624))))
  (is (t/date-time? (t/noon (t/today))))
  (t/with-clock (-> (t/date "2018-02-14") (t/at "10:00"))
    (testing "(noon (today))"
      (is (= "2018-02-14T12:00" (str (t/noon (t/today))))))
    (testing "(noon (date))"
      (is (= "2018-02-14T12:00" (str (t/noon (t/date))))))))

;; TODO: Clock tests
;; Create with a value for a fixed clock. Value can be a time or a zone

(deftest clock-test
  (testing "clock"
    (t/with-clock (-> (t/date "2018-02-14") (t/at "10:00") (t/in "America/New_York"))
      (testing "(clock) return type"
        (is (t/clock? (t/clock))))
      (testing "Time shifting the clock back by 2 hours"
        (is (= "2018-02-14T13:00:00Z" (str (t/instant (t/<< (t/clock) (t/new-duration 2 :hours)))))))
      (testing "with instant"
        (is (= (t/zone (t/clock (t/instant)))
              (t/zone "America/New_York"))))))

  (testing "Converting using with-clock"
    (t/with-clock (t/clock (t/zone "America/New_York"))
      (testing "inst to zoned-date-time"
        (is (t/= (t/zoned-date-time "2019-08-07T16:00Z")
              (t/zoned-date-time "2019-08-07T12:00-04:00[America/New_York]"))))
      (testing "date-time to zoned-date-time"
        (is (t/= (t/zoned-date-time (t/date-time "2019-08-07T12:00"))
              (t/zoned-date-time "2019-08-07T12:00-04:00[America/New_York]"))))
      (testing "date-time to offset-date-time"
        (is (t/= (t/offset-date-time (t/date-time "2019-08-07T12:00"))
              (t/offset-date-time "2019-08-07T12:00-04:00"))))))

  (testing "Creating a clock with a zone, and returning that zone"
    (is (= "America/New_York" (str (t/zone (t/clock (t/zone "America/New_York")))))))

  (testing "Creation of clock with fixed instant"
    (is (= "2017-10-31T16:00:00Z" (str (t/instant (t/clock (t/instant "2017-10-31T16:00:00Z")))))))

  (testing "Creation of clock with fixed offset"
    (is (= "+01:00" (str (t/zone (t/clock (t/offset-date-time "2017-10-31T16:00:00+01:00"))))))))


(deftest constructor-test
  (is (t/year? (t/year 2017)))
  (is (= 2017 (cljc.java-time.year/get-value (t/year 2017))))
  (is (t/month? (t/month 12)))
  (is (= t/DECEMBER (t/month 12)))
  (is (= (t/new-date 3030 3 3)
         (t/date "3030-03-03")))
  (is (-> (t/new-duration 1000 :millis)
          (t/inst)
          (t/instant)
          (cljc.java-time.instant/to-epoch-milli)
          (= 1000)))
  (is (= (t/new-year-month 2020 7)
         (t/year-month  "2020-07"))))

(deftest extraction-test
  (is (= 2 (t/int t/FEBRUARY)))
  (is (= 2 (t/int t/TUESDAY)))
  (is (= t/AUGUST (t/month (t/date-time "2017-08-08T12:00:00"))))
  (is (= t/AUGUST (t/month (t/year-month "2017-08"))))
  (is (= (t/year 2019) (t/year (t/zoned-date-time "2019-09-05T00:00:00+02:00[Europe/Oslo]"))))
  (is (= (t/year 2019) (t/year (t/offset-date-time "2019-09-05T00:00:00-03:00"))))
  (is (= (t/zone-offset "-04:00")
         (t/zone-offset (t/zoned-date-time "2019-03-15T15:00-04:00[America/New_York]"))))
  (is (= (t/zone-offset "-04:00")
         (t/zone-offset (t/offset-date-time "2019-03-15T15:00-04:00")))))

;; Point-in-time tests
(deftest today-test
  (t/with-clock (cljc.java-time.clock/fixed (t/instant "2017-08-08T12:00:00Z") t/UTC)
    (is (= (t/instant "2017-08-08T12:00:00Z") (t/now)))
    (is (= (t/date "2017-08-08") (t/today)))
    (is (= (t/date "2017-08-07") (t/yesterday)))
    (is (= (t/date "2017-08-09") (t/tomorrow)))
    (is (= 8 (t/day-of-month (t/today))))
    (is (= 2017 (t/int (t/year))))
    (is (= (t/date-time "2017-08-08T12:00:00") (t/noon (t/today))))
    (is (= (t/date-time "2017-08-08T00:00:00") (t/midnight (t/today))))))

(deftest instant-test
  (testing "instant basics"
    (is (t/instant? (t/instant (t/now))))
    (is (t/instant? (t/instant (str cljc.java-time.instant/min))))
    (is (t/instant? (t/instant (t/zoned-date-time))))))

(deftest offset-date-time-test
  (let [t "2018-09-24T18:57:08.996+01:00"]
    (testing "offset date time basics"
      (is (t/offset-date-time? (t/offset-date-time (t/now))))
      (is (t/offset-date-time? (t/offset-date-time t)))
      (is (t/offset-date-time? (t/offset-date-time (t/date-time))))
      (is (t/offset-date-time? (t/offset-date-time (t/zoned-date-time)))))))

(deftest zoned-date-time-test
  (is (t/zoned-date-time? (t/zoned-date-time "2020-12-15T12:00:10Z[Europe/London]")))
  (is (t/zoned-date-time? (t/zoned-date-time "2020-12-15T12:00:10+04:00[Europe/London]"))))

(deftest fields-test
  (let [xs [(t/now)
            (t/zoned-date-time)
            (t/offset-date-time)
            (t/date-time)
            (t/date)
            (t/time)
            (t/year)
            (t/year-month)]]
    (doseq [x xs]
      (let [fields (t/fields x)
            fields-map (into {} fields)]
        (is (not-empty fields-map))
        (doseq [[f v] fields-map]
          (is (= v (get fields f)))
          (is (= :foo (get fields :bar :foo))))))))

(deftest formatting-test
  (testing "all predefined formatters exist"
    (doseq [pre-defined (vals t/predefined-formatters)]
      (is pre-defined)))
  (let [d "3030-05-03"]
    (is (= d (t/format :iso-local-date (t/date d))))
    (is (= d (t/format (t/formatter :iso-local-date) (t/date d))))
    (is (= d (t/format (t/formatter "YYYY-MM-dd") (t/date d))))
    #?(:bb
       ; bb only inlcludes the English Locale by default
       (is (= "3030-May-03" (t/format (t/formatter "YYYY-MMM-dd" java.util.Locale/FRENCH)
                              (t/date d))))
       :clj
       (is (= "3030-mai-03" (t/format (t/formatter "YYYY-MMM-dd" java.util.Locale/FRENCH)
                              (t/date d)))))))

(deftest addition-test
  (is (= (t/new-duration 5 :seconds) (t/+ (t/new-duration 2 :seconds) (t/new-duration 3 :seconds))))
  (is (= (t/new-duration 2 :minutes) (t/+ (t/new-duration 90 :seconds) (t/new-duration 30 :seconds)))))

(deftest subtraction-test
  (is (= (t/new-duration 3 :seconds) (t/- (t/new-duration 5 :seconds) (t/new-duration 2 :seconds)))))

  (let [now (t/instant)
        from (t/<< now (t/new-duration 10 :seconds))
        to (t/>> now (t/new-duration 10 :seconds))]
    (is (= (t/new-duration 20 :seconds) (t/between from to) ))
    (is (= 20 (t/between from to :seconds))))
  (is
    (= (t/new-duration 48 :hours)
      (t/between (t/beginning (t/today)) (t/end (t/tomorrow)))))
  (let [start (t/date-time "2020-01-01T12:00") 
        end (t/date-time "2020-01-01T12:02")]
    (is
      (=
        (t/new-duration 2 :minutes)
        (t/between start end))
      (= 2 (t/between start end :minutes))))
  (is
    (=
      (t/new-duration 30 :minutes)
      (t/between (t/new-time 11 0 0) (t/new-time 11 30 0))))
  (testing "LocalDate"
    (let [start (t/date "2020-01-01")
          end (t/date "2020-01-02")]
      (is (= (t/new-period 1 :days)
            (t/between start end)))
      (is (= 1 (t/between start end :days))))))

(deftest units-test
  (is (=
        {:seconds 0, :nanos 1}
        (t/units (t/new-duration 1 :nanos))))
  (is
    (=
      {:years 10, :months 0, :days 0}
      (t/units (t/new-period 10 :years)))))

;; Comparison test
(defn point-in-time-comparable [i]
  [i
   (t/inst i)
   (t/zoned-date-time i)
   (t/offset-date-time i)])

(deftest truncate-test
  (let [dates [(t/instant) (t/zoned-date-time) (t/date-time)
               (t/offset-date-time) (t/time)]
        truncate-tos [:nanos
                     :micros
                     :millis
                     :seconds
                     :minutes
                     :hours
                     :half-days
                     :days     ]]
    (doseq [date dates
            truncate-to truncate-tos]
      (is (t/truncate date truncate-to)))))

(deftest parse-test
  (is (t/date? (t/parse-date "2020/02/02" (t/formatter "yyyy/MM/dd"))))
  (is (t/year? (t/parse-year "20" (t/formatter "yy"))))
  (is (t/year-month? (t/parse-year-month "20/02" (t/formatter "yy/MM"))))
  (is (t/date-time? (t/parse-date-time "2020/02/02:2002" (t/formatter "yyyy/MM/dd:HHmm"))))
  (is (t/time? (t/parse-time "2002" (t/formatter "HHmm"))))
  (is (t/zoned-date-time? (t/parse-zoned-date-time "2020/02/02:2002:Z"
                            (t/formatter "yyyy/MM/dd:HHmm:VV"))))
  (is (t/offset-date-time? (t/parse-offset-date-time "2020/02/02:2002:-08:30"
                             (t/formatter "yyyy/MM/dd:HHmm:VV")))))

(deftest comparison-test
  (let [point (t/truncate (t/instant) :millis)
        later (t/>> point (t/new-duration 1 :millis))]
    (testing "shifting inst"
      (let [i (t/inst)]
        (is (= i (-> i
                     (t/>> (t/new-duration 10 :seconds))
                     (t/<< (t/new-duration 10 :seconds)))))))
    (testing "max-min"
      (is (= later (t/max point later point later)))
      (is (= point (t/min point later point later))))
    (testing "max-min key"
      (is (= {:foo later} (t/max-key :foo {:foo point} {:foo later} {:foo point} {:foo later})))
      (is (= {:foo point} (t/min-key :foo {:foo point} {:foo later} {:foo point} {:foo later}))))
    (testing "comparables not="
      (doseq [point (point-in-time-comparable point)]
        (testing "comparables ="
          (is (apply t/= point (point-in-time-comparable point)))
          (is (apply t/>= point (point-in-time-comparable point))))
        (is (apply t/<= point (point-in-time-comparable later))))
      (doseq [later (point-in-time-comparable later)]
        (is (apply t/>= later (point-in-time-comparable point))))

  (testing "ZonedDateTimes in different zones should be equals"
    (is (t/=
          (t/zoned-date-time "2017-10-31T16:00:00-04:00[America/New_York]")
          (t/zoned-date-time "2017-10-31T13:00:00-07:00[America/Los_Angeles]"))))

  (testing "ZoneDateTimes and OffsetDateTime should be equals if represents the same point in time"
    (is (t/=
          (t/zoned-date-time "2017-10-31T16:00:00-04:00[America/New_York]")
          (t/offset-date-time "2017-10-31T13:00-07:00"))))

  (testing "ZoneDateTimes and platform Date should be equals if represents the same point in time"
    (is (t/=
          (t/zoned-date-time "2017-10-31T16:00:00-04:00[America/New_York]")
          (t/inst "2017-10-31T20:00:00Z"))))

  (testing "Instants and ZonedDateTimes should be equals if represents the same point in time"
    (is (t/=
          (t/instant (t/clock (t/instant "2017-10-31T16:00:00Z")))
          (t/zoned-date-time "2017-10-31T16:00:00Z[UTC]"))))
  (is
    (t/<
      (t/now)
      (t/>> (t/now) (t/new-duration 10 :seconds))
      (t/>> (t/now) (t/new-duration 20 :seconds))))
  (is
    (t/>
      (t/>> (t/now) (t/new-duration 20 :seconds))
      (t/>> (t/now) (t/new-duration 10 :seconds))
      (t/now)))
  (is (not
        (t/<
          (t/now)
          (t/>> (t/now) (t/new-duration 20 :seconds))
          (t/>> (t/now) (t/new-duration 10 :seconds)))))
  (let [at (t/now)]
    (is (t/<= at at (t/>> at (t/new-duration 1 :seconds))))
    (is (t/>= at at (t/<< at (t/new-duration 10 :seconds)))))

  (testing "durations"
    (is (t/> (t/new-duration 20 :seconds) (t/new-duration 10 :seconds)))
    (is (t/>= (t/new-duration 20 :seconds) (t/new-duration 20 :seconds)))
    (is (t/>= (t/new-duration 20 :seconds) (t/new-duration 19 :seconds)))
    (is (not (t/>= (t/new-duration 19 :seconds) (t/new-duration 20 :seconds))))
    (is (t/< (t/new-duration 10 :seconds) (t/new-duration 20 :seconds)))
    (is (t/<= (t/new-duration 20 :seconds) (t/new-duration 20 :seconds)))
    (is (t/<= (t/new-duration 19 :seconds) (t/new-duration 20 :seconds)))
    (is (not (t/<= (t/new-duration 20 :seconds) (t/new-duration 19 :seconds))))))


(deftest comparison-test-date
  (let [t1 (t/inst "2019-12-24T00:00:00.000Z")
        t2 (t/inst "2019-12-31T00:00:00.000Z")]

(deftest coincidence-test
  (let [int-beginning (t/instant "2020-02-02T00:00:00Z")
        int-end (t/>> int-beginning (t/of-hours 2))
        interval {:tick/beginning int-beginning
                  :tick/end       int-end}]
    (is (t/coincident? interval (t/>> int-beginning (t/of-hours 1))))
    (is (not (t/coincident? interval (t/<< int-beginning (t/of-hours 1)))))
    (is (t/coincident? interval int-beginning))
    (is (t/coincident? interval int-end))
    (is (t/coincident? interval interval))
    (is (not (t/coincident? interval (-> interval
                                         (update :tick/end #(t/>> % (t/of-nanos 1))))))))
  (testing "non-interval coincidence"
    (doseq [[start-f new-amount] [[t/date t/of-days] [t/date-time t/of-hours]]]
      (let [start (start-f)
            end (t/>> start (new-amount 2))]
        (is (t/coincident? start end (t/>> start (new-amount 1))))
        (is (not (t/coincident? start end (t/<< start (new-amount 1)))))
        (is (t/coincident? start end start))
        (is (t/coincident? start end end))))))

(deftest day-of-week
  (let [days (fn [strings] (map t/day-of-week strings))]
    (is (every? #{cljc.java-time.day-of-week/sunday} (days ["sun" "sunday"])))
    (is (every? #{cljc.java-time.day-of-week/monday} (days ["mon" "monday"])))
    (is (every? #{cljc.java-time.day-of-week/tuesday} (days ["tue" "tues" "tuesday"])))
    (is (every? #{cljc.java-time.day-of-week/wednesday} (days ["wed" "weds" "wednesday"])))
    (is (every? #{cljc.java-time.day-of-week/thursday} (days ["thur" "thurs" "thursday"])))
    (is (every? #{cljc.java-time.day-of-week/friday} (days ["fri" "friday"])))
    (is (every? #{cljc.java-time.day-of-week/saturday} (days ["sat" "saturday"])))))

(deftest adjusters
  (is (= (t/date "2022-01-15")
         (t/day-of-week-in-month (t/date "2022-01-01") 3 t/SATURDAY)))
  (is (= (t/date "2022-01-01")
         (t/first-in-month (t/date "2022-01-01") t/SATURDAY)))
  (is (= (t/date "2022-01-29")
         (t/last-in-month (t/date "2022-01-01") t/SATURDAY)))
  (is (= (t/date "2022-01-08")
         (t/next (t/date "2022-01-01") t/SATURDAY)))
  (is (= (t/date "2022-01-01")
         (t/next-or-same (t/date "2022-01-01") t/SATURDAY)))
  (is (= (t/date "2021-12-25")
         (t/previous (t/date "2022-01-01") t/SATURDAY)))
  (is (= (t/date "2022-01-01")
         (t/previous-or-same (t/date "2022-01-01") t/SATURDAY))))

(deftest month
  (let [months (fn [strings] (map t/month strings))]
    (is (every? #{cljc.java-time.month/january} (months ["jan" "january"])))
    (is (every? #{cljc.java-time.month/february} (months ["feb" "february"])))
    (is (every? #{cljc.java-time.month/march} (months ["mar" "march"])))
    (is (every? #{cljc.java-time.month/april} (months ["apr" "april"])))
    (is (every? #{cljc.java-time.month/may} (months ["may"])))
    (is (every? #{cljc.java-time.month/june} (months ["jun" "june"])))
    (is (every? #{cljc.java-time.month/july} (months ["jul" "july"])))
    (is (every? #{cljc.java-time.month/august} (months ["aug" "august"])))
    (is (every? #{cljc.java-time.month/september} (months ["sep" "september"])))
    (is (every? #{cljc.java-time.month/october} (months ["oct" "october"])))
    (is (every? #{cljc.java-time.month/november} (months ["nov" "november"])))
    (is (every? #{cljc.java-time.month/december} (months ["dec" "december"])))))

;; Durations. Simple constructors to create durations of specific
;; units.

(deftest duration-test
  (is (= (t/new-duration 1e6 :nanos) (t/new-duration 1 :millis)))
  (is (= (t/new-duration 1e9 :nanos) (t/new-duration 1 :seconds)))
  (is (= (t/new-duration 1000 :millis) (t/new-duration 1 :seconds)))

  (is (= (t/of-hours 24) (t/duration (t/date))))
  (is (= (t/of-days 1) (t/duration {:tick/beginning (t/date)
                                    :tick/end       (t/inc (t/date))}))))

;; Durations. Convenience functions to create durations of specific
;; units.
(deftest duration-functions-test
  (is (= (t/of-nanos 10) (t/new-duration 10 :nanos)))
  (is (= (t/of-micros 10) (t/new-duration 10 :micros))) ;java.time.Duration doesn't have ofMicros method
  (is (= (t/of-millis 10) (t/new-duration 10 :millis)))
  (is (= (t/of-seconds 10) (t/new-duration 10 :seconds)))
  (is (= (t/of-minutes 10) (t/new-duration 10 :minutes)))
  (is (= (t/of-hours 10) (t/new-duration 10 :hours))))


;; Periods. Convenience functions to create periods of specific
;; units.
(deftest period-functions-test
  (is (= (t/of-days 10) (t/new-period 10 :days)))
  (is (= (t/of-months 10) (t/new-period 10 :months)))
  (is (= (t/of-years 10) (t/new-period 10 :years))))


(deftest predicates-test
  (is (true? (t/clock? (t/clock))))
  (is (true? (t/day-of-week? t/MONDAY)))
  (is (true? (t/duration? (t/new-duration 1 :minutes))))
  (is (true? (t/instant? (t/instant))))
  (is (true? (t/date? (t/today))))
  (is (true? (t/date-time? (t/at (t/today) (t/new-time 0 0)))))
  (is (true? (t/time? (t/new-time 0 0))))
  (is (true? (t/month? t/MAY)))
  (is (true? (t/offset-date-time? (t/offset-date-time))))
  (is (true? (t/period? (t/new-period 1 :weeks))))
  (is (true? (t/year? (t/year))))
  (is (true? (t/year-month? (t/year-month))))
  (is (true? (t/zone? (t/zone))))
  (is (true? (t/zone-offset? (t/zone-offset (t/zoned-date-time)))))
  (is (true? (t/zoned-date-time? (t/zoned-date-time))))
  (is (false? (t/date? 16)))
  (is (false? (t/month? 16))))

(deftest in-test
  (is (= (t/zoned-date-time "2021-04-23T11:23:24.576270-04:00[America/Toronto]")
         (t/in (t/instant "2021-04-23T15:23:24.576270Z")
               (t/zone "America/Toronto"))))
  (is (= (t/zoned-date-time "2021-04-23T11:18:46.594720-04:00[America/Toronto]")
         (t/in (t/offset-date-time "2021-04-23T13:18:46.594720-02:00")
               (t/zone "America/Toronto"))))
  (is (= (t/zoned-date-time "2021-04-23T11:18:46.594720-04:00[America/Toronto]")
         (t/in (t/zoned-date-time "2021-04-23T08:18:46.594720-07:00[America/Los_Angeles]")
               (t/zone "America/Toronto")))))

(deftest divide-test
  (is
    ;; Duration -> Long -> Duration
    (= (t/new-duration 6 :hours) (t/divide (t/new-duration 6 :days) 24))
    ;; Duration -> Duration -> Long
    (= 63 (t/divide (t/new-duration 21 :days) (t/new-duration 8 :hours)))))
tick
(ns tick.alpha.interval-test
  (:require
   [clojure.spec.alpha :as s]
   [tick.core :as t]
   [tick.protocols :as p]
   [clojure.test
    :refer [deftest is testing run-tests]
    :refer-macros [deftest is testing run-tests]]
   [tick.alpha.interval :as ti]
   #?@(:cljs [[java.time :refer [Instant LocalDateTime LocalTime]]]))
  #?(:clj
     (:import [java.time LocalDateTime Instant LocalTime])))


(deftest date-relation-test
  (is (=
        (ti/relation
          (ti/new-interval
            (t/zoned-date-time "2021-02-24T00:00Z[GMT]")
            (t/zoned-date-time "2021-02-25T00:00Z[GMT]"))
          (ti/new-interval
            (t/zoned-date-time "2021-02-23T00:00Z[Europe/London]")
            (t/zoned-date-time "2021-02-24T00:00Z[Europe/London]")))
        :met-by)))


(deftest basic-relations-test
  (is (= (count ti/basic-relations) 13))
  (is (distinct? ti/basic-relations)))

;; We can construct every possible combination of interval relation with just 4 instants.
(def instants [(t/instant "2017-07-30T09:00:00Z")
               (t/instant "2017-07-30T11:00:00Z")
               (t/instant "2017-07-30T13:00:00Z")
               (t/instant "2017-07-30T15:00:00Z")])

;; Distinct: because no pair of definite intervals can be related by more than one of the relationships.
;; From [ALSPAUGH-2009]
(deftest distinct-test
  (is
    (= [1]   ; Each interval should have just one relation that is true
       (distinct
         (let [f (apply juxt ti/basic-relations)]
           (for [x1 instants
                 x2 instants
                 y1 instants
                 y2 instants
                 :when (t/< x1 x2)
                 :when (t/< y1 y2)
                 :let [x (ti/new-interval x1 x2)
                       y (ti/new-interval y1 y2)]]
             ;; For each combination, count how many relations are true
             ;; (should be just one each time)
             (count (filter true? (f x y)))))))))

;; Exhaustive: because any pair of definite intervals are described by one of the relations.
(deftest exhaustive-test []
  (is
    (= 13 ; Thirteen basic relations
       (count
         (distinct
           (for [x1 instants
                 x2 instants
                 y1 instants
                 y2 instants
                 :when (t/< x1 x2)
                 :when (t/< y1 y2)
                 :let [x (ti/new-interval x1 x2)
                       y (ti/new-interval y1 y2)]]
             ;; For each combination, count how many relations are true
             ;; (should be just one each time)
             (ti/relation x y)))))))

;; concur is really the complement to disjoint, but we'll test it
;; anywhere to ensure the complement function is working as expected.

(deftest concur?-test []
  (is (nil?
        (ti/concur?
          (ti/new-interval (instants 0) (instants 1))
          (ti/new-interval (instants 2) (instants 3)))))
  (is (= (ti/concur?
           (ti/new-interval (instants 0) (instants 2))
           (ti/new-interval (instants 1) (instants 3)))
         ti/overlaps?))
  (is (= (ti/concur?
           (ti/new-interval (instants 0) (instants 3))
           (ti/new-interval (instants 1) (instants 2)))
         ti/contains?)))

(deftest concur-test []
  (is
    (=
      (ti/new-interval (instants 1) (instants 2))
      (ti/concur
        (ti/new-interval (instants 0) (instants 2))
        (ti/new-interval (instants 1) (instants 3)))))

  (is
    (=
      (ti/new-interval (instants 1) (instants 2))
      (ti/concur
        (ti/new-interval (instants 1) (instants 3))
        (ti/new-interval (instants 0) (instants 2)))))

  (is
    (nil?
      (ti/concur
        (ti/new-interval (instants 0) (instants 1))
        (ti/new-interval (instants 2) (instants 3)))))

  (is
    (nil?
      (ti/concur
        (ti/new-interval (instants 0) (instants 1))
        (ti/new-interval (instants 1) (instants 2)))))

  (is
    (=
      (ti/new-interval (instants 0) (instants 2))
      (ti/concur
        (ti/new-interval (instants 0) (instants 2))
        (ti/new-interval (instants 0) (instants 3)))))

  (is
    (=
      (ti/new-interval (instants 0) (instants 2))
      (ti/concur
        (ti/new-interval (instants 0) (instants 3))
        (ti/new-interval (instants 0) (instants 2)))))

  (is
    (=
      (ti/new-interval (instants 1) (instants 3))
      (ti/concur
        (ti/new-interval (instants 1) (instants 3))
        (ti/new-interval (instants 0) (instants 3)))))

  (is
    (=
      (ti/new-interval (instants 1) (instants 2))
      (ti/concur
        (ti/new-interval (instants 1) (instants 3))
        (ti/new-interval (instants 1) (instants 2))
        (ti/new-interval (instants 0) (instants 2)))))

  (is
    (nil?
      (ti/concur
        (ti/new-interval (instants 1) (instants 2))
        (ti/new-interval (instants 2) (instants 3))
        (ti/new-interval (instants 0) (instants 2)))))

  (is
    (nil?
      (ti/concur
        (ti/new-interval (instants 0) (instants 1))
        (ti/new-interval (instants 1) (instants 2))
        (ti/new-interval (instants 2) (instants 3))))))

;; TODO: Support this: (ti/new-interval (t/now) (t/seconds 10))
;; TODO: Don't allow this: (ti/new-interval (t/now)) -- returns an illegal ti/new-interval

        (map ti/bounds
             (ti/unite [(ti/new-interval (t/date "2017-06-10") (t/date "2017-06-20"))
                        (ti/new-interval (t/date "2017-06-15") (t/date "2017-06-25"))
                        (ti/new-interval (t/date "2017-07-01") (t/date "2017-07-10"))])))))
  (testing "Unite containing intervals"
    (is
      (=
        [(ti/new-interval (t/date "2017-06-15") (t/date "2017-06-25"))]
        (map ti/bounds
             (ti/unite [(ti/new-interval (t/date "2017-06-15") (t/date "2017-06-25"))
                        (ti/new-interval (t/date "2017-06-17") (t/date "2017-06-20"))])))))

(deftest union-test
  (testing "counts"
    (let [coll1 [(ti/new-interval (t/instant "2017-07-30T09:00:00Z")
                                  (t/instant "2017-07-30T12:00:00Z"))]
          coll2 [(ti/new-interval (t/instant "2017-07-30T11:00:00Z")
                                  (t/instant "2017-07-30T15:00:00Z"))]
          coll3 [(ti/new-interval (t/instant "2017-07-30T17:00:00Z")
                                  (t/instant "2017-07-30T19:00:00Z"))]]
      (is (= 1 (count (ti/union coll1 coll2))))
      (is (ti/ordered-disjoint-intervals? (ti/union coll1 coll2)))
      (is (= 2 (count (ti/union coll1 coll2 coll3))))
      (is (ti/ordered-disjoint-intervals? (ti/union coll1 coll2 coll3)))))

  (testing "union"
    (let [ival1 (ti/new-interval (t/instant "2017-07-30T09:00:00Z")
                                 (t/instant "2017-07-30T10:00:00Z"))
          ival2 (ti/new-interval (t/instant "2017-07-30T10:00:00Z")
                                 (t/instant "2017-07-30T11:00:00Z"))
          ival3 (ti/new-interval (t/instant "2017-07-30T11:00:00Z")
                                 (t/instant "2017-07-30T12:00:00Z"))
          ival4  (ti/new-interval (t/instant "2017-07-30T12:00:00Z")
                                  (t/instant "2017-07-30T13:00:00Z"))
          ival5 (ti/new-interval (t/instant "2017-07-30T13:00:00Z")
                                 (t/instant "2017-07-30T14:00:00Z"))
          res (ti/union [ival2 ival4] [ival1 ival3 ival5])]
      (is (= res [ival1 ival2 ival3 ival4 ival5])))))

(deftest intersection-test
  (let [coll1 [(ti/new-interval (t/instant "2017-01-01T06:00:00Z")
                                (t/instant "2017-01-01T07:00:00Z"))

               (ti/new-interval (t/instant "2017-01-01T14:00:00Z")
                                (t/instant "2017-01-01T18:00:00Z"))]]
    (is
      (= [(ti/new-interval (t/instant "2017-01-01T09:00:00Z") (t/instant "2017-01-01T10:00:00Z"))
          (ti/new-interval (t/instant "2017-01-01T11:00:00Z") (t/instant "2017-01-01T12:00:00Z"))
          (ti/new-interval (t/instant "2017-01-01T14:00:00Z") (t/instant "2017-01-01T15:00:00Z"))
          (ti/new-interval (t/instant "2017-01-01T17:00:00Z") (t/instant "2017-01-01T18:00:00Z"))]
         (ti/intersection coll1 coll2))))

    (is
      (= [(ti/new-interval (t/instant "2017-01-01T09:00:00Z") (t/instant "2017-01-01T11:00:00Z"))
          (ti/new-interval (t/instant "2017-01-01T14:00:00Z") (t/instant "2017-01-01T16:00:00Z"))]
         (ti/intersection coll1 coll2))))

  (let [coll1 [(ti/new-interval (t/instant "2017-01-01T08:00:00Z")
                                (t/instant "2017-01-01T12:00:00Z"))
               (ti/new-interval (t/instant "2017-01-01T14:00:00Z")
                                (t/instant "2017-01-01T16:00:00Z"))]
        coll2 [(ti/new-interval (t/instant "2017-01-01T08:00:00Z")
                                (t/instant "2017-01-01T12:00:00Z"))]]
    (is
      (=
        [(ti/new-interval (t/instant "2017-01-01T08:00:00Z")
                          (t/instant "2017-01-01T12:00:00Z"))]
        (ti/intersection coll1 coll2))))

    (is (=
          [(ti/new-interval (t/instant "2017-01-01T08:00:00Z")
                            (t/instant "2017-01-01T12:00:00Z"))
           (ti/new-interval (t/instant "2017-01-01T17:00:00Z")
                            (t/instant "2017-01-01T18:00:00Z"))]
          (ti/intersection coll1 coll2))))

  (let [coll1 [(ti/new-interval (t/instant "2017-01-01T12:00:00Z")
                                (t/instant "2017-01-01T14:00:00Z"))]
        coll2 [(ti/new-interval (t/instant "2017-01-01T11:00:00Z")
                                (t/instant "2017-01-01T14:00:00Z"))]]
    (is (= [(ti/new-interval (t/instant "2017-01-01T12:00:00Z")
                             (t/instant "2017-01-01T14:00:00Z"))]
           (ti/intersection coll1 coll2))))

  (let [coll1 [(ti/new-interval (t/date-time "2017-04-11T00:00")
                                (t/date-time "2017-04-14T00:00"))
               (ti/new-interval (t/date-time "2017-04-18T00:00")
                                (t/date-time "2017-04-20T00:00"))
               (ti/new-interval (t/date-time "2017-12-20T00:00")
                                (t/date-time "2017-12-23T00:00"))
               (ti/new-interval (t/date-time "2017-12-27T00:00")
                                (t/date-time "2018-01-01T00:00"))
               (ti/new-interval (t/date-time "2018-01-02T00:00")
                                (t/date-time "2018-01-08T00:00"))]
        coll2 [(ti/bounds (t/year "2017"))]]
    (is (= [(ti/new-interval (t/date-time "2017-04-11T00:00")
                             (t/date-time "2017-04-14T00:00"))
            (ti/new-interval (t/date-time "2017-04-18T00:00")
                             (t/date-time "2017-04-20T00:00"))
            (ti/new-interval (t/date-time "2017-12-20T00:00")
                             (t/date-time "2017-12-23T00:00"))
            (ti/new-interval (t/date-time "2017-12-27T00:00")
                             (t/date-time "2018-01-01T00:00"))]
           (ti/intersection coll1 coll2))))

  (let [coll1 [(ti/new-interval (t/date-time "2017-04-11T00:00")
                                (t/date-time "2017-04-14T00:00"))
               (ti/new-interval (t/date-time "2017-04-18T00:00")
                                (t/date-time "2017-04-20T00:00"))
               (ti/new-interval (t/date-time "2017-12-20T00:00")
                                (t/date-time "2017-12-23T00:00"))
               (ti/new-interval (t/date-time "2017-12-27T00:00")
                                (t/date-time "2018-01-01T00:00"))
               (ti/new-interval (t/date-time "2018-01-02T00:00")
                                (t/date-time "2018-01-08T00:00"))]
        coll2 [(ti/bounds (t/year "2017"))]]
    (is (= [(ti/new-interval (t/date-time "2017-04-11T00:00")
                             (t/date-time "2017-04-14T00:00"))
            (ti/new-interval (t/date-time "2017-04-18T00:00")
                             (t/date-time "2017-04-20T00:00"))
            (ti/new-interval (t/date-time "2017-12-20T00:00")
                             (t/date-time "2017-12-23T00:00"))
            (ti/new-interval (t/date-time "2017-12-27T00:00")
                             (t/date-time "2018-01-01T00:00"))]
           (ti/intersection coll1 coll2)))

                   (ti/new-interval (t/instant "2017-01-01T14:00:00Z")
                                    (t/instant "2017-01-01T18:00:00Z"))]]
        (is
          (empty? (ti/intersection coll1 coll2)))
        (is
          (empty? (ti/intersection coll2 coll1)))
        (is
          (empty? (ti/intersection [] [])))))))

        coll2 [(ti/new-interval (t/instant "2017-01-01T09:00:00Z")
                                (t/instant "2017-01-01T11:00:00Z"))
               (ti/new-interval (t/instant "2017-01-01T13:00:00Z")
                                (t/instant "2017-01-01T17:00:00Z"))]]
    (is
      (thrown?
        #?(:clj clojure.lang.ExceptionInfo
           :cljs ExceptionInfo)
        (ti/difference coll1 coll2)))))


;; Division test

(deftest division-test
  (is (= 7 (count (ti/divide (ti/bounds (t/year 2012) (t/year 2018)) t/year)))))

  (testing "O"
    (is
      (=
        {(t/year 2015) [(ti/bounds (t/year-month "2015-06") (t/year-month "2015-12"))]
         (t/year 2016) [(ti/bounds (t/year 2016))]
         (t/year 2017) [(ti/bounds (t/year-month "2017-01") (t/year-month "2017-06"))]}
        (ti/group-by
          (ti/divide (ti/bounds (t/year 2014) (t/year 2018)) t/year)
          [(ti/bounds (t/year-month "2015-06") (t/year-month "2017-06"))]))))

  (testing "f"
    (is
      (=
        {(t/year 2015) [(ti/bounds (t/year-month "2015-06") (t/year-month "2015-12"))]}
        (ti/group-by
          [(t/year 2014) (t/year 2015) (t/year 2016)]
          [(ti/bounds (t/year-month "2015-06") (t/year-month "2015-12"))]))))

  (testing "F"
    (is
      (=
        {(ti/bounds (t/year-month "2015-06") (t/year-month "2015-12"))
         [(ti/bounds (t/year-month "2015-06") (t/year-month "2015-12"))]}
        (ti/group-by
          [(ti/bounds (t/year-month "2015-06") (t/year-month "2015-12"))]
          [(t/year 2014) (t/year 2015) (t/year 2016)]))))

  (testing "d"
    (is
      (=
        {(t/year 2015) [(ti/bounds (t/year-month "2015-03") (t/year-month "2015-09"))]}
        (ti/group-by
          [(t/year 2014) (t/year 2015) (t/year 2016)]
          [(ti/bounds (t/year-month "2015-03") (t/year-month "2015-09"))]))))

  (testing "D"
    (is
      (=
        {(ti/bounds (t/year-month "2015-03") (t/year-month "2015-09"))
         [(ti/bounds (t/year-month "2015-03") (t/year-month "2015-09"))]}

        (ti/group-by
          [(ti/bounds (t/year-month "2015-03") (t/year-month "2015-09"))]
          [(t/year 2014) (t/year 2015) (t/year 2016)]))))

  (testing "o"
    (is
      (=
        {(ti/bounds (t/year-month "2015-06") (t/year-month "2017-06"))
         [(ti/bounds (t/year-month "2015-06") (t/year-month "2015-12"))
          (t/year "2016")
          (ti/bounds (t/year-month "2017-01") (t/year-month "2017-06"))]}
        (ti/group-by
          [(ti/bounds (t/year-month "2015-06") (t/year-month "2017-06"))]
          (ti/divide (ti/bounds (t/year 2014) (t/year 2018)) t/year))))))

(deftest group-by-test
  (is
    (= 31
       (count
         (ti/group-by
           t/date
           [(t/date "2015-05-20") (t/year-month "2015-06")])))))

;; TODO: more Interval testing
(deftest successive-intervals-meet
  (doseq [x [(t/date) (t/year) (t/year-month)]]
    (is (ti/meets? (t/end x) (t/beginning (t/inc x))))))

(deftest division-test2
  (is (= 365 (count (ti/divide-by t/date (t/year 2017)))))
  (is (= 12 (count (ti/divide-by t/year-month (t/year 2017)))))
  (is (= 30 (count (ti/divide-by t/date (t/year-month "2017-09")))))
  (is (= (t/date "2017-09-01") (first (ti/divide-by t/date (t/year-month "2017-09")))))
  (is (= (t/date "2017-09-30") (last (ti/divide-by t/date (t/year-month "2017-09")))))
  (is (= 31 (count (ti/divide-by t/date (t/year-month "2017-10")))))
  (is (= 8 (count (ti/divide-by t/date (ti/bounds (t/date "2017-10-03") (t/date "2017-10-10"))))))
  (is (= [(t/date "2017-09-10")] (ti/divide-by t/date (ti/bounds (t/date-time "2017-09-10T12:00")
                                                        (t/date-time "2017-09-10T14:00")))))
  (is (= [(t/date "2017-09-10") (t/date "2017-09-11")] (ti/divide-by t/date (ti/bounds (t/date-time "2017-09-10T12:00") (t/date-time "2017-09-11T14:00")))))
  (is (= 2 (count (ti/divide-by t/year-month (ti/bounds (t/date "2017-09-10") (t/date "2017-10-10"))))))
  (is (= 3 (count (ti/divide-by t/year (ti/bounds (t/date-time "2017-09-10T12:00") (t/year "2019"))))))
  (is (= 3 (count (ti/divide-by t/year (ti/bounds (t/date-time "2017-09-10T12:00") (t/year-month "2019-02"))))))
  (is (= 24 (count (ti/divide-by (t/new-duration 1 :hours) (t/date "2017-09-10"))))))

;; TODO: Divide by duration

;; Concur test

(deftest concur-test2
  (is
    (= 2
      (t/hours
        (t/duration
          (ti/concur (ti/new-interval (t/at (t/today) "16:00")
                      (t/end (t/today)))
            (t/today)
            (ti/new-interval (t/at (t/today) "20:00")
              (t/at (t/today) "22:00"))))))))

(defn moment [t]
  (ti/new-interval
    t
    (t/>> t (t/new-duration 3 :seconds))))

;; TODO: Think about conversions between single instants and intervals. Feather? Widen? Smudge?