Skip to content

Instantly share code, notes, and snippets.

@ivanpierre
Last active December 12, 2016 21:41
Show Gist options
  • Save ivanpierre/b0ea937dac97d910a7c3c1e5774028e0 to your computer and use it in GitHub Desktop.
Save ivanpierre/b0ea937dac97d910a7c3c1e5774028e0 to your computer and use it in GitHub Desktop.
Just enough TimeStamp
; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
; which can be found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
; You must not remove this notice, or any other, from this software.
(ns clojure.instant
(:import [java.util Calendar Date GregorianCalendar TimeZone]
[clojure.lang TimeStamp]))
(set! *warn-on-reflection* true)
;;; ------------------------------------------------------------------------
;;; convenience macros
(defmacro ^:private fail
[msg]
`(throw (RuntimeException. ~msg)))
(defmacro ^:private verify
([test msg] `(when-not ~test (fail ~msg)))
([test] `(verify ~test ~(str "failed: " (pr-str test)))))
(defn- divisible?
[num div]
(zero? (mod num div)))
(defn- indivisible?
[num div]
(not (divisible? num div)))
;;; ------------------------------------------------------------------------
;;; parser implementation
(defn- parse-int [^String s]
(Long/parseLong s))
(defn- zero-fill-right [^String s width]
(cond (= width (count s)) s
(< width (count s)) (.substring s 0 width)
:else (loop [b (StringBuilder. s)]
(if (< (.length b) width)
(recur (.append b \0))
(.toString b)))))
(def parse-timestamp
"Parse a string containing an RFC3339-like like timestamp.
The function new-instant is called with the following arguments.
min max default
--- ------------ -------
years 0 9999 N/A (s must provide years)
months 1 12 1
days 1 31 1 (actual max days depends
hours 0 23 0 on month and year)
minutes 0 59 0
seconds 0 60 0 (though 60 is only valid
nanoseconds 0 999999999 0 when minutes is 59)
offset-sign -1 1 0
offset-hours 0 23 0
offset-minutes 0 59 0
These are all integers and will be non-nil. (The listed defaults
will be passed if the corresponding field is not present in s.)
Grammar (of s):
date-fullyear = 4DIGIT
date-month = 2DIGIT ; 01-12
date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
; month/year
time-hour = 2DIGIT ; 00-23
time-minute = 2DIGIT ; 00-59
time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
; rules
time-secfrac = '.' 1*DIGIT
time-numoffset = ('+' / '-') time-hour ':' time-minute
time-offset = 'Z' / time-numoffset
time-part = time-hour [ ':' time-minute [ ':' time-second
[time-secfrac] [time-offset] ] ]
timestamp = date-year [ '-' date-month [ '-' date-mday
[ 'T' time-part ] ] ]
Unlike RFC3339:
- we only parse the timestamp format
- timestamp can elide trailing components
- time-offset is optional (defaults to +00:00)
Though time-offset is syntactically optional, a missing time-offset
will be treated as if the time-offset zero (+00:00) had been
specified.
"
(let [timestamp #"(\d\d\d\d)(?:-(\d\d)(?:-(\d\d)(?:[T](\d\d)(?::(\d\d)(?::(\d\d)(?:[.](\d+))?)?)?)?)?)?(?:[Z]|([-+])(\d\d):(\d\d))?"]
(fn [new-instant ^CharSequence cs]
(if-let [[_ years months days hours minutes seconds fraction
offset-sign offset-hours offset-minutes]
(re-matches timestamp cs)]
(new-instant
(parse-int years)
(if-not months 1 (parse-int months))
(if-not days 1 (parse-int days))
(if-not hours 0 (parse-int hours))
(if-not minutes 0 (parse-int minutes))
(if-not seconds 0 (parse-int seconds))
(if-not fraction 0 (parse-int (zero-fill-right fraction 9)))
(cond (= "-" offset-sign) -1
(= "+" offset-sign) 1
:else 0)
(if-not offset-hours 0 (parse-int offset-hours))
(if-not offset-minutes 0 (parse-int offset-minutes)))
(fail (str "Unrecognized date/time syntax: " cs))))))
;;; ------------------------------------------------------------------------
;;; Verification of Extra-Grammatical Restrictions from RFC3339
(defn- leap-year?
[year]
(and (divisible? year 4)
(or (indivisible? year 100)
(divisible? year 400))))
(def ^:private days-in-month
(let [dim-norm [nil 31 28 31 30 31 30 31 31 30 31 30 31]
dim-leap [nil 31 29 31 30 31 30 31 31 30 31 30 31]]
(fn [month leap-year?]
((if leap-year? dim-leap dim-norm) month))))
(defn validated
"Return a function which constructs and instant by calling constructor
after first validating that those arguments are in range and otherwise
plausible. The resulting function will throw an exception if called
with invalid arguments."
[new-instance]
(fn [years months days hours minutes seconds nanoseconds
offset-sign offset-hours offset-minutes]
(verify (<= 1 months 12))
(verify (<= 1 days (days-in-month months (leap-year? years))))
(verify (<= 0 hours 23))
(verify (<= 0 minutes 59))
(verify (<= 0 seconds (if (= minutes 59) 60 59)))
(verify (<= 0 nanoseconds 999999999))
(verify (<= -1 offset-sign 1))
(verify (<= 0 offset-hours 23))
(verify (<= 0 offset-minutes 59))
(new-instance years months days hours minutes seconds nanoseconds
offset-sign offset-hours offset-minutes)))
;;; ------------------------------------------------------------------------
;;; print integration
(def ^:private ^ThreadLocal thread-local-utc-date-format
;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access.
;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335
(proxy [ThreadLocal] []
(initialValue []
(doto (java.text.SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ss.SSS-00:00")
;; RFC3339 says to use -00:00 when the timezone is unknown (+00:00 implies a known GMT)
(.setTimeZone (java.util.TimeZone/getTimeZone "GMT"))))))
(defn- print-date
"Print a java.util.Date as RFC3339 timestamp, always in UTC."
[^java.util.Date d, ^java.io.Writer w]
(let [^java.text.DateFormat utc-format (.get thread-local-utc-date-format)]
(.write w "#inst \"")
(.write w (.format utc-format d))
(.write w "\"")))
(defmethod print-method java.util.Date
[^java.util.Date d, ^java.io.Writer w]
(print-date d w))
(defmethod print-dup java.util.Date
[^java.util.Date d, ^java.io.Writer w]
(print-date d w))
(defn- print-calendar
"Print a java.util.Calendar as RFC3339 timestamp, preserving timezone."
[^java.util.Calendar c, ^java.io.Writer w]
(let [calstr (format "%1$tFT%1$tT.%1$tL%1$tz" c)
offset-minutes (- (.length calstr) 2)]
;; calstr is almost right, but is missing the colon in the offset
(.write w "#inst \"")
(.write w calstr 0 offset-minutes)
(.write w ":")
(.write w calstr offset-minutes 2)
(.write w "\"")))
(defmethod print-method java.util.Calendar
[^java.util.Calendar c, ^java.io.Writer w]
(print-calendar c w))
(defmethod print-dup java.util.Calendar
[^java.util.Calendar c, ^java.io.Writer w]
(print-calendar c w))
(def ^:private ^ThreadLocal thread-local-utc-timestamp-format
;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access.
;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335
(proxy [ThreadLocal] []
(initialValue []
(doto (java.text.SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ss")
(.setTimeZone (java.util.TimeZone/getTimeZone "GMT"))))))
(defn- print-timestamp
"Print a clojure.lang.TimeStamp as RFC3339 timestamp, always in UTC."
[^clojure.lang.TimeStamp ts, ^java.io.Writer w]
(let [^java.text.DateFormat utc-format (.get thread-local-utc-timestamp-format)]
(.write w "#inst \"")
(.write w (.format utc-format ts))
;; add on nanos and offset
;; RFC3339 says to use -00:00 when the timezone is unknown (+00:00 implies a known GMT)
(.write w (format ".%09d-00:00" (.getNanos ts)))
(.write w "\"")))
(defmethod print-method clojure.lang.TimeStamp
[^clojure.lang.TimeStamp ts, ^java.io.Writer w]
(print-timestamp ts w))
(defmethod print-dup clojure.lang.TimeStamp
[^clojure.lang.TimeStamp ts, ^java.io.Writer w]
(print-timestamp ts w))
;;; ------------------------------------------------------------------------
;;; reader integration
(defn- construct-calendar
"Construct a java.util.Calendar, preserving the timezone
offset, but truncating the subsecond fraction to milliseconds."
^GregorianCalendar
[years months days hours minutes seconds nanoseconds
offset-sign offset-hours offset-minutes]
(doto (GregorianCalendar. years (dec months) days hours minutes seconds)
(.set Calendar/MILLISECOND (quot nanoseconds 1000000))
(.setTimeZone (TimeZone/getTimeZone
(format "GMT%s%02d:%02d"
(if (neg? offset-sign) "-" "+")
offset-hours offset-minutes)))))
(defn- construct-date
"Construct a java.util.Date, which expresses the original instant as
milliseconds since the epoch, UTC."
[years months days hours minutes seconds nanoseconds
offset-sign offset-hours offset-minutes]
(.getTime (construct-calendar years months days
hours minutes seconds nanoseconds
offset-sign offset-hours offset-minutes)))
(defn- construct-timestamp
"Construct a clojure.lang.TimeStamp, which has nanosecond precision."
[years months days hours minutes seconds nanoseconds
offset-sign offset-hours offset-minutes]
(.. (TimeStamp.
(.getTimeInMillis
(construct-calendar years months days
hours minutes seconds 0
offset-sign offset-hours offset-minutes)))
(.setNanos nanoseconds)))
(def read-instant-date
"To read an instant as a java.util.Date, bind *data-readers* to a map with
this var as the value for the 'inst key. The timezone offset will be used
to convert into UTC."
(partial parse-timestamp (validated construct-date)))
(def read-instant-calendar
"To read an instant as a java.util.Calendar, bind *data-readers* to a map with
this var as the value for the 'inst key. Calendar preserves the timezone
offset."
(partial parse-timestamp (validated construct-calendar)))
(def read-instant-timestamp
"To read an instant as a clojure.lang.TimeStamp, bind *data-readers* to a
map with this var as the value for the 'inst key. TimeStamp preserves
fractional seconds with nanosecond precision. The timezone offset will
be used to convert into UTC."
(partial parse-timestamp (validated construct-timestamp)))
/* Time.java -- Wrapper around java.util.Date
Copyright (C) 1999, 2000, 2003, 2004, 2005 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package clojure.lang;
/**
* Used for parsing and formatting this date.
* @deprecated used by deprecated functions
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
*/
/**
* This class is a wrapper around java.util.Date to allow the JDBC
* driver to identify the value as a SQL TimeStamp. Note that this
* class also adds an additional field for nano-seconds, and so
* is not completely identical to <code>java.util.Date</code> as
* the <code>java.sql.Date</code> and <code>java.sql.Time</code>
* classes are.
*
* @author Aaron M. Renn (arenn@urbanophile.com)
*/
public class TimeStamp extends java.util.Date
{
/**
* @serial
*/
static final long serialVersionUID = 1752345689675676457L;
/*
* nanoseconds
*/
private int nanos;
/**
* Used for parsing and formatting this date.
* @deprecated used by deprecated functions
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static DecimalFormat decimalFormat = new DecimalFormat("000000000");
private static StringBuffer sbuf = new StringBuffer(29);
*/
/**
* This method initializes a new instance of this class with the
* specified time value representing the number of milliseconds since
* Jan 1, 1970 at 12:00 midnight GMT.
*
* @param date The time value to intialize this <code>Time</code> to.
*/
public TimeStamp(long date)
{
// first create the TimeStamp
super(date - (date % 1000));
nanos = (int)(date % 1000) * 1000000;
}
/**
* This method returns a new instance of this class by parsing a
* date in JDBC format into a Java date.
*
* @param str The string to parse.
* @return The resulting <code>java.sql.TimeStamp</code> value.
* @deprecated this is managed by clojure.instant
public static TimeStamp valueOf(String str)
{
int nanos = 0;
int dot = str.indexOf('.');
if (dot != -1)
{
if (str.lastIndexOf('.') != dot)
throw new IllegalArgumentException(str);
int len = str.length() - dot - 1;
if (len < 1 || len > 9)
throw new IllegalArgumentException(str);
nanos = Integer.parseInt(str.substring(dot + 1));
for (int i = len; i < 9; i++)
nanos *= 10;
str = str.substring(0, dot);
}
try
{
java.util.Date d;
synchronized (dateFormat)
{
d = (java.util.Date) dateFormat.parseObject(str);
}
if (d == null)
throw new IllegalArgumentException(str);
TimeStamp ts = new TimeStamp(d.getTime() + nanos / 1000000);
ts.nanos = nanos;
return ts;
}
catch (ParseException e)
{
throw new IllegalArgumentException(str);
}
}
*/
/**
* This method returns this date in JDBC format.
*
* @return This date as a string.
* @deprecated this is managed by clojure.instant
public String toString()
{
synchronized (dateFormat)
{
sbuf.setLength(0);
dateFormat.format(this, sbuf, null);
sbuf.append('.');
decimalFormat.format(nanos, sbuf, null);
int end = sbuf.length() - 1;
while (end > 20 && sbuf.charAt(end) == '0')
end--;
return sbuf.substring(0, end + 1);
}
}
*/
/**
* Return the value of this TimeStamp as the number of milliseconds
* since Jan 1, 1970 at 12:00 midnight GMT.
*/
public long getTime() {
return super.getTime() + (nanos / 1000000);
}
// setTime is done by constructor
/**
* This method returns the nanosecond value for this object.
* @return The nanosecond value for this object.
*/
public int getNanos()
{
return nanos;
}
/**
* This method sets the nanosecond value for this object.
*
* @param nanos The nanosecond value for this object. ! it will discard time < 1 sec
*/
public TimeStamp setNanos(int nanos)
{
if (nanos > 999999999 || nanos < 0)
throw new IllegalArgumentException("nanos > 999999999 or < 0");
// Warning values < 1 sec will be disacarded
this.nanos = nanos;
return this;
}
/**
* This method these the specified <code>Object</code> for equality
* against this object. This will be true if an only if the specified
* object is an instance of <code>TimeStamp</code> and has the same
* time value fields.
*
* @param obj The object to test against for equality.
*
* @return <code>true</code> if the specified object is equal to this
* object, <code>false</code> otherwise.
*/
public boolean equals(Object obj)
{
if (!(obj instanceof TimeStamp))
return false;
return equals((TimeStamp) obj);
}
/**
* This method tests the specified timestamp for equality against this
* object. This will be true if and only if the specified object is
* not <code>null</code> and contains all the same time value fields
* as this object.
*
* @param ts The <code>TimeStamp</code> to test against for equality.
*
* @return <code>true</code> if the specified object is equal to this
* object, <code>false</code> otherwise.
*/
public boolean equals(TimeStamp ts)
{
if (ts == null)
return false;
if (ts.getTime() != getTime())
return false;
if (ts.getNanos() != getNanos())
return false;
return true;
}
/**
* Compares this <code>TimeStamp</code> to another one.
*
* @param ts The other TimeStamp.
* @return <code>0</code>, if both <code>TimeStamp</code>'s represent exactly
* the same date, a negative value if this <code>TimeStamp</code> is
* before the specified <code>TimeStamp</code> and a positive value
* otherwise.
* @since 1.2
*/
public int compareTo(TimeStamp ts)
{
int s = super.compareTo((java.util.Date) ts);
if (s != 0)
return s;
// If Date components were equal, then we check the nanoseconds.
return nanos - ts.nanos;
}
/**
* Compares this <code>TimeStamp</code> to another one. This behaves like
* <code>compareTo(TimeStamp)</code>, but it may throw a
* <code>ClassCastException</code>, if the specified object is not of type
* <code>TimeStamp</code>.
*
* @param obj The object to compare with.
* @return <code>0</code>, if both <code>TimeStamp</code>'s represent exactly
* the same date, a negative value if this <code>TimeStamp</code> is
* before the specified <code>TimeStamp</code> and a positive value
* otherwise.
* @exception ClassCastException if obj is not of type TimeStamp.
* @see #compareTo(TimeStamp)
* @since 1.2
*/
public int compareTo(java.util.Date obj)
{
return compareTo((TimeStamp) obj);
}
}
; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
; which can be found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
; You must not remove this notice, or any other, from this software.
; Author: Stephen C. Gilardi
;;
;; Tests for the Clojure functions documented at the URL:
;;
;; http://clojure.org/Reader
;;
;; scgilardi (gmail)
;; Created 22 October 2008
(ns clojure.test-clojure.reader
(:use clojure.test)
(:use [clojure.instant :only [read-instant-date
read-instant-calendar
read-instant-timestamp]])
(:require clojure.walk
[clojure.edn :as edn]
[clojure.test.generative :refer (defspec)]
[clojure.test-clojure.generators :as cgen]
[clojure.edn :as edn])
(:import [clojure.lang BigInt Ratio]
java.io.File
java.util.TimeZone))
;; Symbols
(deftest Symbols
(is (= 'abc (symbol "abc")))
(is (= '*+!-_? (symbol "*+!-_?")))
(is (= 'abc:def:ghi (symbol "abc:def:ghi")))
(is (= 'abc/def (symbol "abc" "def")))
(is (= 'abc.def/ghi (symbol "abc.def" "ghi")))
(is (= 'abc/def.ghi (symbol "abc" "def.ghi")))
(is (= 'abc:def/ghi:jkl.mno (symbol "abc:def" "ghi:jkl.mno")))
(is (instance? clojure.lang.Symbol 'alphabet))
)
;; Literals
(deftest Literals
; 'nil 'false 'true are reserved by Clojure and are not symbols
(is (= 'nil nil))
(is (= 'false false))
(is (= 'true true)) )
;; Strings
(defn temp-file
[prefix suffix]
(doto (File/createTempFile prefix suffix)
(.deleteOnExit)))
(defn read-from
[source file form]
(if (= :string source)
(read-string form)
(do
(spit file form)
(load-file (str file)))))
(defn code-units
[s]
(and (instance? String s) (map int s)))
(deftest Strings
(is (= "abcde" (str \a \b \c \d \e)))
(is (= "abc
def" (str \a \b \c \newline \space \space \d \e \f)))
(let [f (temp-file "clojure.core-reader" "test")]
(doseq [source [:string :file]]
(testing (str "Valid string literals read from " (name source))
(are [x form] (= x (code-units
(read-from source f (str "\"" form "\""))))
[] ""
[34] "\\\""
[10] "\\n"
[0] "\\0"
[0] "\\000"
[3] "\\3"
[3] "\\03"
[3] "\\003"
[0 51] "\\0003"
[3 48] "\\0030"
[0377] "\\377"
[0 56] "\\0008"
[0] "\\u0000"
[0xd7ff] "\\ud7ff"
[0xd800] "\\ud800"
[0xdfff] "\\udfff"
[0xe000] "\\ue000"
[0xffff] "\\uffff"
[4 49] "\\u00041"))
(testing (str "Errors reading string literals from " (name source))
(are [err msg form] (thrown-with-msg? err msg
(read-from source f (str "\"" form "\"")))
Exception #"EOF while reading string" "\\"
Exception #"Unsupported escape character: \\o" "\\o"
Exception #"Octal escape sequence must be in range \[0, 377\]" "\\400"
Exception #"Invalid digit: 8" "\\8"
Exception #"Invalid digit: 8" "\\8000"
Exception #"Invalid digit: 8" "\\0800"
Exception #"Invalid digit: 8" "\\0080"
Exception #"Invalid digit: a" "\\2and"
Exception #"Invalid unicode escape: \\u" "\\u"
Exception #"Invalid unicode escape: \\ug" "\\ug"
Exception #"Invalid unicode escape: \\ug" "\\ug000"
Exception #"Invalid character length: 1, should be: 4" "\\u0"
Exception #"Invalid character length: 3, should be: 4" "\\u004"
Exception #"Invalid digit: g" "\\u004g")))))
;; Numbers
(deftest Numbers
; Read Integer
(is (instance? Long 2147483647))
(is (instance? Long +1))
(is (instance? Long 1))
(is (instance? Long +0))
(is (instance? Long 0))
(is (instance? Long -0))
(is (instance? Long -1))
(is (instance? Long -2147483648))
; Read Long
(is (instance? Long 2147483648))
(is (instance? Long -2147483649))
(is (instance? Long 9223372036854775807))
(is (instance? Long -9223372036854775808))
;; Numeric constants of different types don't wash out. Regression fixed in
;; r1157. Previously the compiler saw 0 and 0.0 as the same constant and
;; caused the sequence to be built of Doubles.
(let [x 0.0]
(let [sequence (loop [i 0 l '()]
(if (< i 5)
(recur (inc i) (conj l i))
l))]
(is (= [4 3 2 1 0] sequence))
(is (every? #(instance? Long %)
sequence))))
; Read BigInteger
(is (instance? BigInt 9223372036854775808))
(is (instance? BigInt -9223372036854775809))
(is (instance? BigInt 10000000000000000000000000000000000000000000000000))
(is (instance? BigInt -10000000000000000000000000000000000000000000000000))
; Read Double
(is (instance? Double +1.0e+1))
(is (instance? Double +1.e+1))
(is (instance? Double +1e+1))
(is (instance? Double +1.0e1))
(is (instance? Double +1.e1))
(is (instance? Double +1e1))
(is (instance? Double +1.0e-1))
(is (instance? Double +1.e-1))
(is (instance? Double +1e-1))
(is (instance? Double 1.0e+1))
(is (instance? Double 1.e+1))
(is (instance? Double 1e+1))
(is (instance? Double 1.0e1))
(is (instance? Double 1.e1))
(is (instance? Double 1e1))
(is (instance? Double 1.0e-1))
(is (instance? Double 1.e-1))
(is (instance? Double 1e-1))
(is (instance? Double -1.0e+1))
(is (instance? Double -1.e+1))
(is (instance? Double -1e+1))
(is (instance? Double -1.0e1))
(is (instance? Double -1.e1))
(is (instance? Double -1e1))
(is (instance? Double -1.0e-1))
(is (instance? Double -1.e-1))
(is (instance? Double -1e-1))
(is (instance? Double +1.0))
(is (instance? Double +1.))
(is (instance? Double 1.0))
(is (instance? Double 1.))
(is (instance? Double +0.0))
(is (instance? Double +0.))
(is (instance? Double 0.0))
(is (instance? Double 0.))
(is (instance? Double -0.0))
(is (instance? Double -0.))
(is (instance? Double -1.0))
(is (instance? Double -1.))
; Read BigDecimal
(is (instance? BigDecimal 9223372036854775808M))
(is (instance? BigDecimal -9223372036854775809M))
(is (instance? BigDecimal 2147483647M))
(is (instance? BigDecimal +1M))
(is (instance? BigDecimal 1M))
(is (instance? BigDecimal +0M))
(is (instance? BigDecimal 0M))
(is (instance? BigDecimal -0M))
(is (instance? BigDecimal -1M))
(is (instance? BigDecimal -2147483648M))
(is (instance? BigDecimal +1.0e+1M))
(is (instance? BigDecimal +1.e+1M))
(is (instance? BigDecimal +1e+1M))
(is (instance? BigDecimal +1.0e1M))
(is (instance? BigDecimal +1.e1M))
(is (instance? BigDecimal +1e1M))
(is (instance? BigDecimal +1.0e-1M))
(is (instance? BigDecimal +1.e-1M))
(is (instance? BigDecimal +1e-1M))
(is (instance? BigDecimal 1.0e+1M))
(is (instance? BigDecimal 1.e+1M))
(is (instance? BigDecimal 1e+1M))
(is (instance? BigDecimal 1.0e1M))
(is (instance? BigDecimal 1.e1M))
(is (instance? BigDecimal 1e1M))
(is (instance? BigDecimal 1.0e-1M))
(is (instance? BigDecimal 1.e-1M))
(is (instance? BigDecimal 1e-1M))
(is (instance? BigDecimal -1.0e+1M))
(is (instance? BigDecimal -1.e+1M))
(is (instance? BigDecimal -1e+1M))
(is (instance? BigDecimal -1.0e1M))
(is (instance? BigDecimal -1.e1M))
(is (instance? BigDecimal -1e1M))
(is (instance? BigDecimal -1.0e-1M))
(is (instance? BigDecimal -1.e-1M))
(is (instance? BigDecimal -1e-1M))
(is (instance? BigDecimal +1.0M))
(is (instance? BigDecimal +1.M))
(is (instance? BigDecimal 1.0M))
(is (instance? BigDecimal 1.M))
(is (instance? BigDecimal +0.0M))
(is (instance? BigDecimal +0.M))
(is (instance? BigDecimal 0.0M))
(is (instance? BigDecimal 0.M))
(is (instance? BigDecimal -0.0M))
(is (instance? BigDecimal -0.M))
(is (instance? BigDecimal -1.0M))
(is (instance? BigDecimal -1.M))
(is (instance? Ratio 1/2))
(is (instance? Ratio -1/2))
(is (instance? Ratio +1/2))
)
;; Characters
(deftest t-Characters
(let [f (temp-file "clojure.core-reader" "test")]
(doseq [source [:string :file]]
(testing (str "Valid char literals read from " (name source))
(are [x form] (= x (read-from source f form))
(first "o") "\\o"
(char 0) "\\o0"
(char 0) "\\o000"
(char 047) "\\o47"
(char 0377) "\\o377"
(first "u") "\\u"
(first "A") "\\u0041"
(char 0) "\\u0000"
(char 0xd7ff) "\\ud7ff"
(char 0xe000) "\\ue000"
(char 0xffff) "\\uffff"))
(testing (str "Errors reading char literals from " (name source))
(are [err msg form] (thrown-with-msg? err msg (read-from source f form))
Exception #"EOF while reading character" "\\"
Exception #"Unsupported character: \\00" "\\00"
Exception #"Unsupported character: \\0009" "\\0009"
Exception #"Invalid digit: 8" "\\o378"
Exception #"Octal escape sequence must be in range \[0, 377\]" "\\o400"
Exception #"Invalid digit: 8" "\\o800"
Exception #"Invalid digit: a" "\\oand"
Exception #"Invalid octal escape sequence length: 4" "\\o0470"
Exception #"Invalid unicode character: \\u0" "\\u0"
Exception #"Invalid unicode character: \\ug" "\\ug"
Exception #"Invalid unicode character: \\u000" "\\u000"
Exception #"Invalid character constant: \\ud800" "\\ud800"
Exception #"Invalid character constant: \\udfff" "\\udfff"
Exception #"Invalid unicode character: \\u004" "\\u004"
Exception #"Invalid unicode character: \\u00041" "\\u00041"
Exception #"Invalid digit: g" "\\u004g")))))
;; nil
(deftest t-nil)
;; Booleans
(deftest t-Booleans)
;; Keywords
(deftest t-Keywords
(is (= :abc (keyword "abc")))
(is (= :abc (keyword 'abc)))
(is (= :*+!-_? (keyword "*+!-_?")))
(is (= :abc:def:ghi (keyword "abc:def:ghi")))
(is (= :abc/def (keyword "abc" "def")))
(is (= :abc/def (keyword 'abc/def)))
(is (= :abc.def/ghi (keyword "abc.def" "ghi")))
(is (= :abc/def.ghi (keyword "abc" "def.ghi")))
(is (= :abc:def/ghi:jkl.mno (keyword "abc:def" "ghi:jkl.mno")))
(is (instance? clojure.lang.Keyword :alphabet))
)
(deftest reading-keywords
(are [x y] (= x (binding [*ns* (the-ns 'user)] (read-string y)))
:foo ":foo"
:foo/bar ":foo/bar"
:user/foo "::foo")
(are [err msg form] (thrown-with-msg? err msg (read-string form))
Exception #"Invalid token: foo:" "foo:"
Exception #"Invalid token: :bar/" ":bar/"
Exception #"Invalid token: ::does.not/exist" "::does.not/exist"))
;; Lists
(deftest t-Lists)
;; Vectors
(deftest t-Vectors)
;; Maps
(deftest t-Maps)
;; Sets
(deftest t-Sets)
;; Macro characters
;; Quote (')
(deftest t-Quote)
;; Character (\)
(deftest t-Character)
;; Comment (;)
(deftest t-Comment)
;; Deref (@)
(deftest t-Deref)
;; Dispatch (#)
;; #{} - see Sets above
;; Regex patterns (#"pattern")
(deftest t-Regex)
;; Metadata (^ or #^ (deprecated))
(deftest t-line-column-numbers
(let [code "(ns reader-metadata-test
(:require [clojure.java.io
:refer (resource reader)]))
(let [a 5]
^:added-metadata
(defn add-5
[x]
(reduce + x (range a))))"
stream (clojure.lang.LineNumberingPushbackReader.
(java.io.StringReader. code))
top-levels (take-while identity (repeatedly #(read stream false nil)))
expected-metadata '{ns {:line 1, :column 1}
:require {:line 2, :column 3}
resource {:line 3, :column 21}
let {:line 5, :column 1}
defn {:line 6, :column 3 :added-metadata true}
reduce {:line 9, :column 5}
range {:line 9, :column 17}}
verified-forms (atom 0)]
(doseq [form top-levels]
(clojure.walk/postwalk
#(when (list? %)
(is (= (expected-metadata (first %))
(meta %)))
(is (->> (meta %)
vals
(filter number?)
(every? (partial instance? Integer))))
(swap! verified-forms inc))
form))
;; sanity check against e.g. reading returning ()
(is (= (count expected-metadata) @verified-forms))))
(deftest set-line-number
(let [r (clojure.lang.LineNumberingPushbackReader. *in*)]
(.setLineNumber r 100)
(is (= 100 (.getLineNumber r)))))
(deftest t-Metadata
(is (= (meta '^:static ^:awesome ^{:static false :bar :baz} sym) {:awesome true, :bar :baz, :static true})))
;; Var-quote (#')
(deftest t-Var-quote)
;; Anonymous function literal (#())
(deftest t-Anonymouns-function-literal)
;; Syntax-quote (`, note, the "backquote" character), Unquote (~) and
;; Unquote-splicing (~@)
(deftest t-Syntax-quote
(are [x y] (= x y)
`() () ; was NPE before SVN r1337
))
;; (read)
;; (read stream)
;; (read stream eof-is-error)
;; (read stream eof-is-error eof-value)
;; (read stream eof-is-error eof-value is-recursive)
(deftest t-read)
(deftest division
(is (= clojure.core// /))
(binding [*ns* *ns*]
(eval '(do (ns foo
(:require [clojure.core :as bar])
(:use [clojure.test]))
(is (= clojure.core// bar//))))))
(deftest Instants
(testing "Instants are read as java.util.Date by default"
(is (= java.util.Date (class #inst "2010-11-12T13:14:15.666"))))
(let [s "#inst \"2010-11-12T13:14:15.666-06:00\""]
(binding [*data-readers* {'inst read-instant-date}]
(testing "read-instant-date produces java.util.Date"
(is (= java.util.Date (class (read-string s)))))
(testing "java.util.Date instants round-trips"
(is (= (-> s read-string)
(-> s read-string pr-str read-string))))
(testing "java.util.Date instants round-trip throughout the year"
(doseq [month (range 1 13) day (range 1 29) hour (range 1 23)]
(let [s (format "#inst \"2010-%02d-%02dT%02d:14:15.666-06:00\"" month day hour)]
(is (= (-> s read-string)
(-> s read-string pr-str read-string))))))
(testing "java.util.Date handling DST in time zones"
(let [dtz (TimeZone/getDefault)]
(try
;; A timezone with DST in effect during 2010-11-12
(TimeZone/setDefault (TimeZone/getTimeZone "Australia/Sydney"))
(is (= (-> s read-string)
(-> s read-string pr-str read-string)))
(finally (TimeZone/setDefault dtz)))))
(testing "java.util.Date should always print in UTC"
(let [d (read-string s)
pstr (print-str d)
len (.length pstr)]
(is (= (subs pstr (- len 7)) "-00:00\"")))))
(binding [*data-readers* {'inst read-instant-calendar}]
(testing "read-instant-calendar produces java.util.Calendar"
(is (instance? java.util.Calendar (read-string s))))
(testing "java.util.Calendar round-trips"
(is (= (-> s read-string)
(-> s read-string pr-str read-string))))
(testing "java.util.Calendar remembers timezone in literal"
(is (= "#inst \"2010-11-12T13:14:15.666-06:00\""
(-> s read-string pr-str)))
(is (= (-> s read-string)
(-> s read-string pr-str read-string))))
(testing "java.util.Calendar preserves milliseconds"
(is (= 666 (-> s read-string
(.get java.util.Calendar/MILLISECOND)))))))
(let [s "#inst \"2010-11-12T13:14:15.123456789\""
s2 "#inst \"2010-11-12T13:14:15.123\""
s3 "#inst \"2010-11-12T13:14:15.123456789123\""]
(binding [*data-readers* {'inst read-instant-timestamp}]
(testing "read-instant-timestamp produces clojure.lang.TimeStamp"
(is (= clojure.lang.TimeStamp (class (read-string s)))))
(testing "clojure.lang.TimeStamp preserves nanoseconds"
(is (= 123456789 (-> s read-string .getNanos)))
(is (= 123456789 (-> s read-string pr-str read-string .getNanos)))
;; truncate at nanos for s3
(is (= 123456789 (-> s3 read-string pr-str read-string .getNanos))))
(testing "clojure.lang.TimeStamp should compare nanos"
(is (= (read-string s) (read-string s3)))
(is (not= (read-string s) (read-string s2)))))
(binding [*data-readers* {'inst read-instant-date}]
(testing "read-instant-date should truncate at milliseconds"
(is (= (read-string s) (read-string s2)) (read-string s3)))))
(let [s "#inst \"2010-11-12T03:14:15.123+05:00\""
s2 "#inst \"2010-11-11T22:14:15.123Z\""]
(binding [*data-readers* {'inst read-instant-date}]
(testing "read-instant-date should convert to UTC"
(is (= (read-string s) (read-string s2)))))
(binding [*data-readers* {'inst read-instant-timestamp}]
(testing "read-instant-timestamp should convert to UTC"
(is (= (read-string s) (read-string s2)))))
(binding [*data-readers* {'inst read-instant-calendar}]
(testing "read-instant-calendar should preserve timezone"
(is (not= (read-string s) (read-string s2)))))))
;; UUID Literals
;; #uuid "550e8400-e29b-41d4-a716-446655440000"
(deftest UUID
(is (= java.util.UUID (class #uuid "550e8400-e29b-41d4-a716-446655440000")))
(is (.equals #uuid "550e8400-e29b-41d4-a716-446655440000"
#uuid "550e8400-e29b-41d4-a716-446655440000"))
(is (not (identical? #uuid "550e8400-e29b-41d4-a716-446655440000"
#uuid "550e8400-e29b-41d4-a716-446655440000")))
(is (= 4 (.version #uuid "550e8400-e29b-41d4-a716-446655440000")))
(is (= (print-str #uuid "550e8400-e29b-41d4-a716-446655440000")
"#uuid \"550e8400-e29b-41d4-a716-446655440000\"")))
(deftest unknown-tag
(let [my-unknown (fn [tag val] {:unknown-tag tag :value val})
throw-on-unknown (fn [tag val] (throw (RuntimeException. (str "No data reader function for tag " tag))))
my-uuid (partial my-unknown 'uuid)
u "#uuid \"550e8400-e29b-41d4-a716-446655440000\""
s "#never.heard.of/some-tag [1 2]" ]
(binding [*data-readers* {'uuid my-uuid}
*default-data-reader-fn* my-unknown]
(testing "Unknown tag"
(is (= (read-string s)
{:unknown-tag 'never.heard.of/some-tag
:value [1 2]})))
(testing "Override uuid tag"
(is (= (read-string u)
{:unknown-tag 'uuid
:value "550e8400-e29b-41d4-a716-446655440000"}))))
(binding [*default-data-reader-fn* throw-on-unknown]
(testing "Unknown tag with custom throw-on-unknown"
(are [err msg form] (thrown-with-msg? err msg (read-string form))
Exception #"No data reader function for tag foo" "#foo [1 2]"
Exception #"No data reader function for tag bar/foo" "#bar/foo [1 2]"
Exception #"No data reader function for tag bar.baz/foo" "#bar.baz/foo [1 2]")))
(testing "Unknown tag out-of-the-box behavior (like Clojure 1.4)"
(are [err msg form] (thrown-with-msg? err msg (read-string form))
Exception #"No reader function for tag foo" "#foo [1 2]"
Exception #"No reader function for tag bar/foo" "#bar/foo [1 2]"
Exception #"No reader function for tag bar.baz/foo" "#bar.baz/foo [1 2]"))))
(defn roundtrip
"Print an object and read it back. Returns rather than throws
any exceptions."
[o]
(binding [*print-length* nil
*print-dup* nil
*print-level* nil]
(try
(-> o pr-str read-string)
(catch Throwable t t))))
(defn roundtrip-dup
"Print an object with print-dup and read it back.
Returns rather than throws any exceptions."
[o]
(binding [*print-length* nil
*print-dup* true
*print-level* nil]
(try
(-> o pr-str read-string)
(catch Throwable t t))))
(defspec types-that-should-roundtrip
roundtrip
[^{:tag cgen/ednable} o]
(when-not (= o %)
(throw (ex-info "Value cannot roundtrip, see ex-data" {:printed o :read %}))))
(defspec types-that-need-dup-to-roundtrip
roundtrip-dup
[^{:tag cgen/dup-readable} o]
(when-not (= o %)
(throw (ex-info "Value cannot roundtrip, see ex-data" {:printed o :read %}))))
(defrecord TestRecord [x y])
(deftest preserve-read-cond-test
(let [x (read-string {:read-cond :preserve} "#?(:clj foo :cljs bar)" )]
(is (reader-conditional? x))
(is (not (:splicing? x)))
(is (= :foo (get x :no-such-key :foo)))
(is (= (:form x) '(:clj foo :cljs bar)))
(is (= x (reader-conditional '(:clj foo :cljs bar) false))))
(let [x (read-string {:read-cond :preserve} "#?@(:clj [foo])" )]
(is (reader-conditional? x))
(is (:splicing? x))
(is (= :foo (get x :no-such-key :foo)))
(is (= (:form x) '(:clj [foo])))
(is (= x (reader-conditional '(:clj [foo]) true))))
(is (thrown-with-msg? RuntimeException #"No reader function for tag"
(read-string {:read-cond :preserve} "#js {:x 1 :y 2}" )))
(let [x (read-string {:read-cond :preserve} "#?(:cljs #js {:x 1 :y 2})")
[platform tl] (:form x)]
(is (reader-conditional? x))
(is (tagged-literal? tl))
(is (= 'js (:tag tl)))
(is (= {:x 1 :y 2} (:form tl)))
(is (= :foo (get tl :no-such-key :foo)))
(is (= tl (tagged-literal 'js {:x 1 :y 2}))))
(testing "print form roundtrips"
(doseq [s ["#?(:clj foo :cljs bar)"
"#?(:cljs #js {:x 1, :y 2})"
"#?(:clj #clojure.test_clojure.reader.TestRecord [42 85])"]]
(is (= s (pr-str (read-string {:read-cond :preserve} s)))))))
(deftest reader-conditionals
(testing "basic read-cond"
(is (= '[foo-form]
(read-string {:read-cond :allow :features #{:foo}} "[#?(:foo foo-form :bar bar-form)]")))
(is (= '[bar-form]
(read-string {:read-cond :allow :features #{:bar}} "[#?(:foo foo-form :bar bar-form)]")))
(is (= '[foo-form]
(read-string {:read-cond :allow :features #{:foo :bar}} "[#?(:foo foo-form :bar bar-form)]")))
(is (= '[]
(read-string {:read-cond :allow :features #{:baz}} "[#?( :foo foo-form :bar bar-form)]"))))
(testing "environmental features"
(is (= "clojure" #?(:clj "clojure" :cljs "clojurescript" :default "default"))))
(testing "default features"
(is (= "default" #?(:clj-clr "clr" :cljs "cljs" :default "default"))))
(testing "splicing"
(is (= [] [#?@(:clj [])]))
(is (= [:a] [#?@(:clj [:a])]))
(is (= [:a :b] [#?@(:clj [:a :b])]))
(is (= [:a :b :c] [#?@(:clj [:a :b :c])]))
(is (= [:a :b :c] [#?@(:clj [:a :b :c])])))
(testing "nested splicing"
(is (= [:a :b :c :d :e]
[#?@(:clj [:a #?@(:clj [:b #?@(:clj [:c]) :d]):e])]))
(is (= '(+ 1 (+ 2 3))
'(+ #?@(:clj [1 (+ #?@(:clj [2 3]))]))))
(is (= '(+ (+ 2 3) 1)
'(+ #?@(:clj [(+ #?@(:clj [2 3])) 1]))))
(is (= [:a [:b [:c] :d] :e]
[#?@(:clj [:a [#?@(:clj [:b #?@(:clj [[:c]]) :d])] :e])])))
(testing "bypass unknown tagged literals"
(is (= [1 2 3] #?(:cljs #js [1 2 3] :clj [1 2 3])))
(is (= :clojure #?(:foo #some.nonexistent.Record {:x 1} :clj :clojure))))
(testing "error cases"
(is (thrown-with-msg? RuntimeException #"Feature should be a keyword" (read-string {:read-cond :allow} "#?((+ 1 2) :a)")))
(is (thrown-with-msg? RuntimeException #"even number of forms" (read-string {:read-cond :allow} "#?(:cljs :a :clj)")))
(is (thrown-with-msg? RuntimeException #"read-cond-splicing must implement" (read-string {:read-cond :allow} "#?@(:clj :a)")))
(is (thrown-with-msg? RuntimeException #"is reserved" (read-string {:read-cond :allow} "#?@(:foo :a :else :b)")))
(is (thrown-with-msg? RuntimeException #"must be a list" (read-string {:read-cond :allow} "#?[:foo :a :else :b]")))
(is (thrown-with-msg? RuntimeException #"Conditional read not allowed" (read-string {:read-cond :BOGUS} "#?[:clj :a :default nil]")))
(is (thrown-with-msg? RuntimeException #"Conditional read not allowed" (read-string "#?[:clj :a :default nil]")))
(is (thrown-with-msg? RuntimeException #"Reader conditional splicing not allowed at the top level" (read-string {:read-cond :allow} "#?@(:clj [1 2])")))
(is (thrown-with-msg? RuntimeException #"Reader conditional splicing not allowed at the top level" (read-string {:read-cond :allow} "#?@(:clj [1])")))
(is (thrown-with-msg? RuntimeException #"Reader conditional splicing not allowed at the top level" (read-string {:read-cond :allow} "#?@(:clj []) 1"))))
(testing "clj-1698-regression"
(let [opts {:features #{:clj} :read-cond :allow}]
(is (= 1 (read-string opts "#?(:cljs {'a 1 'b 2} :clj 1)")))
(is (= 1 (read-string opts "#?(:cljs (let [{{b :b} :a {d :d} :c} {}]) :clj 1)")))
(is (= '(def m {}) (read-string opts "(def m #?(:cljs ^{:a :b} {} :clj ^{:a :b} {}))")))
(is (= '(def m {}) (read-string opts "(def m #?(:cljs ^{:a :b} {} :clj ^{:a :b} {}))")))
(is (= 1 (read-string opts "#?(:cljs {:a #_:b :c} :clj 1)")))))
(testing "nil expressions"
(is (nil? #?(:default nil)))
(is (nil? #?(:foo :bar :clj nil)))
(is (nil? #?(:clj nil :foo :bar)))
(is (nil? #?(:foo :bar :default nil)))))
(deftest eof-option
(is (= 23 (read-string {:eof 23} "")))
(is (= 23 (read {:eof 23} (clojure.lang.LineNumberingPushbackReader.
(java.io.StringReader. ""))))))
(require '[clojure.string :as s])
(deftest namespaced-maps
(is (= #:a{1 nil, :b nil, :b/c nil, :_/d nil}
#:a {1 nil, :b nil, :b/c nil, :_/d nil}
{1 nil, :a/b nil, :b/c nil, :d nil}))
(is (= #::{1 nil, :a nil, :a/b nil, :_/d nil}
#:: {1 nil, :a nil, :a/b nil, :_/d nil}
{1 nil, :clojure.test-clojure.reader/a nil, :a/b nil, :d nil} ))
(is (= #::s{1 nil, :a nil, :a/b nil, :_/d nil}
#::s {1 nil, :a nil, :a/b nil, :_/d nil}
{1 nil, :clojure.string/a nil, :a/b nil, :d nil}))
(is (= #::clojure.core{1 nil, :a nil, :a/b nil, :_/d nil} {1 nil, :clojure.core/a nil, :a/b nil, :d nil}))
(is (= (read-string "#:a{b 1 b/c 2}") {'a/b 1, 'b/c 2}))
(is (= (binding [*ns* (the-ns 'clojure.test-clojure.reader)] (read-string "#::{b 1, b/c 2, _/d 3}")) {'clojure.test-clojure.reader/b 1, 'b/c 2, 'd 3}))
(is (= (binding [*ns* (the-ns 'clojure.test-clojure.reader)] (read-string "#::s{b 1, b/c 2, _/d 3}")) {'clojure.string/b 1, 'b/c 2, 'd 3}))
(is (= (read-string "#::clojure.core{b 1, b/c 2, _/d 3}") {'clojure.core/b 1, 'b/c 2, 'd 3})))
(deftest namespaced-map-errors
(are [err msg form] (thrown-with-msg? err msg (read-string form))
Exception #"Invalid token" "#:::"
Exception #"Namespaced map literal must contain an even number of forms" "#:s{1}"
Exception #"Namespaced map must specify a valid namespace" "#:s/t{1 2}"
Exception #"Namespaced map literal must contain an even number of forms" "#::clojure.core{1}"
Exception #"Namespaced map must specify a valid namespace" "#::clojure.core/t{1 2}"
Exception #"Unknown auto-resolved namespace alias" "#::BOGUS{1 2}"
Exception #"Namespaced map must specify a namespace" "#:: clojure.core{:a 1}"
Exception #"Namespaced map must specify a namespace" "#: clojure.core{:a 1}"))
(deftest namespaced-map-edn
(is (= {1 1, :a/b 2, :b/c 3, :d 4}
(edn/read-string "#:a{1 1, :b 2, :b/c 3, :_/d 4}")
(edn/read-string "#:a {1 1, :b 2, :b/c 3, :_/d 4}"))))
@ivanpierre
Copy link
Author

ivanpierre commented Dec 12, 2016

To find the changes, just follow TimeStamp et .setNodes.
REPL seems to work, just have to compile leiningen... :D

@ivanpierre
Copy link
Author

Ok, I pass to the GNU version of Timestamp. The code is neater. I mixed some of Sun's for more consistency. I dropped the string management of dates as Clojure will do it in clojure.instant.

It still work. I had a doubt...

If I type (clojure.lang.TimeStamp. 3678141) the response will be :
==> #inst "1970-01-01T01:01:18.141000000-00:00"" with a nano of
141000000

But is I set nano to 1 : (.setNanos (clojure.lang.TimeStamp. 3678141) 1) the response is : #inst "1970-01-01T01:01:18.000000001-00:00"

This is correct, but it's a little disturbing to see my nice .141disapear... :D

So good, now pass to Leinigen...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment