ISO Schematron is the tool for your problem. Just use the implementation for XPath 2.0 that allows data typing. Then you only have to test an assertion. Something like <assert test="@from < @to"/>
See : http://www.schematron.com/implementation.html
Concerning overlap : you can code an XSLT function that return a boolean in your schematron schema and call it from your test attribute.
Schematron with XSLT function embedded :
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://purl.oclc.org/dsdl/schematron" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fct="localFunctions" queryBinding="xslt2"
xmlns:xs="http://www.w3.org/2001/XMLSchema" >
<ns prefix="fct" uri="localFunctions"/>
<pattern>
<rule context="intervals">
<assert test="fct:checkOverlaping((),range)" >OVERLAPING</assert>
</rule>
</pattern>
<xsl:function name="fct:checkOverlaping" as="xs:boolean">
<xsl:param name="currentEndDate" as="xs:dateTime?"/>
<xsl:param name="ranges" as="node()*"/>
<xsl:choose>
<xsl:when test="not(exists($currentEndDate))">
<xsl:variable name="orderedRanges" as="node()*">
<xsl:for-each select="$ranges">
<xsl:sort select="@from"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="fct:checkOverlaping(xs:dateTime($orderedRanges[position()=1]/@to),$orderedRanges[position()>1])"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="
if (not(exists($ranges))) then true() else
if ($currentEndDate > $ranges[position()=1]/@from) then false() else
fct:checkOverlaping($ranges[position()=1]/@to,$ranges[position()>1])
"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
</schema>
Use the following implementation : http://www.schematron.com/tmp/iso-schematron-xslt2.zip which implements allow-foreign
and uses XPath 2.0. When this parameter allow-foreign
is set to true, the implementation will embed the function above.
How the function works : first loop (currentEndDate not set) the range
elements are sorted by from
date. Other recursive loop (after sorting) : test if the to
date of the first range is before the from date of the second date and go on for the next if it is true (otherwise false and stop).