R.C
R.C

Reputation: 593

XSLT datetime formatting

If i have the following:

<showtime><time>2017-01-01T15:30:01.331Z</time><time>2017-01-02T20:30:00.995Z</time></showtime>

Is there a way to format the datetime using xslt so that it will both round the millisecond and change last section to just 2 digits?

Outcome:

<showtime><time>2017-01-01T15:30:01.33Z</time><time>2017-01-02T20:30:01.00Z</time></showtime>

Upvotes: 0

Views: 4742

Answers (4)

michael.hor257k
michael.hor257k

Reputation: 116982

It's surprising how difficult and awkward this task can be, despite all the date/time functions provided by XPath 2.0.

Anyway, this is what I came up with. Note that this is done in two separate and independent steps: first we calculate the rounded dateTime value, then we format it. Unlike what others have said, formatting is the trivial part here.

XSLT 2.0

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="time">
    <xsl:variable name="seconds" select="3600 * hours-from-dateTime(.) + 60 * minutes-from-dateTime(.) + seconds-from-dateTime(.)" />
    <xsl:variable name="round-seconds" select="round($seconds * 100) div 100" />

    <xsl:variable name="dur" select="xs:dayTimeDuration(concat('PT', $seconds, 'S'))" />
    <xsl:variable name="round-dur" select="xs:dayTimeDuration(concat('PT', $round-seconds, 'S'))" />

    <xsl:variable name="new-dateTime" select="xs:dateTime(.) - $dur + $round-dur" />
    <xsl:copy>
        <xsl:value-of select="format-dateTime($new-dateTime, '[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01].[f01][ZN]')" /> 
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Upvotes: 0

Michael Kay
Michael Kay

Reputation: 163312

As others have said, format-dateTime in XSLT 2.0 does essentially what you want. Unfortunately there are some rough edges in the handling of seconds and milliseconds. There are two separate picture elements for formatting the seconds and fractional seconds. In principle '[s01],[f001]' formats two digits for the seconds and three for the fractional seconds, separated by a comma. But the complication arises with values like 23.9999.

XSLT 2.0 says, "In the case of the fractional seconds component, the value is rounded to the specified size as if by applying the function round-half-to-even(fractional-seconds, max-width)." However you interpret it, this doesn't give a sensible answer for rounding a fractional seconds value of .9999 to three digits. There is no mention of rolling any overflow into the seconds value (and potentially then into the minutes value, hours value and so on).

In the Functions and Operators 3.1 specification this bug was fixed using the path of least resistance: the spec was changed to say that the value is truncated to the specified width.

If it's really important to you to achieve rounding in such a way that 2016-12-31T23:59:59.9999Z is rounded to 2017-01-01T00:00:00.00, then you're going to have to write the logic yourself.

Upvotes: 1

Valdi_Bo
Valdi_Bo

Reputation: 30971

In XSLT 2 you can use such functions as format-date, format-dateTime and format-time.

Unfortunately, these functions take xs:date, xs:dateTime and xs:time values respectively (not just strings). So as the first thing you have to select respective date / time substrings (from the source content) and create these date / time variables.

Then you can output these values in any format, specified with picture argument.

Upvotes: 0

Thomas W
Thomas W

Reputation: 15371

If truncation is acceptable and true rounding is not required, this simple solution works across XSLT 1.0 and 2.0:

<xsl:template match="showtime/time">
  <xsl:copy>
    <xsl:value-of select="concat(substring(., 1, 22), 'Z')"/>
  </xsl:copy>
</xsl:template>

Upvotes: 1

Related Questions