Observed behaviour
(require '[tick.core :as t])
(t/of-seconds -1) ;=> #time/duration "PT-1S"
(t/nanos (t/of-seconds -1)) ;=> -1000000000 ✓
(t/millis (t/of-seconds -1)) ;=> -1000 ✓
(t/micros (t/of-seconds -1)) ;=> 18446744072709551 ✗
(t/micros (t/of-micros -42)) ;=> 18446744073709509 ✗
Note: The result 18446744072709551 is -1e9 reinterpreted as an unsigned 64-bit integer and then divided.
Root cause
micros is implemented as:
(micros [d] (#?(:clj Long/divideUnsigned :cljs cljs.core//) (p/nanos d) 1000))
This bug has been present from its initial implementation in a5f1703
It's unclear why Long/divideUnsigned was used in the first place - presumably to handle positive durations whose nanosecond count exceeds Long/MAX_VALUE (~ 292 years)? But this silently corrupts any negative duration(which are legitimate and supported by the spec) , producing a large positive number with no indication of error.
Suggested fix
(micros [d]
(clojure.core/+ (* (cljc.java-time.duration/get-seconds d) 1000000)
(quot (cljc.java-time.duration/get-nano d) 1000)))
Incidentally this also fixes another bug in the current impl, where the cljs branch returns a non-truncated value:
(t/micros (t/of-nanos 1234)) ;=> 1 (JVM Clojure)
;=> 1.234 (ClojureScript)
Observed behaviour
Note: The result
18446744072709551is -1e9 reinterpreted as an unsigned 64-bit integer and then divided.Root cause
microsis implemented as:This bug has been present from its initial implementation in a5f1703
It's unclear why
Long/divideUnsignedwas used in the first place - presumably to handle positive durations whose nanosecond count exceedsLong/MAX_VALUE(~ 292 years)? But this silently corrupts any negative duration(which are legitimate and supported by the spec) , producing a large positive number with no indication of error.Suggested fix
Incidentally this also fixes another bug in the current impl, where the cljs branch returns a non-truncated value: