You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by dk...@apache.org on 2019/10/31 14:48:39 UTC

[ws-axiom] 02/25: Initial datatype library code.

This is an automated email from the ASF dual-hosted git repository.

dkulp pushed a commit to branch datatypes
in repository https://gitbox.apache.org/repos/asf/ws-axiom.git

commit d63b90cdbe9f6955401712b1ea6f3b15babc584a
Author: Andreas Veithen <ve...@apache.org>
AuthorDate: Sun Nov 15 19:30:57 2015 +0000

    Initial datatype library code.
---
 aspects/core-aspects/pom.xml                       |   5 +
 .../org/apache/axiom/core/ContextAccessorImpl.java |  18 +-
 .../java/org/apache/axiom/core/CoreElement.java    |   5 +
 .../org/apache/axiom/core/CoreElementSupport.aj    |  17 ++
 aspects/fom-aspects/pom.xml                        |   5 +
 .../org/apache/axiom/fom/AbderaDateTimeMixin.aj    |  18 +-
 {aspects/core-aspects => datatypes}/pom.xml        |  35 ++-
 .../axiom/datatype/AbstractInvariantType.java      |  14 +-
 .../org/apache/axiom/datatype/ContextAccessor.java |   7 +-
 .../apache/axiom/datatype/DOMContextAccessor.java  |  21 +-
 .../java/org/apache/axiom/datatype/DOMHelper.java  |  19 +-
 .../org/apache/axiom/datatype/InvariantType.java   |   9 +-
 .../main/java/org/apache/axiom/datatype/Type.java  |   9 +-
 .../datatype/UnexpectedCharacterException.java     |  12 +-
 .../datatype/UnexpectedEndOfStringException.java   |  12 +-
 .../AbstractWhitespaceCollapsingInvariantType.java |  46 ++++
 .../axiom/datatype/xsd/NoTimeZoneException.java    |   6 +-
 .../apache/axiom/datatype/xsd/SimpleTimeZone.java  |  95 ++++++++
 .../org/apache/axiom/datatype/xsd/Temporal.java    |  26 ++-
 .../apache/axiom/datatype/xsd/TemporalImpl.java    |  68 ++++++
 .../apache/axiom/datatype/xsd/TemporalType.java    | 245 +++++++++++++++++++++
 .../java/org/apache/axiom/datatype/xsd/Util.java   |  10 +-
 .../java/org/apache/axiom/datatype/xsd/XSDate.java |   6 +-
 .../org/apache/axiom/datatype/xsd/XSDateImpl.java  | 106 +++++++++
 .../org/apache/axiom/datatype/xsd/XSDateTime.java  |  21 +-
 .../apache/axiom/datatype/xsd/XSDateTimeImpl.java  | 129 +++++++++++
 .../apache/axiom/datatype/xsd/XSDateTimeType.java  |   8 +-
 .../axiom/datatype/xsd/XSDateTimeTypeImpl.java     |  22 +-
 .../org/apache/axiom/datatype/xsd/XSDateType.java  |   8 +-
 .../apache/axiom/datatype/xsd/XSDateTypeImpl.java  |  21 +-
 .../org/apache/axiom/datatype/xsd/XSQNameType.java |  10 +-
 .../apache/axiom/datatype/xsd/XSQNameTypeImpl.java |  97 ++++++++
 .../apache/axiom/datatype/xsd/XSStringType.java    |   8 +-
 .../axiom/datatype/xsd/XSStringTypeImpl.java       |  16 +-
 .../java/org/apache/axiom/datatype/xsd/XSTime.java |   5 +-
 .../org/apache/axiom/datatype/xsd/XSTimeImpl.java  | 103 +++++++++
 .../org/apache/axiom/datatype/xsd/XSTimeType.java  |   8 +-
 .../apache/axiom/datatype/xsd/XSTimeTypeImpl.java  |  21 +-
 .../org/apache/axiom/datatype/DOMHelperTest.java   |  42 ++++
 .../axiom/datatype/xsd/XSDateTimeTypeTest.java     |  53 +++++
 implementations/pom.xml                            |   1 +
 pom.xml                                            |   1 +
 testing/soap-testsuite/pom.xml                     |   5 +
 .../axiom/ts/soap/ConvertedSOAPSampleContent.java  |  57 +++--
 44 files changed, 1336 insertions(+), 114 deletions(-)

diff --git a/aspects/core-aspects/pom.xml b/aspects/core-aspects/pom.xml
index a4c1348..afb0952 100644
--- a/aspects/core-aspects/pom.xml
+++ b/aspects/core-aspects/pom.xml
@@ -40,6 +40,11 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>datatypes</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>org.aspectj</groupId>
             <artifactId>aspectjrt</artifactId>
         </dependency>
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/aspects/core-aspects/src/main/java/org/apache/axiom/core/ContextAccessorImpl.java
similarity index 56%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to aspects/core-aspects/src/main/java/org/apache/axiom/core/ContextAccessorImpl.java
index 900ce36..d28636b 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/aspects/core-aspects/src/main/java/org/apache/axiom/core/ContextAccessorImpl.java
@@ -16,8 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.core;
 
-interface TextTransformer {
-    String transform(String in);
+import org.apache.axiom.datatype.ContextAccessor;
+
+final class ContextAccessorImpl implements ContextAccessor<CoreElement,Semantics> {
+    public static final ContextAccessorImpl INSTANCE = new ContextAccessorImpl();
+    
+    private ContextAccessorImpl() {}
+
+    public String lookupNamespaceURI(CoreElement element, Semantics semantics, String prefix) {
+        return element.coreLookupNamespaceURI(prefix, semantics);
+    }
+
+    public String lookupPrefix(CoreElement element, Semantics semantics, String namespaceURI) {
+        return element.coreLookupPrefix(namespaceURI, semantics);
+    }
 }
diff --git a/aspects/core-aspects/src/main/java/org/apache/axiom/core/CoreElement.java b/aspects/core-aspects/src/main/java/org/apache/axiom/core/CoreElement.java
index a5dd131..be5e79f 100644
--- a/aspects/core-aspects/src/main/java/org/apache/axiom/core/CoreElement.java
+++ b/aspects/core-aspects/src/main/java/org/apache/axiom/core/CoreElement.java
@@ -18,8 +18,11 @@
  */
 package org.apache.axiom.core;
 
+import java.text.ParseException;
 import java.util.Iterator;
 
+import org.apache.axiom.datatype.Type;
+
 public interface CoreElement extends CoreChildNode, CoreMixedContentContainer, CoreNamedNode, DeferringParentNode {
     /**
      * Get the first attribute of this element.
@@ -153,4 +156,6 @@ public interface CoreElement extends CoreChildNode, CoreMixedContentContainer, C
     // TODO: wrong Javadoc: null vs. empty string
     // TODO: we can support default namespaces!
     String coreLookupPrefix(String namespaceURI, Semantics semantics);
+    
+    <T> T coreGetValue(Type<T> type, Semantics semantics) throws ParseException;
 }
diff --git a/aspects/core-aspects/src/main/java/org/apache/axiom/core/CoreElementSupport.aj b/aspects/core-aspects/src/main/java/org/apache/axiom/core/CoreElementSupport.aj
index 2a271d2..e3e7f32 100644
--- a/aspects/core-aspects/src/main/java/org/apache/axiom/core/CoreElementSupport.aj
+++ b/aspects/core-aspects/src/main/java/org/apache/axiom/core/CoreElementSupport.aj
@@ -18,8 +18,11 @@
  */
 package org.apache.axiom.core;
 
+import java.text.ParseException;
 import java.util.Iterator;
 
+import org.apache.axiom.datatype.Type;
+
 public aspect CoreElementSupport {
     private CoreAttribute CoreElement.firstAttribute;
 
@@ -220,4 +223,18 @@ public aspect CoreElementSupport {
     // This is basically a hook for OMSourcedElement
     public <T> void CoreElement.initSource(ClonePolicy<T> policy, T options, CoreElement other) {
     }
+    
+    public final <T> T CoreElement.coreGetValue(Type<T> type, Semantics semantics) throws ParseException {
+        Object characterData = coreGetCharacterData(ElementAction.RETURN_NULL);
+        if (characterData == null) {
+            throw new ParseException("Element has mixed content", 0);
+        } else {
+            return type.parse(characterData.toString(), ContextAccessorImpl.INSTANCE, this, semantics);
+        }
+    }
+    
+    public final <T> void CoreElement.coreSetValue(Type<T> type, T value, Semantics semantics) {
+        // TODO: actually set value
+        type.format(value, ContextAccessorImpl.INSTANCE, this, semantics);
+    }
 }
diff --git a/aspects/fom-aspects/pom.xml b/aspects/fom-aspects/pom.xml
index 3a66c31..e2b467c 100644
--- a/aspects/fom-aspects/pom.xml
+++ b/aspects/fom-aspects/pom.xml
@@ -43,6 +43,11 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>datatypes</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>org.aspectj</groupId>
             <artifactId>aspectjrt</artifactId>
         </dependency>
diff --git a/aspects/fom-aspects/src/main/java/org/apache/axiom/fom/AbderaDateTimeMixin.aj b/aspects/fom-aspects/src/main/java/org/apache/axiom/fom/AbderaDateTimeMixin.aj
index 9bf2142..fb75b3b 100644
--- a/aspects/fom-aspects/src/main/java/org/apache/axiom/fom/AbderaDateTimeMixin.aj
+++ b/aspects/fom-aspects/src/main/java/org/apache/axiom/fom/AbderaDateTimeMixin.aj
@@ -17,19 +17,35 @@
  */
 package org.apache.axiom.fom;
 
+import java.text.ParseException;
 import java.util.Calendar;
 import java.util.Date;
 
 import org.apache.abdera.model.AtomDate;
 import org.apache.abdera.model.DateTime;
+import org.apache.axiom.datatype.xsd.XSDateTime;
+import org.apache.axiom.datatype.xsd.XSDateTimeType;
 import org.apache.axiom.fom.AbderaDateTime;
 
 public aspect AbderaDateTimeMixin {
     private AtomDate AbderaDateTime.value;
 
+    private XSDateTime AbderaDateTime.getXSDateTime() {
+        XSDateTime dateTime;
+        try {
+            dateTime = coreGetValue(XSDateTimeType.INSTANCE, FOMSemantics.INSTANCE);
+        } catch (ParseException ex) {
+            throw new IllegalArgumentException("Invalid Date Format", ex);
+        }
+        if (!dateTime.hasTimeZone()) {
+            throw new IllegalArgumentException("Not a valid RFC3339 date/time value: no time zone");
+        }
+        return dateTime;
+    }
+    
     public final AtomDate AbderaDateTime.getValue() {
         if (value == null) {
-            value = AtomDate.valueOf(getText());
+            value = new AtomDate(getXSDateTime().getDate(null));
         }
         return value;
     }
diff --git a/aspects/core-aspects/pom.xml b/datatypes/pom.xml
similarity index 61%
copy from aspects/core-aspects/pom.xml
copy to datatypes/pom.xml
index a4c1348..9111070 100644
--- a/aspects/core-aspects/pom.xml
+++ b/datatypes/pom.xml
@@ -17,31 +17,46 @@
   ~ specific language governing permissions and limitations
   ~ under the License.
   -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <parent>
         <groupId>org.apache.ws.commons.axiom</groupId>
-        <artifactId>aspects</artifactId>
+        <artifactId>axiom</artifactId>
         <version>1.2.16-SNAPSHOT</version>
     </parent>
 
-    <artifactId>core-aspects</artifactId>
+    <artifactId>datatypes</artifactId>
     <packaging>bundle</packaging>
 
-    <name>Core Aspects</name>
     <url>http://ws.apache.org/axiom/</url>
 
     <dependencies>
         <dependency>
-            <!-- This dependency will eventually go away -->
-            <groupId>${project.groupId}</groupId>
-            <artifactId>axiom-api</artifactId>
-            <version>${project.version}</version>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.aspectj</groupId>
-            <artifactId>aspectjrt</artifactId>
+            <groupId>com.google.truth</groupId>
+            <artifactId>truth</artifactId>
+            <scope>test</scope>
         </dependency>
     </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>*;-noimport:=true</Export-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/AbstractInvariantType.java
similarity index 63%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/AbstractInvariantType.java
index 900ce36..30a4a97 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/AbstractInvariantType.java
@@ -16,8 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype;
 
-interface TextTransformer {
-    String transform(String in);
+import java.text.ParseException;
+
+public abstract class AbstractInvariantType<T> implements InvariantType<T> {
+    public final <S,O> T parse(String literal, ContextAccessor<S,O> contextAccessor, S contextObject, O options) throws ParseException {
+        return parse(literal);
+    }
+
+    public final <S,O> String format(T value, ContextAccessor<S,O> contextAccessor, S contextObject, O options) {
+        return format(value);
+    }
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/ContextAccessor.java
similarity index 78%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/ContextAccessor.java
index 900ce36..a384eb0 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/ContextAccessor.java
@@ -16,8 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype;
 
-interface TextTransformer {
-    String transform(String in);
+public interface ContextAccessor<T,O> {
+    String lookupNamespaceURI(T contextObject, O options, String prefix);
+    String lookupPrefix(T contextObject, O options, String namespaceURI);
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/DOMContextAccessor.java
similarity index 52%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/DOMContextAccessor.java
index 900ce36..fc3ed9f 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/DOMContextAccessor.java
@@ -16,8 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype;
 
-interface TextTransformer {
-    String transform(String in);
+import org.w3c.dom.Element;
+
+final class DOMContextAccessor implements ContextAccessor<Element,Void> {
+    static final DOMContextAccessor INSTANCE = new DOMContextAccessor();
+
+    public String lookupNamespaceURI(Element element, Void options, String prefix) {
+        String namespaceURI = element.lookupNamespaceURI(prefix.length() == 0 ? null : prefix);
+        if (namespaceURI != null) {
+            return namespaceURI;
+        } else {
+            return prefix.length() == 0 ? "" : null;
+        }
+    }
+
+    public String lookupPrefix(Element element, Void options, String namespaceURI) {
+        return element.lookupPrefix(namespaceURI.length() == 0 ? null : namespaceURI);
+    }
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/DOMHelper.java
similarity index 55%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/DOMHelper.java
index 900ce36..46f13cc 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/DOMHelper.java
@@ -16,8 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype;
 
-interface TextTransformer {
-    String transform(String in);
+import java.text.ParseException;
+
+import org.w3c.dom.Element;
+
+public final class DOMHelper {
+    private DOMHelper() {}
+    
+    public static <T> T getValue(Element element, Type<T> type) throws ParseException {
+        // TODO: using getTextContent here is actually incorrect because it extracts text recursively
+        return type.parse(element.getTextContent(), DOMContextAccessor.INSTANCE, element, null);
+    }
+    
+    public static <T> void setValue(Element element, Type<T> type, T value) {
+        element.setTextContent(type.format(value, DOMContextAccessor.INSTANCE, element, null));
+    }
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/InvariantType.java
similarity index 80%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/InvariantType.java
index 900ce36..88faefe 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/InvariantType.java
@@ -16,8 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype;
 
-interface TextTransformer {
-    String transform(String in);
+import java.text.ParseException;
+
+public interface InvariantType<T> extends Type<T> {
+    T parse(String literal) throws ParseException;
+    String format(T value);
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/Type.java
similarity index 71%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/Type.java
index 900ce36..b5351b8 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/Type.java
@@ -16,8 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype;
 
-interface TextTransformer {
-    String transform(String in);
+import java.text.ParseException;
+
+public interface Type<T> {
+    <S,O> T parse(String literal, ContextAccessor<S,O> contextAccessor, S contextObject, O options) throws ParseException;
+    <S,O> String format(T value, ContextAccessor<S,O> contextAccessor, S contextObject, O options);
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/UnexpectedCharacterException.java
similarity index 69%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/UnexpectedCharacterException.java
index 900ce36..ac03ac4 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/UnexpectedCharacterException.java
@@ -16,8 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype;
 
-interface TextTransformer {
-    String transform(String in);
+import java.text.ParseException;
+
+public class UnexpectedCharacterException extends ParseException {
+    private static final long serialVersionUID = 1L;
+
+    public UnexpectedCharacterException(String literal, int errorOffset) {
+        super("Unexpected character '" + literal.charAt(errorOffset) + "'", errorOffset);
+    }
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/UnexpectedEndOfStringException.java
similarity index 71%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/UnexpectedEndOfStringException.java
index 900ce36..879f78d 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/UnexpectedEndOfStringException.java
@@ -16,8 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype;
 
-interface TextTransformer {
-    String transform(String in);
+import java.text.ParseException;
+
+public class UnexpectedEndOfStringException extends ParseException {
+    private static final long serialVersionUID = 1L;
+
+    public UnexpectedEndOfStringException(String literal) {
+        super("Unexpected end of string", literal.length());
+    }
 }
diff --git a/datatypes/src/main/java/org/apache/axiom/datatype/xsd/AbstractWhitespaceCollapsingInvariantType.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/AbstractWhitespaceCollapsingInvariantType.java
new file mode 100644
index 0000000..03cd2d8
--- /dev/null
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/AbstractWhitespaceCollapsingInvariantType.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.datatype.xsd;
+
+import java.text.ParseException;
+
+import org.apache.axiom.datatype.AbstractInvariantType;
+import org.apache.axiom.datatype.UnexpectedEndOfStringException;
+
+abstract class AbstractWhitespaceCollapsingInvariantType<T> extends AbstractInvariantType<T> {
+    public final T parse(String literal) throws ParseException {
+        final int len = literal.length();
+        if (len == 0) {
+            throw new UnexpectedEndOfStringException(literal);
+        }
+        int begin = 0;
+        while (Util.isWhitespace(literal.charAt(begin))) {
+            if (++begin == len) {
+                throw new UnexpectedEndOfStringException(literal);
+            }
+        }
+        int end = len;
+        while (Util.isWhitespace(literal.charAt(end-1))) {
+            end--;
+        }
+        return parse(literal, begin, end);
+    }
+    
+    protected abstract T parse(String literal, int begin, int end) throws ParseException;
+}
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/NoTimeZoneException.java
similarity index 84%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/NoTimeZoneException.java
index 900ce36..12d1d69 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/NoTimeZoneException.java
@@ -16,8 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+public class NoTimeZoneException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
 }
diff --git a/datatypes/src/main/java/org/apache/axiom/datatype/xsd/SimpleTimeZone.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/SimpleTimeZone.java
new file mode 100644
index 0000000..28dd8bd
--- /dev/null
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/SimpleTimeZone.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.datatype.xsd;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+final class SimpleTimeZone extends TimeZone {
+    private static final long serialVersionUID = 1L;
+
+    private static final SimpleTimeZone[] instances;
+    
+    static {
+        instances = new SimpleTimeZone[57];
+        for (int i=0; i<instances.length; i++) {
+            instances[i] = new SimpleTimeZone(30*(-28 + i));
+        }
+    }
+    
+    private final int delta;
+    
+    SimpleTimeZone(int delta) {
+        this.delta = delta;
+        if (delta == 0) {
+            super.setID("GMT");
+        } else {
+            StringBuilder buffer = new StringBuilder("GMT");
+            buffer.append(delta < 0 ? '-' : '+');
+            delta = Math.abs(delta);
+            append2Digits(buffer, delta/60);
+            buffer.append(':');
+            append2Digits(buffer, delta % 60);
+            super.setID(buffer.toString());
+        }
+    }
+
+    private static void append2Digits(StringBuilder buffer, int value) {
+        buffer.append((char)('0' + value/10));
+        buffer.append((char)('0' + value%10));
+    }
+    
+    static SimpleTimeZone getInstance(int delta) {
+        return delta % 30 == 0 ? instances[delta/30 + 28] : new SimpleTimeZone(delta);
+    }
+    
+    @Override
+    public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) {
+        return getRawOffset();
+    }
+
+    @Override
+    public void setRawOffset(int offsetMillis) {
+        if (getRawOffset() != offsetMillis) {
+            throw new UnsupportedOperationException("Immutable time zone object");
+        }
+    }
+
+    @Override
+    public int getRawOffset() {
+        return delta * 60000;
+    }
+
+    @Override
+    public boolean useDaylightTime() {
+        return false;
+    }
+
+    @Override
+    public boolean inDaylightTime(Date date) {
+        return false;
+    }
+
+    @Override
+    public void setID(String ID) {
+        if (!getID().equals(ID)) {
+            throw new UnsupportedOperationException("Immutable time zone object");
+        }
+    }
+}
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/Temporal.java
similarity index 51%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/Temporal.java
index 900ce36..0d808f6 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/Temporal.java
@@ -16,8 +16,28 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+public interface Temporal {
+    /**
+     * 
+     * @param defaultTimeZone
+     *            the time zone to use if this temporal object has no time zone; may be
+     *            <code>null</code>
+     * @return
+     * @throws NoTimeZoneException
+     *             if this temporal object doesn't have a time zone and no default time zone was
+     *             specified
+     */
+    GregorianCalendar getCalendar(TimeZone defaultTimeZone);
+    
+    /**
+     * Determine if this temporal object has a time zone.
+     * 
+     * @return <code>true</code> if the object has a time zone, <code>false</code> otherwise
+     */
+    boolean hasTimeZone();
 }
diff --git a/datatypes/src/main/java/org/apache/axiom/datatype/xsd/TemporalImpl.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/TemporalImpl.java
new file mode 100644
index 0000000..5e381c2
--- /dev/null
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/TemporalImpl.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.datatype.xsd;
+
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+abstract class TemporalImpl implements Temporal {
+    abstract boolean hasDatePart();
+    abstract boolean hasTimePart();
+    abstract boolean isBC();
+    abstract String getAeon();
+    abstract int getYear();
+    abstract int getMonth();
+    abstract int getDay();
+    abstract int getHour();
+    abstract int getMinute();
+    abstract int getSecond();
+    abstract int getNanoSecond();
+    abstract SimpleTimeZone getTimeZone();
+    abstract String getNanoSecondFraction();
+    
+    public final GregorianCalendar getCalendar(TimeZone defaultTimeZone) {
+        // TODO: check aeon
+        TimeZone timeZone = getTimeZone();
+        if (timeZone == null) {
+            if (defaultTimeZone == null) {
+                throw new NoTimeZoneException();
+            }
+            timeZone = defaultTimeZone;
+        }
+        GregorianCalendar calendar = new GregorianCalendar(timeZone);
+        if (hasDatePart()) {
+            // TODO: BC
+            // TODO: throw exception if aeon is not null
+            calendar.set(GregorianCalendar.YEAR, getYear());
+            calendar.set(GregorianCalendar.MONTH, getMonth()-1);
+            calendar.set(GregorianCalendar.DAY_OF_MONTH, getDay());
+        }
+        if (hasTimePart()) {
+            calendar.set(GregorianCalendar.HOUR_OF_DAY, getHour());
+            calendar.set(GregorianCalendar.MINUTE, getMinute());
+            calendar.set(GregorianCalendar.SECOND, getSecond());
+            calendar.set(GregorianCalendar.MILLISECOND, getNanoSecond()/1000000);
+        }
+        return calendar;
+    }
+    
+    public boolean hasTimeZone() {
+        return getTimeZone() != null;
+    }
+}
diff --git a/datatypes/src/main/java/org/apache/axiom/datatype/xsd/TemporalType.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/TemporalType.java
new file mode 100644
index 0000000..342a449
--- /dev/null
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/TemporalType.java
@@ -0,0 +1,245 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.datatype.xsd;
+
+import java.text.ParseException;
+
+import org.apache.axiom.datatype.UnexpectedCharacterException;
+import org.apache.axiom.datatype.UnexpectedEndOfStringException;
+
+abstract class TemporalType<T> extends AbstractWhitespaceCollapsingInvariantType<T> {
+    abstract boolean hasDatePart();
+    abstract boolean hasTimePart();
+    abstract T createInstance(boolean bc, String aeon, int year, int month, int day, int hour,
+            int minute, int second, int nanoSecond, String nanoSecondFraction,
+            SimpleTimeZone timeZone);
+    
+    @Override
+    protected final T parse(String literal, int begin, int end) throws ParseException {
+        int index = begin;
+        boolean bc;
+        String aeon;
+        int year;
+        int month;
+        int day;
+        if (hasDatePart()) {
+            if (literal.charAt(0) == '-') {
+                bc = true;
+                index++;
+            } else {
+                bc = false;
+            }
+            {
+                int digits = countDigits(literal, index, "-", false);
+                if (digits < 4) {
+                    throw new UnexpectedCharacterException(literal, index+digits);
+                }
+                if (digits > 4 && literal.charAt(index) == '0') {
+                    throw new UnexpectedCharacterException(literal, index);
+                }
+                if (digits > 9) {
+                    aeon = literal.substring(index, index+digits-9);
+                    index += digits-9;
+                    digits = 9;
+                } else {
+                    aeon = null;
+                }
+                year = parseDigitsUnchecked(literal, index, digits);
+                if (year == 0) {
+                    throw new ParseException("Year can't be zero", index);
+                }
+                index += digits + 1;
+            }
+            month = parseDigitsChecked(literal, index, 2);
+            index += 2;
+            if (index == end) {
+                throw new UnexpectedEndOfStringException(literal);
+            }
+            if (literal.charAt(index) != '-') {
+                throw new UnexpectedCharacterException(literal, index);
+            }
+            index++;
+            day = parseDigitsChecked(literal, index, 2);
+            index += 2;
+        } else {
+            bc = false;
+            aeon = null;
+            year = 0;
+            month = 0;
+            day = 0;
+        }
+        if (hasDatePart() && hasTimePart()) {
+            if (index == end) {
+                throw new UnexpectedEndOfStringException(literal);
+            }
+            if (literal.charAt(index) != 'T') {
+                throw new UnexpectedCharacterException(literal, index);
+            }
+            index++;
+        }
+        int hour;
+        int minute;
+        int second;
+        int nanoSecond;
+        String nanoSecondFraction;
+        if (hasTimePart()) {
+            hour = parseDigitsChecked(literal, index, 2);
+            index += 2;
+            if (index == end) {
+                throw new UnexpectedEndOfStringException(literal);
+            }
+            if (literal.charAt(index) != ':') {
+                throw new UnexpectedCharacterException(literal, index);
+            }
+            index++;
+            minute = parseDigitsChecked(literal, index, 2);
+            index += 2;
+            if (index == end) {
+                throw new UnexpectedEndOfStringException(literal);
+            }
+            if (literal.charAt(index) != ':') {
+                throw new UnexpectedCharacterException(literal, index);
+            }
+            index++;
+            second = parseDigitsChecked(literal, index, 2);
+            index += 2;
+            if (index < end && literal.charAt(index) == '.') {
+                index++;
+                if (index == end) {
+                    throw new UnexpectedEndOfStringException(literal);
+                }
+                int digits = countDigits(literal, index, "Z+-", true);
+                if (digits == 0) {
+                    if (index == end) {
+                        throw new UnexpectedEndOfStringException(literal);
+                    } else {
+                        throw new UnexpectedCharacterException(literal, index);
+                    }
+                }
+                // TODO: we should remove trailing zeros
+                nanoSecond = parseDigitsUnchecked(literal, index, Math.min(digits, 9));
+                if (digits > 9) {
+                    nanoSecondFraction = literal.substring(index+9, index+digits-9);
+                } else {
+                    for (int i = digits; i < 9; i++) {
+                        nanoSecond *= 10;
+                    }
+                    nanoSecondFraction = null;
+                }
+                index += digits;
+            } else {
+                nanoSecond = 0;
+                nanoSecondFraction = null;
+            }
+        } else {
+            hour = 0;
+            minute = 0;
+            second = 0;
+            nanoSecond = 0;
+            nanoSecondFraction = null;
+        }
+        SimpleTimeZone timeZone;
+        if (index == end) {
+            timeZone = null;
+        } else {
+            int delta;
+            char c = literal.charAt(index);
+            index++;
+            if (c == 'Z') {
+                delta = 0;
+            } else {
+                boolean negative = c == '-';
+                int deltaHours = parseDigitsChecked(literal, index, 2);
+                index += 2;
+                if (index == end) {
+                    throw new UnexpectedEndOfStringException(literal);
+                }
+                if (literal.charAt(index) != ':') {
+                    throw new UnexpectedCharacterException(literal, index);
+                }
+                index++;
+                int deltaMinutes = parseDigitsChecked(literal, index, 2);
+                index += 2;
+                delta = 60*deltaHours + deltaMinutes;
+                if (negative) {
+                    delta = -delta;
+                }
+            }
+            timeZone = SimpleTimeZone.getInstance(delta);
+        }
+        if (index != end) {
+            throw new UnexpectedCharacterException(literal, index);
+        }
+        return createInstance(bc, aeon, year, month, day, hour, minute, second, nanoSecond,
+                nanoSecondFraction, timeZone);
+    }
+    
+    private static int countDigits(String literal, int index, String stopChars, boolean allowEndOfString) throws ParseException {
+        final int len = literal.length();
+        int count = 0;
+        while (true) {
+            if (index == len) {
+                if (allowEndOfString) {
+                    return count;
+                } else {
+                    throw new UnexpectedEndOfStringException(literal);
+                }
+            }
+            char c = literal.charAt(index);
+            if ('0' <= c && c <= '9') {
+                index++;
+                count++;
+            } else if (stopChars.indexOf(c) != -1) {
+                return count;
+            } else {
+                throw new UnexpectedCharacterException(literal, index);
+            }
+        }
+    }
+    
+    private static int parseDigitsUnchecked(String literal, int index, int count) {
+        int value = 0;
+        for (; count > 0; count--) {
+            value = 10*value + literal.charAt(index++) - '0';
+        }
+        return value;
+    }
+    
+    private static int parseDigitsChecked(String literal, int index, int count) throws ParseException {
+        final int len = literal.length();
+        int value = 0;
+        for (; count > 0; count--) {
+            if (index == len) {
+                throw new UnexpectedEndOfStringException(literal);
+            }
+            char c = literal.charAt(index);
+            if ('0' <= c && c <= '9') {
+                value = 10*value + c - '0';
+            } else {
+                throw new UnexpectedCharacterException(literal, index);
+            }
+            index++;
+        }
+        return value;
+    }
+    
+    public final String format(T value) {
+        return value.toString();
+    }
+}
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/Util.java
similarity index 80%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/Util.java
index 900ce36..b244db7 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/Util.java
@@ -16,8 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+final class Util {
+    private Util() {}
+    
+    static boolean isWhitespace(char c) {
+        return c == ' ' || c == '\r' || c == '\n' || c == '\t';
+    }
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDate.java
similarity index 87%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDate.java
index 900ce36..45bef69 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDate.java
@@ -16,8 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+public interface XSDate extends Temporal {
+    XSDateTime getDayStart();
 }
diff --git a/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateImpl.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateImpl.java
new file mode 100644
index 0000000..4eb748a
--- /dev/null
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateImpl.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.datatype.xsd;
+
+final class XSDateImpl extends TemporalImpl implements XSDate {
+    private final boolean bc;
+    private final String aeon;
+    private final int year;
+    private final int month;
+    private final int day;
+    private final SimpleTimeZone timeZone;
+    
+    XSDateImpl(boolean bc, String aeon, int year, int month, int day, SimpleTimeZone timeZone) {
+        this.bc = bc;
+        this.aeon = aeon;
+        this.year = year;
+        this.month = month;
+        this.day = day;
+        this.timeZone = timeZone;
+    }
+
+    @Override
+    boolean hasDatePart() {
+        return true;
+    }
+
+    @Override
+    boolean hasTimePart() {
+        return false;
+    }
+
+    @Override
+    boolean isBC() {
+        return bc;
+    }
+
+    @Override
+    String getAeon() {
+        return aeon;
+    }
+
+    @Override
+    int getYear() {
+        return year;
+    }
+
+    @Override
+    int getMonth() {
+        return month;
+    }
+
+    @Override
+    int getDay() {
+        return day;
+    }
+
+    @Override
+    int getHour() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    int getMinute() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    int getSecond() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    int getNanoSecond() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    String getNanoSecondFraction() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    SimpleTimeZone getTimeZone() {
+        return timeZone;
+    }
+
+    public XSDateTime getDayStart() {
+        return new XSDateTimeImpl(bc, aeon, year, month, day, 0, 0, 0, 0, null, timeZone);
+    }
+}
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTime.java
similarity index 56%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTime.java
index 900ce36..dbf45db 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTime.java
@@ -16,8 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+import java.util.Date;
+import java.util.TimeZone;
+
+public interface XSDateTime extends Temporal {
+    XSDate getDate();
+    XSTime getTime();
+    
+    /**
+     * Convert this date/time to a {@link Date} object.
+     * 
+     * @param defaultTimeZone
+     *            the time zone to use if this date/time has no time zone; may be <code>null</code>
+     * @return the {@link Date} object
+     * @throws NoTimeZoneException
+     *             if this date/time doesn't have a time zone and no default time zone was specified
+     */
+    Date getDate(TimeZone defaultTimeZone);
 }
diff --git a/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTimeImpl.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTimeImpl.java
new file mode 100644
index 0000000..6d33144
--- /dev/null
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTimeImpl.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.datatype.xsd;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+final class XSDateTimeImpl extends TemporalImpl implements XSDateTime {
+    private final boolean bc;
+    private final String aeon;
+    private final int year;
+    private final int month;
+    private final int day;
+    private final int hour;
+    private final int minute;
+    private final int second;
+    private final int nanoSecond;
+    private final String nanoSecondFraction;
+    private final SimpleTimeZone timeZone;
+    
+    XSDateTimeImpl(boolean bc, String aeon, int year, int month, int day, int hour, int minute,
+            int second, int nanoSecond, String nanoSecondFraction, SimpleTimeZone timeZone) {
+        this.bc = bc;
+        this.aeon = aeon;
+        this.year = year;
+        this.month = month;
+        this.day = day;
+        this.hour = hour;
+        this.minute = minute;
+        this.second = second;
+        this.nanoSecond = nanoSecond;
+        this.nanoSecondFraction = nanoSecondFraction;
+        this.timeZone = timeZone;
+    }
+    
+    @Override
+    boolean hasDatePart() {
+        return true;
+    }
+
+    @Override
+    boolean hasTimePart() {
+        return true;
+    }
+
+    @Override
+    boolean isBC() {
+        return bc;
+    }
+
+    @Override
+    String getAeon() {
+        return aeon;
+    }
+
+    @Override
+    int getYear() {
+        return year;
+    }
+
+    @Override
+    int getMonth() {
+        return month;
+    }
+
+    @Override
+    int getDay() {
+        return day;
+    }
+
+    @Override
+    int getHour() {
+        return hour;
+    }
+
+    @Override
+    int getMinute() {
+        return minute;
+    }
+
+    @Override
+    int getSecond() {
+        return second;
+    }
+
+    @Override
+    int getNanoSecond() {
+        return nanoSecond;
+    }
+
+    @Override
+    String getNanoSecondFraction() {
+        return nanoSecondFraction;
+    }
+
+    @Override
+    SimpleTimeZone getTimeZone() {
+        return timeZone;
+    }
+
+    public XSDate getDate() {
+        return new XSDateImpl(bc, aeon, year, month, day, timeZone);
+    }
+
+    public XSTime getTime() {
+        return new XSTimeImpl(hour, minute, second, nanoSecond, nanoSecondFraction, timeZone);
+    }
+
+    public Date getDate(TimeZone defaultTimeZone) {
+        // TODO: fast path for dates in the 20th and 21st century
+        return getCalendar(defaultTimeZone).getTime();
+    }
+}
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTimeType.java
similarity index 79%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTimeType.java
index 900ce36..5b3cbb4 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTimeType.java
@@ -16,8 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+import org.apache.axiom.datatype.InvariantType;
+
+public interface XSDateTimeType extends InvariantType<XSDateTime> {
+    XSDateTimeType INSTANCE = new XSDateTimeTypeImpl();
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTimeTypeImpl.java
similarity index 55%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTimeTypeImpl.java
index 900ce36..8d19cda 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTimeTypeImpl.java
@@ -16,8 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+final class XSDateTimeTypeImpl extends TemporalType<XSDateTime> implements XSDateTimeType {
+    @Override
+    boolean hasDatePart() {
+        return true;
+    }
+
+    @Override
+    boolean hasTimePart() {
+        return true;
+    }
+
+    @Override
+    XSDateTime createInstance(boolean bc, String aeon, int year, int month, int day, int hour,
+            int minute, int second, int nanoSecond, String nanoSecondFraction,
+            SimpleTimeZone timeZone) {
+        return new XSDateTimeImpl(bc, aeon, year, month, day, hour, minute, second, nanoSecond,
+                nanoSecondFraction, timeZone);
+    }
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateType.java
similarity index 84%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateType.java
index 900ce36..e982540 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateType.java
@@ -16,8 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
+
+import org.apache.axiom.datatype.InvariantType;
+
+public interface XSDateType extends InvariantType<XSDate> {
 
-interface TextTransformer {
-    String transform(String in);
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTypeImpl.java
similarity index 59%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTypeImpl.java
index 900ce36..901a798 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSDateTypeImpl.java
@@ -16,8 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+final class XSDateTypeImpl extends TemporalType<XSDate> implements XSDateType {
+    @Override
+    boolean hasDatePart() {
+        return true;
+    }
+
+    @Override
+    boolean hasTimePart() {
+        return false;
+    }
+
+    @Override
+    XSDate createInstance(boolean bc, String aeon, int year, int month, int day, int hour,
+            int minute, int second, int nanoSecond, String nanoSecondFraction,
+            SimpleTimeZone timeZone) {
+        return new XSDateImpl(bc, aeon, year, month, day, timeZone);
+    }
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSQNameType.java
similarity index 78%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSQNameType.java
index 900ce36..c8497ac 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSQNameType.java
@@ -16,8 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+import javax.xml.namespace.QName;
+
+import org.apache.axiom.datatype.Type;
+
+public interface XSQNameType extends Type<QName> {
+    XSQNameType INSTANCE = new XSQNameTypeImpl();
 }
diff --git a/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSQNameTypeImpl.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSQNameTypeImpl.java
new file mode 100644
index 0000000..509dcca
--- /dev/null
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSQNameTypeImpl.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.datatype.xsd;
+
+import java.text.ParseException;
+
+import javax.xml.namespace.QName;
+
+import org.apache.axiom.datatype.ContextAccessor;
+import org.apache.axiom.datatype.UnexpectedCharacterException;
+import org.apache.axiom.datatype.UnexpectedEndOfStringException;
+
+final class XSQNameTypeImpl implements XSQNameType {
+    public <S,O> QName parse(String literal, ContextAccessor<S,O> contextAccessor, S contextObject, O options)
+            throws ParseException {
+        int len = literal.length();
+        int start = -1;
+        int end = -1;
+        int colonIndex = -1;
+        for (int index = 0; index<len; index++) {
+            char c = literal.charAt(index);
+            if (Util.isWhitespace(c)) {
+                if (start != -1 && end == -1) {
+                    end = index;
+                }
+            } else {
+                if (start == -1) {
+                    start = index;
+                } else if (end != -1) {
+                    throw new UnexpectedCharacterException(literal, index);
+                }
+                // TODO: we should check that the literal is a valid NCName
+                if (literal.charAt(index) == ':') {
+                    if (colonIndex != -1) {
+                        throw new UnexpectedCharacterException(literal, index);
+                    }
+                    colonIndex = index;
+                }
+            }
+        }
+        if (start == -1) {
+            throw new UnexpectedEndOfStringException(literal);
+        }
+        if (end == -1) {
+            end = len;
+        }
+        String prefix;
+        String localPart;
+        if (colonIndex == -1) {
+            prefix = "";
+            localPart = literal.toString();
+        } else {
+            prefix = literal.substring(start, colonIndex);
+            localPart = literal.substring(colonIndex+1, end);
+        }
+        String namespaceURI = contextAccessor.lookupNamespaceURI(contextObject, options, prefix);
+        if (namespaceURI == null) {
+            throw new ParseException("Unbound namespace prefix \"" + prefix + "\"", 0);
+        }
+        return new QName(namespaceURI, localPart, prefix);
+    }
+
+    public <S,O> String format(QName value, ContextAccessor<S,O> contextAccessor, S contextObject, O options) {
+        String prefix = value.getPrefix();
+        String namespaceURI = value.getNamespaceURI();
+        if (!namespaceURI.equals(contextAccessor.lookupNamespaceURI(contextObject, options, prefix))) {
+            String existingPrefix = contextAccessor.lookupPrefix(contextObject, options, namespaceURI);
+            if (existingPrefix != null) {
+                prefix = existingPrefix;
+            } else {
+                // TODO
+                throw new RuntimeException();
+            }
+        }
+        if (prefix.length() == 0) {
+            return value.getLocalPart();
+        } else {
+            return prefix + ":" + value.getLocalPart();
+        }
+    }
+}
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSStringType.java
similarity index 79%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSStringType.java
index 900ce36..068656e 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSStringType.java
@@ -16,8 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+import org.apache.axiom.datatype.InvariantType;
+
+public interface XSStringType extends InvariantType<String> {
+    XSStringType INSTANCE = new XSStringTypeImpl();
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSStringTypeImpl.java
similarity index 67%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSStringTypeImpl.java
index 900ce36..7c08cc6 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSStringTypeImpl.java
@@ -16,8 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+import java.text.ParseException;
+
+import org.apache.axiom.datatype.AbstractInvariantType;
+
+final class XSStringTypeImpl extends AbstractInvariantType<String> implements XSStringType {
+    public String parse(String literal) throws ParseException {
+        return literal;
+    }
+
+    public String format(String value) {
+        return value;
+    }
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTime.java
similarity index 89%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTime.java
index 900ce36..dda1343 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTime.java
@@ -16,8 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+public interface XSTime extends Temporal {
 }
diff --git a/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTimeImpl.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTimeImpl.java
new file mode 100644
index 0000000..5b2e347
--- /dev/null
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTimeImpl.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.datatype.xsd;
+
+final class XSTimeImpl extends TemporalImpl implements XSTime {
+    private final int hour;
+    private final int minute;
+    private final int second;
+    private final int nanoSecond;
+    private final String nanoSecondFraction;
+    private final SimpleTimeZone timeZone;
+    
+    XSTimeImpl(int hour, int minute, int second, int nanoSecond, String nanoSecondFraction,
+            SimpleTimeZone timeZone) {
+        this.hour = hour;
+        this.minute = minute;
+        this.second = second;
+        this.nanoSecond = nanoSecond;
+        this.nanoSecondFraction = nanoSecondFraction;
+        this.timeZone = timeZone;
+    }
+
+    @Override
+    boolean hasDatePart() {
+        return false;
+    }
+
+    @Override
+    boolean hasTimePart() {
+        return true;
+    }
+
+    @Override
+    boolean isBC() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    String getAeon() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    int getYear() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    int getMonth() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    int getDay() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    int getHour() {
+        return hour;
+    }
+
+    @Override
+    int getMinute() {
+        return minute;
+    }
+
+    @Override
+    int getSecond() {
+        return second;
+    }
+
+    @Override
+    int getNanoSecond() {
+        return nanoSecond;
+    }
+
+    @Override
+    String getNanoSecondFraction() {
+        return nanoSecondFraction;
+    }
+
+    @Override
+    SimpleTimeZone getTimeZone() {
+        return timeZone;
+    }
+}
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTimeType.java
similarity index 84%
copy from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
copy to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTimeType.java
index 900ce36..1d9c8ac 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTimeType.java
@@ -16,8 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
+
+import org.apache.axiom.datatype.InvariantType;
+
+public interface XSTimeType extends InvariantType<XSTime> {
 
-interface TextTransformer {
-    String transform(String in);
 }
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTimeTypeImpl.java
similarity index 57%
rename from testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
rename to datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTimeTypeImpl.java
index 900ce36..039945d 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/TextTransformer.java
+++ b/datatypes/src/main/java/org/apache/axiom/datatype/xsd/XSTimeTypeImpl.java
@@ -16,8 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.ts.soap;
+package org.apache.axiom.datatype.xsd;
 
-interface TextTransformer {
-    String transform(String in);
+final class XSTimeTypeImpl extends TemporalType<XSTime> implements XSTimeType {
+    @Override
+    boolean hasDatePart() {
+        return false;
+    }
+
+    @Override
+    boolean hasTimePart() {
+        return true;
+    }
+
+    @Override
+    XSTime createInstance(boolean bc, String aeon, int year, int month, int day, int hour,
+            int minute, int second, int nanoSecond, String nanoSecondFraction,
+            SimpleTimeZone timeZone) {
+        return new XSTimeImpl(hour, minute, second, nanoSecond, nanoSecondFraction, timeZone);
+    }
 }
diff --git a/datatypes/src/test/java/org/apache/axiom/datatype/DOMHelperTest.java b/datatypes/src/test/java/org/apache/axiom/datatype/DOMHelperTest.java
new file mode 100644
index 0000000..97e3fab
--- /dev/null
+++ b/datatypes/src/test/java/org/apache/axiom/datatype/DOMHelperTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.datatype;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.axiom.datatype.xsd.XSQNameType;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class DOMHelperTest {
+    @Test
+    public void testGetQNameFromElement() throws Exception {
+        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+        Element element = document.createElementNS("urn:test", "p:elem");
+        element.setTextContent("p:value");
+        QName qname = DOMHelper.getValue(element, XSQNameType.INSTANCE);
+        assertThat(qname.getNamespaceURI()).isEqualTo("urn:test");
+        assertThat(qname.getLocalPart()).isEqualTo("value");
+        assertThat(qname.getPrefix()).isEqualTo("p");
+    }
+}
diff --git a/datatypes/src/test/java/org/apache/axiom/datatype/xsd/XSDateTimeTypeTest.java b/datatypes/src/test/java/org/apache/axiom/datatype/xsd/XSDateTimeTypeTest.java
new file mode 100644
index 0000000..eb81dad
--- /dev/null
+++ b/datatypes/src/test/java/org/apache/axiom/datatype/xsd/XSDateTimeTypeTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.datatype.xsd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import java.util.Date;
+
+import org.apache.axiom.datatype.UnexpectedEndOfStringException;
+import org.junit.Test;
+
+public class XSDateTimeTypeTest {
+    @Test
+    public void testParse() throws Exception {
+        XSDateTime dateTime = XSDateTimeType.INSTANCE.parse("2003-12-13T18:30:02Z");
+        assertThat(dateTime.getDate(null)).isEqualTo(new Date(1071340202000L));
+    }
+    
+    @Test
+    public void testParseTruncated() throws Exception {
+        String literal = "2002-10-10T12:00:00-05:00";
+        for (int i=0; i<literal.length()-1; i++) {
+            if (i == 19) {
+                // This would give a valid literal without time zone
+                continue;
+            }
+            String truncatedLiteral = literal.substring(0, i);
+            try {
+                XSDateTimeType.INSTANCE.parse(truncatedLiteral);
+                fail("Expected UnexpectedEndOfStringException for literal \"" + truncatedLiteral + "\"");
+            } catch (UnexpectedEndOfStringException ex) {
+                // Expected
+            }
+        }
+    }
+}
diff --git a/implementations/pom.xml b/implementations/pom.xml
index 7c6572d..24a48e6 100644
--- a/implementations/pom.xml
+++ b/implementations/pom.xml
@@ -88,6 +88,7 @@
                     <artifactSet>
                         <includes>
                             <include>${project.groupId}:*-aspects</include>
+                            <include>${project.groupId}:datatypes</include>
                             <include>org.aspectj:aspectjrt</include>
                         </includes>
                     </artifactSet>
diff --git a/pom.xml b/pom.xml
index a145f0a..529056a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -223,6 +223,7 @@
         <module>testing</module>
         <module>aspects</module>
         <module>implementations</module>
+        <module>datatypes</module>
     </modules>
 
     <scm>
diff --git a/testing/soap-testsuite/pom.xml b/testing/soap-testsuite/pom.xml
index 1d299b4..658022a 100644
--- a/testing/soap-testsuite/pom.xml
+++ b/testing/soap-testsuite/pom.xml
@@ -45,6 +45,11 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>datatypes</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
         </dependency>
diff --git a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/ConvertedSOAPSampleContent.java b/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/ConvertedSOAPSampleContent.java
index 983487d..36063c9 100644
--- a/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/ConvertedSOAPSampleContent.java
+++ b/testing/soap-testsuite/src/main/java/org/apache/axiom/ts/soap/ConvertedSOAPSampleContent.java
@@ -20,6 +20,7 @@ package org.apache.axiom.ts.soap;
 
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.text.ParseException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -30,6 +31,8 @@ import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
 
+import org.apache.axiom.datatype.DOMHelper;
+import org.apache.axiom.datatype.xsd.XSQNameType;
 import org.apache.axiom.testing.multiton.Multiton;
 import org.apache.axiom.ts.xml.ComputedMessageContent;
 import org.w3c.dom.Attr;
@@ -40,15 +43,15 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
 final class ConvertedSOAPSampleContent extends ComputedMessageContent {
-    private static Map<String,String> faultCodeMap = new HashMap<String,String>();
+    private static Map<QName,QName> faultCodeMap = new HashMap<>();
     
     static {
         faultCodeMap.put(
-                SOAPSpec.SOAP12.getSenderFaultCode().getLocalPart(),
-                SOAPSpec.SOAP11.getSenderFaultCode().getLocalPart());
+                SOAPSpec.SOAP12.getSenderFaultCode(),
+                SOAPSpec.SOAP11.getSenderFaultCode());
         faultCodeMap.put(
-                SOAPSpec.SOAP12.getReceiverFaultCode().getLocalPart(),
-                SOAPSpec.SOAP11.getReceiverFaultCode().getLocalPart());
+                SOAPSpec.SOAP12.getReceiverFaultCode(),
+                SOAPSpec.SOAP11.getReceiverFaultCode());
     }
     
     private final SOAPSample soap12Message;
@@ -112,25 +115,17 @@ final class ConvertedSOAPSampleContent extends ComputedMessageContent {
                 child = nextChild;
             }
         } else if (type == SOAPFaultChild.CODE) {
-            final Element value = getChild(element, SOAPFaultChild.VALUE);
-            element.setTextContent(transform(value.getTextContent(), new TextTransformer() {
-                @Override
-                public String transform(String in) {
-                    int idx = in.indexOf(':');
-                    if (idx == -1) {
-                        return in;
-                    }
-                    String prefix = in.substring(0, idx);
-                    if (!SOAPSpec.SOAP12.getEnvelopeNamespaceURI().equals(value.lookupNamespaceURI(prefix))) {
-                        return in;
-                    }
-                    String newCode = faultCodeMap.get(in.substring(idx+1));
-                    if (newCode == null) {
-                        return in;
-                    }
-                    return prefix + ":" + newCode;
-                }
-            }));
+            Element value = getChild(element, SOAPFaultChild.VALUE);
+            String[] whitespace = getSurroundingWhitespace(value.getTextContent());
+            QName qname;
+            try {
+                qname = DOMHelper.getValue(value, XSQNameType.INSTANCE);
+            } catch (ParseException ex) {
+                throw new Error(ex);
+            }
+            QName newQName = faultCodeMap.get(qname);
+            DOMHelper.setValue(element, XSQNameType.INSTANCE, newQName == null ? qname : newQName);
+            element.setTextContent(reapplyWhitespace(element.getTextContent(), whitespace));
         } else if (type == SOAPFaultChild.REASON) {
             Element text = getChild(element, SOAPFaultChild.TEXT);
             element.setTextContent(text.getTextContent());
@@ -210,19 +205,19 @@ final class ConvertedSOAPSampleContent extends ComputedMessageContent {
         return " \r\n\t".indexOf(c) != -1;
     }
     
-    private static String transform(String text, TextTransformer transformer) {
+    private static String[] getSurroundingWhitespace(String text) {
         int start = 0;
         while (isWhitespace(text.charAt(start))) {
-            if (++start == text.length()) {
-                return text;
-            }
+            start++;
         }
         int end = text.length();
         while (isWhitespace(text.charAt(end-1))) {
             end--;
         }
-        return text.substring(0, start)
-                + transformer.transform(text.substring(start, end))
-                + text.substring(end);
+        return new String[] { text.substring(0, start), text.substring(end) };
+    }
+    
+    private static String reapplyWhitespace(String text, String[] surroundingWhitespace) {
+        return surroundingWhitespace[0] + text + surroundingWhitespace[1];
     }
 }