Format a date in XML via XSLT

59,800

Solution 1

Here are a couple of 1.0 templates that you can use:-

<xsl:template name="formatDate">
    <xsl:param name="dateTime" />
    <xsl:variable name="date" select="substring-before($dateTime, 'T')" />
    <xsl:variable name="year" select="substring-before($date, '-')" />
    <xsl:variable name="month" select="substring-before(substring-after($date, '-'), '-')" />
    <xsl:variable name="day" select="substring-after(substring-after($date, '-'), '-')" />
    <xsl:value-of select="concat($day, ' ', $month, ' ', $year)" />
</xsl:template>

<xsl:template name="formatTime">
    <xsl:param name="dateTime" />
    <xsl:value-of select="substring-after($dateTime, 'T')" />
</xsl:template>

Call them with:-

    <xsl:call-template name="formatDate">
        <xsl:with-param name="dateTime" select="xpath" />
    </xsl:call-template>

and

    <xsl:call-template name="formatTime">
        <xsl:with-param name="dateTime" select="xpath" />
    </xsl:call-template>

where xpath is the path to an element or attribute that has the standard date time format.

Solution 2

Date formatting is not easy in XSLT 1.0. Probably the most elegant way is to write a short XSLT extension function in C# for date formatting. Here's an example:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                xmlns:myExtension="urn:myExtension"
                exclude-result-prefixes="msxsl myExtension">
  <xsl:output method="xml" indent="yes"/>

  <msxsl:script implements-prefix="myExtension" language="C#">
    <![CDATA[
      public string FormatDateTime(string xsdDateTime, string format)
      {
          DateTime date = DateTime.Parse(xsdDateTime);
          return date.ToString(format); 
      }

    ]]>
  </msxsl:script>

  <xsl:template match="date">
    <formattedDate>
      <xsl:value-of select="myExtension:FormatDateTime(self::node(), 'd')"/>
    </formattedDate>
  </xsl:template>
</xsl:stylesheet>

With this input document

<?xml version="1.0" encoding="utf-8"?>
<date>2007-11-14T12:01:00</date>

you will get

<?xml version="1.0" encoding="utf-8"?>
<formattedDate>14.11.2007</formattedDate> 

The function formatting the date takes a date value as string and a format as described in DateTime.ToString Method. Using .NET's DateTime struct gives you parsing arbitrary XSD datetime values (including time zone specifiers), timezone calculation and localized output for free.

However, be aware that there is one caveat (http://support.microsoft.com/kb/316775) with msxml script extensions: Each time you load the XSLT an assembly containing the script code is generated dynamically and loaded into memory. Due to the design of the .NET runtime, this assembly cannot be unloaded. That's why you have to make sure that your XSLT is only loaded once (and then cached for further re-use). This is especially important when running inside IIS.

Solution 3

John Workman discusses this issue at length and gives several solutions in this discussion[1] on his blog. Basically, parse the individual date components and recombine in whatever order you wish. For your case, a pure XSLT 1.0+ version would be:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="date">
<!-- converts FROM <date>2001-12-31T12:00:00</date> TO some new format (DEFINED below) -->
<xsl:template name="FormatDate">
<xsl:param name="DateTime" />

<xsl:variable name="year" select="substring($DateTime,1,4)" />
<xsl:variable name="month-temp" select="substring-after($DateTime,'-')" />
<xsl:variable name="month" select="substring-before($month-temp,'-')" />
<xsl:variable name="day-temp" select="substring-after($month-temp,'-')" />
<xsl:variable name="day" select="substring($day-temp,1,2)" />
<xsl:variable name="time" select="substring-after($DateTime,'T')" />
<xsl:variable name="hh" select="substring($time,1,2)" />
<xsl:variable name="mm" select="substring($time,4,2)" />
<xsl:variable name="ss" select="substring($time,7,2)" />

<!-- EUROPEAN FORMAT -->
<xsl:value-of select="$day"/>
<xsl:value-of select="'.'"/> <!--18.-->
<xsl:value-of select="$month"/>
<xsl:value-of select="'.'"/> <!--18.03.-->
<xsl:value-of select="$year"/>
<xsl:value-of select="' '"/> <!--18.03.1976 -->
<xsl:value-of select="$hh"/>
<xsl:value-of select="':'"/> <!--18.03.1976 13: -->
<xsl:value-of select="$mm"/>
<xsl:value-of select="':'"/> <!--18.03.1976 13:24 -->
<xsl:value-of select="$ss"/> <!--18.03.1976 13:24:55 -->
<!-- END: EUROPEAN FORMAT -->

</xsl:template>

Another format (REPLACEs the EUROPEAN FORMAT section):

<!-- Long DATE FORMAT -->
<xsl:choose>
<xsl:when test="$month = '1' or $month= '01'">January</xsl:when>
<xsl:when test="$month = '2' or $month= '02'">February</xsl:when>
<xsl:when test="$month= '3' or $month= '03'">March</xsl:when>
<xsl:when test="$month= '4' or $month= '04'">April</xsl:when>
<xsl:when test="$month= '5' or $month= '05'">May</xsl:when>
<xsl:when test="$month= '6' or $month= '06'">June</xsl:when>
<xsl:when test="$month= '7' or $month= '07'">July</xsl:when>
<xsl:when test="$month= '8' or $month= '08'">August</xsl:when>
<xsl:when test="$month= '9' or $month= '09'">September</xsl:when>
<xsl:when test="$month= '10'">October</xsl:when>
<xsl:when test="$month= '11'">November</xsl:when>
<xsl:when test="$month= '12'">December</xsl:when>
</xsl:choose> 
<xsl:value-of select="' '"/> <!--January -->
<xsl:value-of select="$day"/> <!--January 12 -->
<xsl:value-of select="','"/> <!--January 12,-->
<xsl:value-of select="' '"/> <!--January 12, -->
<xsl:value-of select="$year"/> <!--January 12, 2001-->
<!-- END: Long DATE FORMAT -->

You can recombine the elements in any way you choose.

[1] http://geekswithblogs.net/workdog/archive/2007/02/08/105858.aspx @@ http://archive.is/4Hjep

Solution 4

Apologies for commenting on this old thread but for others finding it like me you could also use javascript if you are using an MS transformer:

Declare the "msxsl" namespace:

xmlns:msxsl="urn:schemas-microsoft-com:xslt" 

Declare a namespace for your script:

xmlns:js="urn:custom-javascript" 

(Optional) Omit the prefixes from the output:

exclude-result-prefixes="msxsl js" 

So you end up with an xsl declaration like this:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  xmlns:js="urn:custom-javascript"
  exclude-result-prefixes="msxsl js">

Write the JavaScript in the msxsl:script element:

<msxsl:script language="JavaScript" implements-prefix="js"> 
<![CDATA[ 
function javascriptFunction(dateValue){
  var date = new Date(dateValue);
  if(!isNaN(date)) return date.toLocaleString();
  return dateValue;
}
]]>
</msxsl:script>

Call your JavaScript function (using the XPath syntax '.' denoting 'this node'):

<xsl:value-of select="js:javascriptFunction(string(.))"/>

NB: As of writing there doesn't seem to be an (xsl) way to include external js files (eg. jquery library). This could be done by parsing the xsl file server side before the transformation and adding the js file contents as a string into a CDATA section. I started to go down this route myself but concluded that if you need this level of functionality it might be better placed in a different part of the pipeline.

source: http://dev.ektron.com/kb_article.aspx?id=482
ref: http://www.ibm.com/developerworks/xml/library/x-tipxsltjs/index.html

Solution 5

correction to roy's post: the day from the function will always get the month value. Use the following:

<xsl:variable name="year" select="substring($dateTime,1,4)" />
<xsl:variable name="month-temp" select="substring-after($dateTime,'-')" />
<xsl:variable name="month" select="substring-before($month-temp,'-')" />
<xsl:variable name="day-temp" select="substring-after($month-temp,'-')" />
<xsl:variable name="day" select="substring($day-temp,1,2)" />
<xsl:variable name="time" select="substring-after($dateTime,'T')" />
<xsl:variable name="hh" select="substring($time,1,2)" />
<xsl:variable name="mm" select="substring($time,4,2)" />
<xsl:variable name="ss" select="substring($time,7,2)" />

<xsl:value-of select="concat($month,'/',$day,'/',$year,' ',$hh,':',$mm,':',$ss)" />

Share:
59,800
peterchen
Author by

peterchen

Updated on April 02, 2020

Comments

  • peterchen
    peterchen about 4 years

    When I use the XML serializer to serialize a DateTime, it is written in the following format:

    <Date>2007-11-14T12:01:00</Date>
    

    When passing this through an XSLT stylesheet to output HTML, how can I format this? In most cases I just need the date, and when I need the time I of course don't want the "funny T" in there.

  • Cerebrus
    Cerebrus over 15 years
    Yep, that's almost identical to the method I use!
  • Dirk Vollmar
    Dirk Vollmar over 15 years
    Just curious about the downvote: Is there a technical reason? Or just personal dislike of the approach?
  • Dirk Vollmar
    Dirk Vollmar over 15 years
    The issue is the one I mentioned and in practice usually can easily be worked around. Loading XSLT only once is good practice anyway for performance reasons. XSLT extension objects have the strong disadvantage (at least up to now) that they use late-binding-calls and therefore are terribly slow.
  • Dirk Vollmar
    Dirk Vollmar over 15 years
    (continued) AnthonyW has in my opinion too a very elegant (pure) XSLT solution, however supporting different date formats is a little more work since you don't get all the .NET date time stuff for free
  • Martijn Laarman
    Martijn Laarman over 15 years
    Your absolutely right caching does prevent this. I read guidanceshare.com/wiki/… some time back and read "Avoid using inline script." while neglecting to memorize the remainder of the line. [cont]
  • Martijn Laarman
    Martijn Laarman over 15 years
    googling for "XSLT Extension Objects vs msxl:script" or similar always gives back results stating XSLT Extension Objects as the cleaner, elegant and fastest solution whilst i still agree on the first two you've made me give up on the latter view :)
  • peterchen
    peterchen over 15 years
    Thanks for the answer (+1) - I am going with Anthony's suggestion, it seems to be the smaller wormcan :)
  • Dirk Vollmar
    Dirk Vollmar over 15 years
    @martijn:I think too that extension objects are cleaner and more elegant, however they are slower than inline script. Reason: script is available at compile time and so the compiler generates direct calls,while extension objects are only available at runtime and so can only be called via reflection.
  • Dirk Vollmar
    Dirk Vollmar over 15 years
    (continued) This is mentioned in Tkachenko's blog article that you first posted.
  • Martijn Laarman
    Martijn Laarman over 15 years
    Yeah i did think they were faster but wasnt sure enough to put it as an argument for Extension objects. I love to be proven wrong so thanks for doing just that :)
  • Ryan
    Ryan over 12 years
    XSLT sucks. Your solution is elegant, but surely we shouldn't be crafting date formatting routines by hand.
  • AnthonyWJones
    AnthonyWJones over 12 years
    @Ryan: I agree and XSLT 2 has much better support for date handling. Unfortunately there is very thin support for it in the installed base of HTML Browsers even now.
  • erik
    erik almost 12 years
    I would like to throw you a nice comment here. Your code just saved me a helluva headache.
  • J. M. Becker
    J. M. Becker over 11 years
    @AnthonyWJones: That's a serious understatement, XSLT 2.0 is very thin outside of dynamic languages. Majority of which are Java, and some .NET. We have no libXSLT for XSLT 2.0, which would otherwise bring XSLT to the a handful of browsers. Once a FOSS and efficient C/C++ XSLT 2.0 library exists, with reasonably minimal cross-platform dependencies, We'll see browser support.