2222 ([test msg] `(when-not ~test (fail ~msg)))
2323 ([test] `(verify ~test ~(str " failed: " (pr-str test)))))
2424
25- (defmacro ^:private divisible?
25+ (defn- divisible?
2626 [num div]
27- ` (zero? (mod ~ num ~ div)))
27+ (zero? (mod num div)))
2828
29- (defmacro ^:private indivisible?
29+ (defn- indivisible?
3030 [num div]
31- ` (not (divisible? ~ num ~ div)))
31+ (not (divisible? num div)))
3232
3333
3434; ;; ------------------------------------------------------------------------
@@ -52,13 +52,13 @@ The function new-instant is called with the following arguments.
5252
5353 min max default
5454 --- ------------ -------
55- years 0 9'999 N/A (s must provide years)
55+ years 0 9999 N/A (s must provide years)
5656 months 1 12 1
5757 days 1 31 1 (actual max days depends
5858 hours 0 23 0 on month and year)
5959 minutes 0 59 0
6060 seconds 0 60 0 (though 60 is only valid
61- nanoseconds 0 999'999'999 0 when minutes is 59)
61+ nanoseconds 0 999999999 0 when minutes is 59)
6262 offset-sign -1 1 0
6363 offset-hours 0 23 0
6464 offset-minutes 0 59 0
@@ -88,10 +88,9 @@ Grammar (of s):
8888
8989Unlike RFC3339:
9090
91- - we only consdier timestamp (was 'date-time')
92- (removed: 'full-time', 'full-date')
91+ - we only parse the timestamp format
9392 - timestamp can elide trailing components
94- - time-offset is optional
93+ - time-offset is optional (defaults to +00:00)
9594
9695Though time-offset is syntactically optional, a missing time-offset
9796will be treated as if the time-offset zero (+00:00) had been
@@ -158,52 +157,78 @@ with invalid arguments."
158157; ;; ------------------------------------------------------------------------
159158; ;; print integration
160159
161- (defn- fixup-offset
162- [^String s]
163- (let [x (- (count s) 3 )]
164- (str (.substring s 0 x) " :" (.substring s x))))
165-
166- (defn- caldate->rfc3339
167- " format java.util.Date or java.util.Calendar as RFC3339 timestamp."
168- [d]
169- (fixup-offset (format " #inst \" %1$tFT%1$tT.%1$tL%1$tz\" " d)))
160+ (def ^:private thread-local-utc-date-format
161+ ; ; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access.
162+ ; ; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335
163+ (proxy [ThreadLocal] []
164+ (initialValue []
165+ (doto (java.text.SimpleDateFormat. " yyyy-MM-dd'T'HH:mm:ss.SSS-00:00" )
166+ ; ; RFC3339 says to use -00:00 when the timezone is unknown (+00:00 implies a known GMT)
167+ (.setTimeZone (java.util.TimeZone/getTimeZone " GMT" ))))))
168+
169+ (defn- print-date
170+ " Print a java.util.Date as RFC3339 timestamp, always in UTC."
171+ [^java.util.Date d, ^java.io.Writer w]
172+ (let [utc-format (.get thread-local-utc-date-format)]
173+ (.write w " #inst \" " )
174+ (.write w (.format utc-format d))
175+ (.write w " \" " )))
170176
171177(defmethod print-method java.util.Date
172178 [^java.util.Date d, ^java.io.Writer w]
173- (.write w ( caldate->rfc3339 d) ))
179+ (print-date d w ))
174180
175181(defmethod print-dup java.util.Date
176182 [^java.util.Date d, ^java.io.Writer w]
177- (.write w (caldate->rfc3339 d)))
183+ (print-date d w))
184+
185+ (defn- print-calendar
186+ " Print a java.util.Calendar as RFC3339 timestamp, preserving timezone."
187+ [^java.util.Calendar c, ^java.io.Writer w]
188+ (let [calstr (format " %1$tFT%1$tT.%1$tL%1$tz" c)
189+ offset-minutes (- (.length calstr) 2 )]
190+ ; ; calstr is almost right, but is missing the colon in the offset
191+ (.write w " #inst \" " )
192+ (.write w calstr 0 offset-minutes)
193+ (.write w " :" )
194+ (.write w calstr offset-minutes 2 )
195+ (.write w " \" " )))
178196
179197(defmethod print-method java.util.Calendar
180198 [^java.util.Calendar c, ^java.io.Writer w]
181- (.write w ( caldate->rfc3339 c) ))
199+ (print-calendar c w ))
182200
183201(defmethod print-dup java.util.Calendar
184202 [^java.util.Calendar c, ^java.io.Writer w]
185- (.write w (caldate->rfc3339 c)))
186-
187- (defn- fixup-nanos ; 0123456789012345678901234567890123456
188- [^long nanos ^String s] ; #@2011-01-01T01:00:00.000000000+01:00
189- (str (.substring s 0 22 )
190- (format " %09d" nanos)
191- (.substring s 31 )))
192-
193- (defn- timestamp->rfc3339
194- [^java.sql.Timestamp ts]
195- (->> ts
196- (format " #inst \" %1$tFT%1$tT.%1$tN%1$tz\" " ) ; %1$tN prints 9 digits for frac.
197- fixup-offset ; second, but last 6 are always
198- (fixup-nanos (.getNanos ts)))) ; 0 though timestamp has getNanos
203+ (print-calendar c w))
204+
205+
206+ (def ^:private thread-local-utc-timestamp-format
207+ ; ; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access.
208+ ; ; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335
209+ (proxy [ThreadLocal] []
210+ (initialValue []
211+ (doto (java.text.SimpleDateFormat. " yyyy-MM-dd'T'HH:mm:ss" )
212+ (.setTimeZone (java.util.TimeZone/getTimeZone " GMT" ))))))
213+
214+ (defn- print-timestamp
215+ " Print a java.sql.Timestamp as RFC3339 timestamp, always in UTC."
216+ [^java.sql.Timestamp ts, ^java.io.Writer w]
217+ (let [utc-format (.get thread-local-utc-timestamp-format)]
218+ (.write w " #inst \" " )
219+ (.write w (.format utc-format ts))
220+ ; ; add on nanos and offset
221+ ; ; RFC3339 says to use -00:00 when the timezone is unknown (+00:00 implies a known GMT)
222+ (.write w (format " .%09d-00:00" (.getNanos ts)))
223+ (.write w " \" " )))
199224
200225(defmethod print-method java.sql.Timestamp
201- [^java.sql.Timestamp t , ^java.io.Writer w]
202- (.write w ( timestamp->rfc3339 t) ))
226+ [^java.sql.Timestamp ts , ^java.io.Writer w]
227+ (print-timestamp ts w ))
203228
204229(defmethod print-dup java.sql.Timestamp
205- [^java.sql.Timestamp t , ^java.io.Writer w]
206- (.write w ( timestamp->rfc3339 t) ))
230+ [^java.sql.Timestamp ts , ^java.io.Writer w]
231+ (print-timestamp ts w ))
207232
208233
209234; ;; ------------------------------------------------------------------------
@@ -216,7 +241,7 @@ offset, but truncating the subsecond fraction to milliseconds."
216241 [years months days hours minutes seconds nanoseconds
217242 offset-sign offset-hours offset-minutes]
218243 (doto (GregorianCalendar. years (dec months) days hours minutes seconds)
219- (.set Calendar/MILLISECOND (/ nanoseconds 1000000 ))
244+ (.set Calendar/MILLISECOND (quot nanoseconds 1000000 ))
220245 (.setTimeZone (TimeZone/getTimeZone
221246 (format " GMT%s%02d:%02d"
222247 (if (neg? offset-sign) " -" " +" )
@@ -238,23 +263,27 @@ milliseconds since the epoch, GMT."
238263 (doto (Timestamp.
239264 (.getTimeInMillis
240265 (construct-calendar years months days
241- hours minutes seconds nanoseconds
266+ hours minutes seconds 0
242267 offset-sign offset-hours offset-minutes)))
268+ ; ; nanos must be set separately, pass 0 above for the base calendar
243269 (.setNanos nanoseconds)))
244270
245271(def read-instant-date
246- " Bind this to *instant-reader* to read instants as java.util.Date."
247- (partial parse-timestamp (validated construct-date)))
272+ " To read an instant as a java.util.Date, bind *data-readers* to a map with
273+ this var as the value for the 'inst key. The timezone offset will be used
274+ to convert into UTC."
275+ (partial parse-timestamp (validated construct-date)))
248276
249277(def read-instant-calendar
250- " Bind this to *instant-reader * to read instants as java.util.Calendar.
251- Calendar preserves the timezone offset originally used in the date
252- literal as written ."
253- (partial parse-timestamp (validated construct-calendar)))
278+ " To read an instant as a java.util.Calendar, bind *data-readers * to a map with
279+ this var as the value for the 'inst key. Calendar preserves the timezone
280+ offset ."
281+ (partial parse-timestamp (validated construct-calendar)))
254282
255283(def read-instant-timestamp
256- " Bind this to *instant-reader* to read instants as
257- java.sql.Timestamp. Timestamp preserves fractional seconds with
258- nanosecond precision."
259- (partial parse-timestamp (validated construct-timestamp)))
284+ " To read an instant as a java.sql.Timestamp, bind *data-readers* to a
285+ map with this var as the value for the 'inst key. Timestamp preserves
286+ fractional seconds with nanosecond precision. The timezone offset will
287+ be used to convert into UTC."
288+ (partial parse-timestamp (validated construct-timestamp)))
260289
0 commit comments