Format a date in XML via XSLT
Asked Answered
H

9

41

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.

Hogle answered 1/2, 2009 at 14:28 Comment(3)
Its very important to state your version and XSLT platformAmyloid
It's most likely XSLT 1.0 and .NET because the question is tagged with C#Calliecalligraphy
Have you tried to use the buil-in function? msdn.microsoft.com/en-us/library/ms256099.aspxTasso
A
74

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.

Amyloid answered 1/2, 2009 at 15:1 Comment(3)
XSLT sucks. Your solution is elegant, but surely we shouldn't be crafting date formatting routines by hand.Nonstop
@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.Amyloid
@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.Becky
C
26

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.

Calliecalligraphy answered 1/2, 2009 at 14:50 Comment(11)
Yep, that's almost identical to the method I use!Rifkin
Just curious about the downvote: Is there a technical reason? Or just personal dislike of the approach?Calliecalligraphy
I downvoted because msxsl:script is not needed (See AnthonyW's post which is the most elegant solution) and has serious downsides to it: tkachenko.com/blog/archives/000620.html. XSLT Extension Objects are far more preferable to create custom XSLT functions in .NET try it out :)Dragnet
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.Calliecalligraphy
(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 freeCalliecalligraphy
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]Dragnet
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 :)Dragnet
Thanks for the answer (+1) - I am going with Anthony's suggestion, it seems to be the smaller wormcan :)Hogle
@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.Calliecalligraphy
(continued) This is mentioned in Tkachenko's blog article that you first posted.Calliecalligraphy
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 :)Dragnet
I
9

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] <Link> @@ http://archive.is/4Hjep

Indefinite answered 1/2, 2009 at 15:12 Comment(1)
I would like to throw you a nice comment here. Your code just saved me a helluva headache.Kierstenkieselguhr
M
5

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

Misdoing answered 9/3, 2012 at 6:28 Comment(0)
G
3

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)" />

Grantor answered 24/11, 2010 at 17:31 Comment(0)
S
3

Thanks, this post helped a lot.

I was transforming an RSS feed which uses the following date format: Mon, 04 Apr 2011 23:18:00 -0700. Here is the named template I used to parse it.

<!--Parse date format: Mon, 04 Apr 2011 23:18:00 -0700-->
<xsl:template name="formatDate">

    <xsl:param name="dateIn" />

    <xsl:variable name="day" select="substring($dateIn, 0, 3)" />
    <xsl:variable name="date" select="substring($dateIn, 6, 2)" />
    <xsl:variable name="month" select="substring($dateIn, 9, 3)" />
    <xsl:variable name="year" select="substring($dateIn, 13, 4)" />

    <xsl:variable name="hour" select="substring($dateIn, 18, 2)" />
    <xsl:variable name="min" select="substring($dateIn, 21, 2)" />
    <xsl:variable name="sec" select="substring($dateIn, 24, 2)" />

    <xsl:value-of select="concat($date, ' ', $month, ' ', $year, ' ', $hour, ':', $min, ':', $sec)" />

</xsl:template>
Slemmer answered 5/4, 2011 at 22:25 Comment(0)
K
1
<xsl:template match="date">
     <xsl:copy>
         <xsl:call-template name="formatdate">
             <xsl:with-param name="DateTimeStr" select="."/>
        </xsl:call-template>
     </xsl:copy>
  </xsl:template>

  <xsl:template name="formatdate">
     <xsl:param name="DateTimeStr" />

     <!-- input format xslt datetime string -->
     <!-- output format mm/dd/yyyy -->

     <xsl:variable name="datestr">
         <xsl:value-of select="substring-before($DateTimeStr,'T')" />
     </xsl:variable>

     <xsl:variable name="mm">
         <xsl:value-of select="substring($datestr,6,2)" />
     </xsl:variable>

     <xsl:variable name="dd">
        <xsl:value-of select="substring($datestr,9,2)" />
     </xsl:variable>

     <xsl:variable name="yyyy">
        <xsl:value-of select="substring($datestr,1,4)" />
     </xsl:variable>

     <xsl:value-of select="concat($mm,'/', $dd, '/', $yyyy)" />
  </xsl:template>

This worked for me. You can check other options at :

https://blog.fpmurphy.com/2008/05/xslt-datetime-formatting.html

Korney answered 1/4, 2020 at 10:12 Comment(0)
W
0

With XSLT2 lib, you can

  • parse the text : get the dateTime format with xs:dateTime(text())
  • format the dateTime format with the XSLT 2 function : format-dateTime [*]

[*] https://www.w3.org/TR/xslt20/#function-format-dateTime

With your XML sample

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

The XSL snippet

<p>My date : <xsl:value-of select="format-dateTime(xs:dateTime(text()), '[Y0001]-[M01]-[D01] [H01]:[m]:[s]')"/></p>

The result is

<p>My date : 2007-11-14 12:01:00</p>
Warlord answered 28/4, 2022 at 16:1 Comment(0)
M
-1

Me and my brother wrote this, and it helped me. I figured I would post for the next person. Enjoy!

Example Image

<xsl:template name="timestamp">
    <!-- transform an ISO 8601 timestamp into a human readable representation -->
    <xsl:param name="iso-timestamp" />
    <!-- converts FROM <date>2001-12-31T12:00:00</date> TO some new format (DEFINED below) -->

    <xsl:variable name="year" select="substring($iso-timestamp,1,4)" />
    <xsl:variable name="month-temp" select="substring-after($iso-timestamp,'-')" />
    <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($iso-timestamp,'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: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="' '"/>
    <xsl:value-of select="$day"/> 
    <xsl:value-of select="','"/> 
    <xsl:value-of select="' '"/> 
    <xsl:value-of select="$year"/> 
    <xsl:value-of select="' '"/>
    <xsl:choose>
        <xsl:when test="$hh = '0' or $hh= '12'">01</xsl:when>
        <xsl:when test="$hh = '1' or $hh= '01'">01</xsl:when>
        <xsl:when test="$hh = '2' or $hh= '02'">02</xsl:when>
        <xsl:when test="$hh= '3' or $hh= '03'">03</xsl:when>
        <xsl:when test="$hh= '4' or $hh= '04'">04</xsl:when>
        <xsl:when test="$hh= '5' or $hh= '05'">05</xsl:when>
        <xsl:when test="$hh= '6' or $hh= '06'">06</xsl:when>
        <xsl:when test="$hh= '7' or $hh= '07'">07</xsl:when>
        <xsl:when test="$hh= '8' or $hh= '08'">08</xsl:when>
        <xsl:when test="$hh= '9' or $hh= '09'">09</xsl:when>
        <xsl:when test="$hh= '10'">10</xsl:when>
        <xsl:when test="$hh= '11'">11</xsl:when>
        <xsl:when test="$hh= '12'">12</xsl:when>
        <xsl:when test="$hh= '13'">01</xsl:when>
        <xsl:when test="$hh= '14'">02</xsl:when>
        <xsl:when test="$hh= '15'">03</xsl:when>
        <xsl:when test="$hh= '16'">04</xsl:when>
        <xsl:when test="$hh= '17'">05</xsl:when>
        <xsl:when test="$hh= '18'">06</xsl:when>
        <xsl:when test="$hh= '19'">07</xsl:when>
        <xsl:when test="$hh= '20'">08</xsl:when>
        <xsl:when test="$hh= '21'">09</xsl:when>
        <xsl:when test="$hh= '22'">10</xsl:when>
        <xsl:when test="$hh= '23'">11</xsl:when>
    </xsl:choose>
    <xsl:value-of select="':'"/>
    <xsl:value-of select="$mm"/>
    <xsl:value-of select="':'"/>
    <xsl:value-of select="$ss"/>
    <xsl:value-of select="'  '"/>
    <xsl:choose>
        <xsl:when test="$hh = '0' or $hh = '00' or $hh= '1' or $hh= '01' or $hh = '2' or $hh = '02' or $hh = '3' or $hh = '03' or $hh = '4' or $hh = '04' or $hh = '5' or $hh = '05' or $hh = '6' or $hh = '06' or $hh = '7' or $hh = '07' or $hh = '8' or $hh = '08' or $hh = '9' or $hh = '09' or $hh = '10' or $hh = '11'">AM</xsl:when>
        <xsl:when test="$hh = '12' or $hh= '13' or $hh = '14' or $hh = '15' or $hh = '16' or $hh = '17' or $hh = '18' or $hh = '19' or $hh = '20' or $hh = '21' or $hh = '22' or $hh = '23'">PM</xsl:when>
    </xsl:choose>
    <!-- <xsl:value-of select="concat(substring($iso-timestamp, 0, 11), ' ', substring($iso-timestamp, 12, 5))" /> -->
</xsl:template>
Myatt answered 8/4, 2022 at 0:45 Comment(2)
This is not only horribly overcomplicated, but also wrong - see: xsltfiddle.liberty-development.net/pNP54eEGooey
It may be overly complicated, but it worked for my project. I was just posting for someone else to use as inspiration if anything. Sorry it is "incorrect", please bear some insight to what is incorrect besides just posting code. If you want me to understand what I did wrong, tell me what I need to do right, or how I am doing it wrong.Myatt

© 2022 - 2024 — McMap. All rights reserved.