views:

4662

answers:

3

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

2007-11-14T12:01:00

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.

+14  A: 

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.

0xA3
Yep, that's almost identical to the method I use!
Cerebrus
Just curious about the downvote: Is there a technical reason? Or just personal dislike of the approach?
0xA3
I downvoted because msxsl:script is not needed (See AnthonyW's post which is the most elegant solution) and has serious downsides to it: http://www.tkachenko.com/blog/archives/000620.html. XSLT Extension Objects are far more preferable to create custom XSLT functions in .NET try it out :)
Martijn Laarman
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.
0xA3
(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
0xA3
Your absolutely right caching does prevent this. I read http://www.guidanceshare.com/wiki/XML_(.NET_1.1)_Performance_Guidelines_-_XSLT_Processing some time back and read "Avoid using inline script." while neglecting to memorize the remainder of the line. [cont]
Martijn Laarman
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 :)
Martijn Laarman
Thanks for the answer (+1) - I am going with Anthony's suggestion, it seems to be the smaller wormcan :)
peterchen
@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.
0xA3
(continued) This is mentioned in Tkachenko's blog article that you first posted.
0xA3
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 :)
Martijn Laarman
+13  A: 

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.

AnthonyWJones
The most elegant solution posted, hope this makes it as answer :)
Martijn Laarman
Awesome! Thanks
Neil
+2  A: 

John Workman discusses this issue at length and gives several solutions in this discussion 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"&gt;
<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($month-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.

Roy