   (instant [zdt] (.toInstant zdt))
   (zone [zdt] (.getZone zdt)))
 
-(defprotocol IDuration
-  (nanos [_] "Return the given quantity in nanoseconds.")
-  (micros [_] "Return the given quantity in microseconds.")
-  (millis [_] "Return the given quantity in milliseconds.")
-  (seconds [_] "Return the given quantity in seconds.")
-  (minutes [_] "Return the given quantity in minutes.")
-  (hours [_] "Return the given quantity in hours.")
-  (days [_] "Return the given quantity in days."))
-
-(extend-protocol IDuration
-  Number
-  (nanos [n] (Duration/ofNanos n))
-  (micros [n] (Duration/ofNanos (* n 1000)))
-  (millis [n] (Duration/ofMillis n))
-  (seconds [n] (Duration/ofSeconds n))
-  (minutes [n] (Duration/ofMinutes n))
-  (hours [n] (Duration/ofHours n))
-  (days [n] (Duration/ofDays n))
-
-  Duration
-  (nanos [d] (.toNanos d))
-  (micros [d] (Long/divideUnsigned (nanos d) 1000))
-  (millis [d] (.toMillis d))
-  (seconds [d] (.getSeconds d))
-  (minutes [d] (.toMinutes d))
-  (hours [d] (.toHours d))
-  (days [d] (.toDays d)))
+(defprotocol ITimeComparison
+  (< [x y] "Is x before y?")
+  (<= [x y] "Is x before or at the same time as y?")
+  (> [x y] "Is x after y?")
+  (>= [x y] "Is x after or at the same time as y?"))
+
+(extend-protocol ITimeComparison
+  Instant
+  (< [x y] (.isBefore x y))
+  (<= [x y] (not (.isAfter x y)))
+  (> [x y] (.isAfter x y))
+  (>= [x y] (not (.isBefore x y)))
+
+  LocalDateTime
+  (< [x y] (.isBefore x y))
+  (<= [x y] (not (.isAfter x y)))
+  (> [x y] (.isAfter x y))
+  (>= [x y] (not (.isBefore x y)))
+
+  LocalTime
+  (< [x y] (.isBefore x y))
+  (<= [x y] (not (.isAfter x y)))
+  (> [x y] (.isAfter x y))
+  (>= [x y] (not (.isBefore x y)))
+
+  LocalDate
+  (< [x y] (.isBefore x y))
+  (<= [x y] (not (.isAfter x y)))
+  (> [x y] (.isAfter x y))
+  (>= [x y] (not (.isBefore x y)))
+
+  OffsetDateTime
+  (< [x y] (.isBefore x y))
+  (<= [x y] (not (.isAfter x y)))
+  (> [x y] (.isAfter x y))
+  (>= [x y] (not (.isBefore x y)))
+
+  ZonedDateTime
+  (< [x y] (.isBefore x y))
+  (<= [x y] (not (.isAfter x y)))
+  (> [x y] (.isAfter x y))
+  (>= [x y] (not (.isBefore x y)))
+
+  YearMonth
+  (< [x y] (.isBefore x y))
+  (<= [x y] (not (.isAfter x y)))
+  (> [x y] (.isAfter x y))
+  (>= [x y] (not (.isBefore x y)))
+
+  Year
+  (< [x y] (.isBefore x y))
+  (<= [x y] (not (.isAfter x y)))
+  (> [x y] (.isAfter x y))
+  (>= [x y] (not (.isBefore x y))))
+
+;; Units
 
 (def unit-map
   {:nanos ChronoUnit/NANOS

    :months (Period/ofMonths n)
    :years (Period/ofYears n)))

;; Units

(def reverse-unit-map (into {} (map vec (map reverse unit-map))))

(defn units [x]
  (into {}
        (for [tu (.getUnits x)
              :let [k (reverse-unit-map tu)]
              :when k]
          [k (.get x tu)])))

;; Between

(defprotocol ITimeBetween
  (between [t1 t2] "Return the most appropriate type of value that
  represents the gap between two times"))

(extend-protocol ITimeBetween
  Instant
  (between [inst _] (throw (ex-info "TODO" {}))))

(defprotocol ITimeComparison
  (< [x y] "Is x before y?")
  (<= [x y] "Is x before or at the same time as y?")
  (> [x y] "Is x after y?")
  (>= [x y] "Is x after or at the same time as y?"))

(extend-protocol ITimeComparison
  Instant
  (< [x y] (.isBefore x y))
  (<= [x y] (not (.isAfter x y)))
  (> [x y] (.isAfter x y))
  (>= [x y] (not (.isBefore x y)))

  LocalDateTime
  (< [x y] (.isBefore x y))
  (<= [x y] (not (.isAfter x y)))
  (> [x y] (.isAfter x y))
  (>= [x y] (not (.isBefore x y)))

  LocalTime
  (< [x y] (.isBefore x y))
  (<= [x y] (not (.isAfter x y)))
  (> [x y] (.isAfter x y))
  (>= [x y] (not (.isBefore x y)))

  LocalDate
  (< [x y] (.isBefore x y))
  (<= [x y] (not (.isAfter x y)))
  (> [x y] (.isAfter x y))
  (>= [x y] (not (.isBefore x y)))

  OffsetDateTime
  (< [x y] (.isBefore x y))
  (<= [x y] (not (.isAfter x y)))
  (> [x y] (.isAfter x y))
  (>= [x y] (not (.isBefore x y)))

  ZonedDateTime
  (< [x y] (.isBefore x y))
  (<= [x y] (not (.isAfter x y)))
  (> [x y] (.isAfter x y))
  (>= [x y] (not (.isBefore x y)))

  YearMonth
  (< [x y] (.isBefore x y))
  (<= [x y] (not (.isAfter x y)))
  (> [x y] (.isAfter x y))
  (>= [x y] (not (.isBefore x y)))

  Year
  (< [x y] (.isBefore x y))
  (<= [x y] (not (.isAfter x y)))
  (> [x y] (.isAfter x y))
  (>= [x y] (not (.isBefore x y))))

(defprotocol ITimeRangeable
  (range [_] [_ _] [_ _ _] "Returns a lazy seq of times from start (inclusive) to end (exclusive, nil means forever), by step, where start defaults to 0, step to 1, and end to infinity."))

(defprotocol IDivisible
  (/ [_ _] "Divide time"))

(extend-protocol IDivisible
  String
  (/ [s d] (/ (parse s) d)))

(defprotocol ITimeArithmetic
  (+ [_ _] "Add time")
  (- [_ _] "Subtract time"))

(defprotocol ITimeSpan
  (beginning [_] "Return the beginning of a span of time")
  (end [_] "Return the end of a span of time"))

(defprotocol ITimeBetween
  (between [t1 t2] "Return the most appropriate type of value that
  represents the gap between two times"))

(extend-protocol ITimeBetween
  Instant
  (between [inst _] (throw (ex-info "TODO" {}))))

(defn length
  "Return the distance between the beginning and end as a duration or
