Tuesday, June 23, 2009

XSLT 1.0 dateTime manipulation

Lately, I was working on some transformations in AmberPoint (a SOA Management tool for web services governance and a lightweight ESB because we can do lot of transformations and other ESB related tasks apart from Web Service Management). These transformations requires to manipulate dateTime from 2009-06-23T00:00:00Z pattern to 2009-06-23T00:00:00Z+X minutes. We were trying to integrate with a trading partner who has .NET and requires timestamp signed and as part of timestamp, creation datetime and expiration datetime which is typically creation datetime + X minutes needed.

Any system that supports XPath 2.0, this can be achieved very easily with built-in functions. Ex: Adding minutes/hours/days..etc to existing dateTime is as simple as "add-dayTimeDuration-to-dateTime($arg1 as xs:dateTime, $arg2 as xs:dayTimeDuration)" (it is just one way). Believe it or not the similar thing in XPath 1.0 takes more than hundred lines of code. While XPath 2.0 supports rich set of time, datetime manipulation features, XPath 1.0 just ignores these functions but supports string manipulation functions. Since the barebones string manipulation is allowed, though it will be cumbersome, one can write such functionality.

The following example takes the input in the format "2009-12-31T00:00:59-06:00" and then converts it to "2009-12-31T06:00:59Z" after that it adds "5" minutes to the time to make it "2009-12-31T06:05:59Z"

Here is the XSL (XSLT-DateTime.xsl)

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:for-each select="sales/summary">

old time <xsl:value-of select="date" />


<xsl:variable name="dt">
<xsl:call-template name="makeGMTTime">
<xsl:with-param name="DateTime" select="date"/>
</xsl:call-template>
</xsl:variable>
gmt time <xsl:value-of select="$dt"/>


exp time <xsl:call-template name="addMinutes">
<xsl:with-param name="DateTime" select="$dt"/>
<xsl:with-param name="deltaMinutes" select="number(5)"/>
</xsl:call-template>





</xsl:for-each>
</xsl:template>

<xsl:template name="makeGMTTime">
<xsl:param name="DateTime"/>
<xsl:variable name="year">
<xsl:value-of select="substring($DateTime,1,4)"/>
</xsl:variable>
<xsl:variable name="month">
<xsl:value-of select="substring($DateTime,6,2)"/>
</xsl:variable>
<xsl:variable name="day">
<xsl:value-of select="substring($DateTime,9,2)"/>
</xsl:variable>
<xsl:variable name="hours">
<xsl:value-of select="substring($DateTime,12,2)"/>
</xsl:variable>
<xsl:variable name="minutes">
<xsl:value-of select="substring($DateTime,15,2)"/>
</xsl:variable>
<xsl:variable name="seconds">
<xsl:value-of select="substring($DateTime,18,2)"/>
</xsl:variable>
<xsl:variable name="deltahours">
<xsl:value-of select="substring($DateTime,21,2)"/>
</xsl:variable>
<xsl:variable name="thours">
<xsl:value-of select="$hours + $deltahours"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$thours > 23">
<xsl:variable name="nhours">
<xsl:call-template name="resetHours">
<xsl:with-param name="hours" select="$thours"/>
</xsl:call-template>
</xsl:variable>

<xsl:variable name="ldayofmonth">
<xsl:call-template name="last-day-of-month">
<xsl:with-param name="month" select="$month"/>
<xsl:with-param name="year" select="$year"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$day = $ldayofmonth">
<xsl:variable name="nday">
<xsl:value-of select="concat('0','1')"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$month + 1 > 12">
<xsl:variable name="nmonth">
<xsl:value-of select="concat('0','1')"/>
</xsl:variable>
<xsl:variable name="nyear">
<xsl:value-of select="$year + 1"/>
</xsl:variable>
<xsl:value-of
select="concat($nyear,'-',$nmonth,'-',$nday,'T',$nhours,':',$minutes,':',$seconds,'Z')"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="nmonth">
<xsl:call-template name="resetMonths">
<xsl:with-param name="months" select="$month+1"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="nyear">
<xsl:value-of select="$year"/>
</xsl:variable>
<xsl:value-of
select="concat($nyear,'-',$nmonth,'-',$nday,'T',$nhours,':',$minutes,':',$seconds,'Z')"/>
</xsl:otherwise>
</xsl:choose>

</xsl:when>
<xsl:otherwise>
<xsl:variable name="nday">

<xsl:call-template name="resetDays">
<xsl:with-param name="days" select="$day+1"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="nmonth">
<xsl:value-of select="$month"/>
</xsl:variable>
<xsl:variable name="nyear">
<xsl:value-of select="$year"/>
</xsl:variable>
<xsl:value-of
select="concat($nyear,'-',$nmonth,'-',$nday,'T',$nhours,':',$minutes,':',$seconds,'Z')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="nhours">
<xsl:call-template name="resetHours">
<xsl:with-param name="hours" select="$thours"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="nday">
<xsl:value-of select="$day"/>
</xsl:variable>
<xsl:variable name="nmonth">
<xsl:value-of select="$month"/>
</xsl:variable>
<xsl:variable name="nyear">
<xsl:value-of select="$year"/>
</xsl:variable>
<xsl:value-of
select="concat($nyear,'-',$nmonth,'-',$nday,'T',$nhours,':',$minutes,':',$seconds,'Z')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="addMinutes">
<xsl:param name="DateTime" />
<xsl:param name="deltaMinutes" />

<xsl:variable name="year">
<xsl:value-of select="substring($DateTime,1,4)" />
</xsl:variable>
<xsl:variable name="month">
<xsl:value-of select="substring($DateTime,6,2)" />
</xsl:variable>
<xsl:variable name="day">
<xsl:value-of select="substring($DateTime,9,2)" />
</xsl:variable>
<xsl:variable name="hours">
<xsl:value-of select="substring($DateTime,12,2)" />
</xsl:variable>
<xsl:variable name="minutes">
<xsl:value-of select="substring($DateTime,15,2)" />
</xsl:variable>
<xsl:variable name="seconds">
<xsl:value-of select="substring($DateTime,18,2)" />
</xsl:variable>
<xsl:variable name="adjhours">
<xsl:value-of select="substring($DateTime,21,2)" />
</xsl:variable>
<xsl:choose>
<xsl:when test="$minutes + $deltaMinutes > 59">
<xsl:variable name="tmins">
<xsl:value-of select="$minutes + $deltaMinutes - 60" />
</xsl:variable>
<xsl:variable name="nmins">
<xsl:value-of select="concat('0',string($tmins))" />
</xsl:variable>
<xsl:choose>
<xsl:when test="$hours = 23">
<xsl:variable name="nhours">
<xsl:value-of select="concat('0','0')" />
</xsl:variable>
<xsl:variable name="ldayofmonth">
<xsl:call-template name="last-day-of-month">
<xsl:with-param name="month" select="$month"/>
<xsl:with-param name="year" select="$year"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$day = $ldayofmonth">
<xsl:variable name="nday">
<xsl:value-of select="concat('0','1')" />
</xsl:variable>
<xsl:choose>
<xsl:when test="$month + 1 > 12">
<xsl:variable name="nmonth">
<xsl:value-of select="concat('0','1')" />
</xsl:variable>
<xsl:variable name="nyear">
<xsl:value-of select="$year + 1" />
</xsl:variable>
<xsl:value-of select="concat($nyear,'-',$nmonth,'-',$nday,'T',$nhours,':',$nmins,':',$seconds,'Z')"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="nmonth">
<xsl:call-template name="resetMonths">
<xsl:with-param name="months" select="$month + 1"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="nyear">
<xsl:value-of select="$year" />
</xsl:variable>
<xsl:value-of select="concat($nyear,'-',$nmonth,'-',$nday,'T',$nhours,':',$nmins,':',$seconds,'Z')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="nday">
<xsl:value-of select="$day + 1" />
</xsl:variable>
<xsl:variable name="nmonth">
<xsl:value-of select="$month" />
</xsl:variable>
<xsl:variable name="nyear">
<xsl:value-of select="$year" />
</xsl:variable>
<xsl:value-of select="concat($nyear,'-',$nmonth,'-',$nday,'T',$nhours,':',$nmins,':',$seconds,'Z')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="$hours < 8">
<xsl:variable name="nhours">
<xsl:value-of select="concat('0',string($hours + 1))" />
</xsl:variable>
<xsl:variable name="nday">
<xsl:value-of select="$day" />
</xsl:variable>
<xsl:variable name="nmonth">
<xsl:value-of select="$month" />
</xsl:variable>
<xsl:variable name="nyear">
<xsl:value-of select="$year" />
</xsl:variable>
<xsl:value-of select="concat($nyear,'-',$nmonth,'-',$nday,'T',$nhours,':',$nmins,':',$seconds,'Z')"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="nhours">
<xsl:value-of select="$hours + 1" />
</xsl:variable>
<xsl:variable name="nday">
<xsl:value-of select="$day" />
</xsl:variable>
<xsl:variable name="nmonth">
<xsl:value-of select="$month" />
</xsl:variable>
<xsl:variable name="nyear">
<xsl:value-of select="$year" />
</xsl:variable>
<xsl:value-of select="concat($nyear,'-',$nmonth,'-',$nday,'T',$nhours,':',$nmins,':',$seconds,'Z')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="nmins">
<xsl:call-template name="resetMinutes">
<xsl:with-param name="minutes" select="$minutes + $deltaMinutes"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="nhours">
<xsl:value-of select="$hours" />
</xsl:variable>
<xsl:variable name="nday">
<xsl:value-of select="$day" />
</xsl:variable>
<xsl:variable name="nmonth">
<xsl:value-of select="$month" />
</xsl:variable>
<xsl:variable name="nyear">
<xsl:value-of select="$year" />
</xsl:variable>
<xsl:value-of select="concat($nyear,'-',$nmonth,'-',$nday,'T',$nhours,':',$nmins,':',$seconds,'Z')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="last-day-of-month">
<xsl:param name="month"/>
<xsl:param name="year"/>
<xsl:choose>
<xsl:when test="$month = 2 and
not($year mod 4) and
($year mod 100 or not($year mod 400))">
<xsl:value-of select="29"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of
select="substring('312831303130313130313031',
2 * $month - 1,2)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

<xsl:template name="resetHours">
<xsl:param name="hours"/>
<xsl:choose>
<xsl:when test="$hours = 24">
<xsl:value-of select="concat('0','0')"/>
</xsl:when>
<xsl:when test="$hours > 24">
<xsl:value-of select="concat('0',string($hours - 24))"/>
</xsl:when>
<xsl:when test="$hours < 10">
<xsl:value-of select="concat('0',string($hours))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$hours"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="resetDays">
<xsl:param name="days"/>
<xsl:choose>
<xsl:when test="$days < 10">
<xsl:value-of select="concat('0',string($days))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$days"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="resetMinutes">
<xsl:param name="minutes"/>
<xsl:choose>
<xsl:when test="$minutes < 10">
<xsl:value-of select="concat('0',string($minutes))"/>
</xsl:when>
<xsl:when test="$minutes > 60 ">
<xsl:value-of select="concat('0',string($minutes - 60))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$minutes"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="resetMonths">
<xsl:param name="months"/>
<xsl:choose>
<xsl:when test="$months > 12">
<xsl:value-of select="concat('0',string($months - 12))"/>
</xsl:when>
<xsl:when test="$months < 10">
<xsl:value-of select="concat('0',string($months))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$months"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Here is the XML for testing it


<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="XSLT-DateTime.xsl"?>
<sales>
<summary>
<date>2009-12-31T00:00:59-06:00</date>
</summary>
<summary>
<date>2009-12-31T18:00:59-06:00</date>
</summary>
<summary>
<date>2009-12-31T00:10:59-04:00</date>
</summary>
<summary>
<date>2009-12-31T00:08:59-05:00</date>
</summary>
<summary>
<date>2009-12-31T01:01:59-04:00</date>
</summary>
<summary>
<date>2009-12-31T07:01:59-04:00</date>
</summary>
<summary>
<date>2009-12-31T05:56:59-04:00</date>
</summary>
<summary>
<date>2009-12-31T23:55:59-04:00</date>
</summary>
<summary>
<date>2012-02-28T23:55:59-04:00</date>
</summary>
<summary>
<date>2009-12-31T19:55:59-04:00</date>
</summary>
<summary>
<date>2012-02-28T07:06:59-04:00</date>
</summary>
<summary>
<date>2012-02-28T19:57:59-04:00</date>
</summary>
<summary>
<date>2012-02-29T19:57:59-04:00</date>
</summary>
<summary>
<date>2012-01-31T19:57:59-04:00</date>
</summary>
<summary>
<date>2012-03-31T19:57:59-04:00</date>
</summary>


<summary>
<date>2012-04-30T19:57:59-04:00</date>
</summary>
<summary>
<date>2012-05-31T19:57:59-04:00</date>
</summary>
<summary>
<date>2012-06-30T19:57:59-04:00</date>
</summary>
<summary>
<date>2012-07-31T19:57:59-04:00</date>
</summary>
<summary>
<date>2012-08-31T19:57:59-04:00</date>
</summary>
<summary>
<date>2012-09-30T19:57:59-04:00</date>
</summary>
<summary>
<date>2012-10-31T19:57:59-04:00</date>
</summary>
<summary>
<date>2012-11-30T19:57:59-04:00</date>
</summary>
<summary>
<date>2012-12-31T19:57:59-04:00</date>
</summary>
</sales>

You are free to use, refactor, distribute the above code without any limitations.

References:
Determining the Last Day of the Month: http://my.safaribooksonline.com/0596009747/xsltckbk2-CHP-4-SECT-4#X2ludGVybmFsX1NlY3Rpb25Db250ZW50P3htbGlkPTA1OTYwMDk3NDcveHNsdGNrYmsyLUNIUC00LVNFQ1QtMg==