From 68ecfa05b3e4a7c0f11bca7dba74383537980899 Mon Sep 17 00:00:00 2001
From: Stuart Halloway <stu@thinkrelevance.com>
Date: Fri, 4 Nov 2011 13:50:55 -0400
Subject: [PATCH] CLJ-871 instant reader

---
 build.xml                            |   16 +++++++---------
 pom.xml                              |   11 +++++++++++
 readme.txt                           |   10 +++++++---
 src/clj/clojure/core.clj             |   18 ++++++++++++++++++
 src/clj/clojure/joda.clj             |   32 ++++++++++++++++++++++++++++++++
 src/jvm/clojure/lang/LispReader.java |   23 ++++++++++++++++++++++-
 src/jvm/clojure/lang/RT.java         |    2 ++
 7 files changed, 99 insertions(+), 13 deletions(-)
 create mode 100644 src/clj/clojure/joda.clj

diff --git a/build.xml b/build.xml
index a693e6d..a64d43b 100644
--- a/build.xml
+++ b/build.xml
@@ -42,7 +42,7 @@
   <target name="compile-clojure"
           description="Compile Clojure sources.">
     <java classname="clojure.lang.Compile"
-          classpath="${build}:${cljsrc}"
+          classpath="${build}:${cljsrc}:${maven_compile_classpath}"
           failonerror="true"
           fork="true">
       <sysproperty key="clojure.compile.path" value="${build}"/>
@@ -70,6 +70,7 @@
       <arg value="clojure.string"/>
       <arg value="clojure.data"/>
       <arg value="clojure.reflect"/>
+      <arg value="clojure.joda"/>
     </java>
   </target>
 
@@ -81,7 +82,7 @@
     <!-- <javac srcdir="${jtestsrc}" destdir="${test-classes}" includeJavaRuntime="yes" -->
     <!--        debug="true" target="1.5" includeantruntime="no"/> -->
     <java classname="clojure.lang.Compile"
-          classpath="${test-classes}:${test}:${build}:${cljsrc}"
+          classpath="${test-classes}:${test}:${build}:${cljsrc}:${maven_compile_classpath}"
           failonerror="true"
 	  fork="true">
       <sysproperty key="clojure.compile.path" value="${test-classes}"/>
@@ -94,13 +95,10 @@
           description="Run clojure tests without recompiling clojure."
           depends="compile-tests"
           unless="maven.test.skip">
-    <java classname="clojure.main" failonerror="true" fork="true">
-      <classpath>
-        <path location="${test-classes}"/>
-        <path location="${test}"/>
-        <path location="${build}"/>
-	<path location="${cljsrc}"/>
-      </classpath>
+    <java classname="clojure.main" 
+          classpath="${test-classes}:${test}:${build}:${cljsrc}:${maven_compile_classpath}"
+          failonerror="true" 
+          fork="true">
       <arg value="${test-script}"/>
     </java>
   </target>
diff --git a/pom.xml b/pom.xml
index ae7dd17..025fd0b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,15 @@
     <url>git@github.com:clojure/clojure.git</url>
   </scm>
 
+  <dependencies>
+    <dependency>
+      <groupId>joda-time</groupId>
+      <artifactId>joda-time</artifactId>
+      <version>1.6.2</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
   <build>
     <resources>
       <resource>
@@ -72,6 +81,7 @@
 	    </goals>
 	    <configuration>
 	      <target>
+                <property name="maven_compile_classpath" refid="maven.compile.classpath"/>
 		<ant target="compile-clojure" />
 	      </target>
 	    </configuration>
@@ -84,6 +94,7 @@
 	    </goals>
 	    <configuration>
 	      <target>
+                <property name="maven_compile_classpath" refid="maven.compile.classpath"/>
 		<ant target="test" />
 	      </target>
 	    </configuration>
diff --git a/readme.txt b/readme.txt
index 41c8c2a..c4089e9 100644
--- a/readme.txt
+++ b/readme.txt
@@ -13,9 +13,6 @@ Getting Started: http://dev.clojure.org/display/doc/Getting+Started
 
 To run:  java -cp clojure-${VERSION}.jar clojure.main
 
-To build locally with Ant:  ant
-
-
 Maven 2 build instructions:
 
   To build:  mvn package 
@@ -28,6 +25,13 @@ Maven 2 build instructions:
   To build a ZIP distribution:  mvn package -Pdistribution
   The built .zip will be in target/
 
+To build locally with Ant:  
+
+  Determine your local maven classpath with:
+  mvn dependency:build-classpath
+
+  Build
+  ant -Dmaven_compile_classpath=YOUR_LOCAL_PATH_HERE
 
 --------------------------------------------------------------------------
 This program uses the ASM bytecode engineering library which is distributed
diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj
index b0a25fc..6d114e9 100644
--- a/src/clj/clojure/core.clj
+++ b/src/clj/clojure/core.clj
@@ -5739,6 +5739,16 @@
   Defaults to true"
   {:added "1.0"})
 
+(add-doc-and-meta
+ *instant-reader*
+ "Used by Clojure's reader to convert date literals of the form
+      #@{ISO8601 Date String}
+  into a representation of an instant.
+
+  Default instant reader uses Joda Time to parse the date, or throws
+  an exception if Joda Time library is not available."
+ {:added "1.4"})
+
 (defn future?
   "Returns true if x is a future"
   {:added "1.1"
@@ -5971,6 +5981,14 @@
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; helper files ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 (alter-meta! (find-ns 'clojure.core) assoc :doc "Fundamental library of the Clojure language")
+(defn ^:dynamic *instant-reader*
+  [& args]
+  (throw (Exception. "Clojure's default date reader requires the Joda library.")))
+(eval '(when
+           (try
+            (Class/forName "org.joda.time.DateTime")
+            (catch Exception _ nil))
+         (require 'clojure.joda)))
 (load "core_proxy")
 (load "core_print")
 (load "genclass")
diff --git a/src/clj/clojure/joda.clj b/src/clj/clojure/joda.clj
new file mode 100644
index 0000000..33a37cc
--- /dev/null
+++ b/src/clj/clojure/joda.clj
@@ -0,0 +1,32 @@
+;   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.joda)
+
+(def ^:private ^org.joda.time.format.DateTimeFormatter date-parser
+  (-> (org.joda.time.format.ISODateTimeFormat/dateTimeParser)
+      (.withZone(org.joda.time.DateTimeZone/UTC))))
+
+(def ^:private ^org.joda.time.format.DateTimeFormatter date-printer
+  (-> (org.joda.time.format.ISODateTimeFormat/dateTime)
+      (.withZone(org.joda.time.DateTimeZone/UTC))))
+
+(defn read-instant
+  "Alpha, subject to change."
+  {:added "1.4"}
+  [s]
+  (.parseDateTime date-parser s))
+
+(defmethod print-method org.joda.time.DateTime [^org.joda.time.DateTime d, ^java.io.Writer w]
+  (.write w "#@")
+  (.printTo date-printer w d))
+
+(defmethod print-dup org.joda.time.DateTime [d w] (print-method d w))
+
+(alter-var-root #'clojure.core/*instant-reader* (constantly read-instant))
+
+
diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java
index dbb59a6..3ff6715 100644
--- a/src/jvm/clojure/lang/LispReader.java
+++ b/src/jvm/clojure/lang/LispReader.java
@@ -99,7 +99,6 @@ static IFn ctorReader = new CtorReader();
 	macros['%'] = new ArgReader();
 	macros['#'] = new DispatchReader();
 
-
 	dispatchMacros['^'] = new MetaReader();
 	dispatchMacros['\''] = new VarReader();
 	dispatchMacros['"'] = new RegexReader();
@@ -109,6 +108,7 @@ static IFn ctorReader = new CtorReader();
 	dispatchMacros['!'] = new CommentReader();
 	dispatchMacros['<'] = new UnreadableReader();
 	dispatchMacros['_'] = new DiscardReader();
+    dispatchMacros['@'] = new InstantReader();
 	}
 
 static boolean isWhitespace(int ch){
@@ -705,6 +705,27 @@ static class ArgReader extends AFn{
 	}
 }
 
+static class InstantReader extends AFn {
+    public Object invoke(Object reader, Object pct) {
+        PushbackReader r = (PushbackReader) reader;
+        StringBuilder sb = new StringBuilder();
+        for(; ;)
+            {
+            int ch = read1(r);
+            if(ch == -1 || isWhitespace(ch) || isMacro(ch))
+                {
+                unread(r, ch);
+                break;
+                }
+            sb.append((char) ch);
+            }
+        String s = sb.toString();
+        if (!(s instanceof String))
+            throw new IllegalArgumentException(s + " is not an ISO8601 date string");
+        return RT.instantReader.fn().invoke(s);
+    }
+}
+
 public static class MetaReader extends AFn{
 	public Object invoke(Object reader, Object caret) {
 		PushbackReader r = (PushbackReader) reader;
diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java
index 81ca178..9a89035 100644
--- a/src/jvm/clojure/lang/RT.java
+++ b/src/jvm/clojure/lang/RT.java
@@ -218,6 +218,8 @@ final static Var NS_VAR = Var.intern(CLOJURE_NS, Symbol.intern("ns"), F);
 final static Var FN_LOADER_VAR = Var.intern(CLOJURE_NS, Symbol.intern("*fn-loader*"), null).setDynamic();
 static final Var PRINT_INITIALIZED = Var.intern(CLOJURE_NS, Symbol.intern("print-initialized"));
 static final Var PR_ON = Var.intern(CLOJURE_NS, Symbol.intern("pr-on"));
+static final public Var instantReader = Var.intern(CLOJURE_NS, Symbol.intern("*instant-reader*"), null).setDynamic();
+
 //final static Var IMPORTS = Var.intern(CLOJURE_NS, Symbol.intern("*imports*"), DEFAULT_IMPORTS);
 final static IFn inNamespace = new AFn(){
 	public Object invoke(Object arg1) {
-- 
1.7.4.1

