XSLT Date Formatting

2020-02-13 03:25发布

问题:

I have had a look at various suggestions on here but none really help my problem. Due to the sources my XML comes from i can receive dates in the following three formats;

04-04-2014(DD-MM-YYYY)
04-Apr-2014(DD-MMM-YYYY)
2014-04-04(YYYY-MM-DD)

I would like to have a function or simple command that will change all of these (other than the third, yet able to recognize that the third is correct) into YYYY-MM-DD

I have a long winded When/when/when to do this currently, but there must be an easier way. My current XSLT does the following;

<xsl:choose>
    <xsl:when test="contains(date, 'Jan')">
        <xsl:value-of select="concat(substring(date,6),'-01-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="contains(date, 'Feb')">
        <xsl:value-of select="concat(substring(date,6),'-02-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="contains(date, 'Mar')">
        <xsl:value-of select="concat(substring(date,6),'-03-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="contains(date, 'Apr')">
        <xsl:value-of select="concat(substring(date,6),'-04-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="contains(date, 'May')">
        <xsl:value-of select="concat(substring(date,6),'-05-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="contains(date, 'Jun')">
        <xsl:value-of select="concat(substring(date,6),'-06-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="contains(date, 'Jul')">
        <xsl:value-of select="concat(substring(date,6),'-07-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="contains(date, 'Aug')">
        <xsl:value-of select="concat(substring(date,6),'-08-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="contains(date, 'Sep')">
        <xsl:value-of select="concat(substring(date,6),'-09-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="contains(date, 'Oct')">
        <xsl:value-of select="concat(substring(date,6),'-10-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="contains(date, 'Nov')">
        <xsl:value-of select="concat(substring(date,6),'-11-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="contains(date, 'Dec')">
        <xsl:value-of select="concat(substring(date,6),'-12-',substring(date,1,2))" />
    </xsl:when>
    <xsl:when test="string-length($dateStart) = 2">
        <xsl:value-of select="concat(substring(date,7),'-',substring(date,4,2),'-',substring(date,1,2))" />
    </xsl:when>
    <xsl:otherwise>
        <xsl:value-of select="date"/>
    </xsl:otherwise>
</xsl:choose>

So this will check if the date contains any months as JAN,Feb etc. and then if not, check if the first number up to - is 2 characters (DD) and format, other wise it assumes it is YYYY-MM-DD already and outputs it as it is.

I have tried - <xsl:value-of select = "format-dateTime(date, '[Y0001]-[MN]-[D01]')"/> But this complains that the Dates year is not long enough (as date is being treated as a datetime, which should be in format YYYY-MM-DD

Thanks to Ian roberts Answer below, i created the below to deal with a few more scenarios and group the outputs together;

<xsl:variable name="months" select="('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')" />
<xsl:analyze-string select="date" flags="x"
       regex="^(
            (\d\d)-(\d\d)-(\d\d\d\d)
          | (\d\d)-([A-Za-z]{{3}})-(\d\d\d\d)
          | (\d\d\d\d)-(\d\d)-(\d\d)
          | (\d\d\d\d)-([A-Za-z]{{3}})-(\d\d)
          | (\d\d)([A-Za-z]{{3}})(\d\d\d\d)
          | (\d\d\d\d)([A-Za-z]{{3}})(\d\d))$">
        <xsl:matching-substring>
            <xsl:value-of select="if (regex-group(4)) then concat(regex-group(4),'-',regex-group(3),'-',regex-group(2)) else ''"/>
            <xsl:value-of select="if (regex-group(7)) then concat(regex-group(7),'-',format-number(index-of($months, regex-group(6)), '00'),'-',regex-group(5)) else ''"/>
            <xsl:value-of select="if (regex-group(8)) then concat(regex-group(8),'-',regex-group(9),'-',regex-group(10)) else ''"/>
            <xsl:value-of select="if (regex-group(11)) then concat(regex-group(11),'-',format-number(index-of($months, regex-group(12)), '00'),'-',regex-group(13)) else ''"/>
            <xsl:value-of select="if (regex-group(16)) then concat(regex-group(16),'-',format-number(index-of($months, regex-group(15)), '00'),'-',regex-group(14)) else ''"/>
            <xsl:value-of select="if (regex-group(17)) then concat(regex-group(17),'-',format-number(index-of($months, regex-group(18)), '00'),'-',regex-group(19)) else ''"/>
        </xsl:matching-substring>
    </xsl:analyze-string>

回答1:

Since you refer to format-dateTime you must be in XSLT 2.0, so you could approach it using regular expressions. If you know you'll always have one of these three forms then you could use analyze-string:

<xsl:variable name="months" select="('Jan', 'Feb', 'Mar', 'Apr', ...)" />

<xsl:analyze-string select="date" flags="x"
   regex="^(
        (\d\d)-(\d\d)-(\d\d\d\d)
      | (\d\d)-([A-Za-z]{{3}})-(\d\d\d\d)
      | (\d\d\d\d)-(\d\d)-(\d\d))$">
  <xsl:matching-substring>
    <!-- year -->
    <xsl:value-of select="regex-group(4)"/>
    <xsl:value-of select="regex-group(7)"/>
    <xsl:value-of select="regex-group(8)"/>
    <xsl:text>-</xsl:text>
    <!-- month -->
    <xsl:value-of select="regex-group(3)"/>
    <xsl:value-of select="if (regex-group(6))
          then format-number(index-of($months, regex-group(6)), '00')
          else ''"/>
    <xsl:value-of select="regex-group(9)"/>
    <xsl:text>-</xsl:text>
    <!-- day -->
    <xsl:value-of select="regex-group(2)"/>
    <xsl:value-of select="regex-group(5)"/>
    <xsl:value-of select="regex-group(10)"/>
  </xsl:matching-substring>

</xsl:analyze-string>

Each of your example formats will match exactly one of the three alternatives in the pattern, and all the regex-group calls for the non-matching alternatives will produce empty strings.



回答2:

Even in XSLT 1.0, the problem becomes fairly trivial once you focus on the structure of the different formats rather than on their contents:

<xsl:template name="normalize-datestring">
<xsl:param name="datestring"/>
        <xsl:choose>
            <xsl:when test="string-length(substring-before($datestring, '-')) = 4">
            <!-- this is YYYY-MM-DD; copy as is -->
                <xsl:value-of select="$datestring" />
            </xsl:when>
            <xsl:when test="string-length($datestring) = 10">
            <!-- this is DD-MM-YYYY; reorder -->
                <xsl:value-of select="substring($datestring, 7, 4)" />
                <xsl:text>-</xsl:text>
                <xsl:value-of select="substring($datestring, 4, 2)" />
                <xsl:text>-</xsl:text>
                <xsl:value-of select="substring($datestring, 1, 2)" />
            </xsl:when>
            <xsl:otherwise>
            <!-- this is DD-MMM-YYYY; reorder and calculate month number-->
                <xsl:value-of select="substring($datestring, 8, 4)" />
                <xsl:text>-</xsl:text>
                <xsl:variable name="mmm" select="substring($datestring, 4, 3)" />
                <xsl:variable name="m" select="string-length(substring-before('JanFebMarAprMayJunJulAugSepOctNovDec', $mmm)) div 3 + 1" />
                <xsl:value-of select="format-number($m, '00')" />
                <xsl:text>-</xsl:text>
                <xsl:value-of select="substring($datestring, 1, 2)" />
            </xsl:otherwise>
        </xsl:choose>
</xsl:template>


回答3:

Here's a function uitlizing similar regex and month sequence as the other answer:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:f="function" exclude-result-prefixes="xs"
version="2.0">
<xsl:variable name="d1" select="'2014-04-04'"/>
<xsl:variable name="d2" select="'04-04-2014'"/>
<xsl:variable name="d3" select="'04-Apr-2014'"/>

<xsl:template match="/">
    <xsl:value-of select="f:processDate($d2,'[Y0001]-[MN]-[D01]')"/>
</xsl:template>

<xsl:function name="f:processDate">
    <xsl:param name="dateString"/>
    <xsl:param name="datePattern"/>
    <xsl:variable name="month"
        select="('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Okt','Nov','Dez')"/>
    <xsl:choose>
        <!-- this will work for d1 -->
        <xsl:when test="$dateString castable as xs:date">
            <xsl:value-of select="format-date( xs:date($dateString),$datePattern)"/>
        </xsl:when>
        <!-- d2 -->
        <xsl:when test="matches($dateString,'^(\d\d-\d\d-\d\d\d\d)$')">
            <xsl:variable name="d" select="substring($dateString,1,2)"/>
            <xsl:variable name="m" select="substring($dateString,4,2)"/>
            <xsl:variable name="y" select="substring($dateString,7,4)"/>
            <xsl:value-of select="format-date( xs:date( string-join(($y, $m, $d), '-')), $datePattern )"
            />
        </xsl:when>
        <!-- d3 -->
        <xsl:when test="some $i in $month satisfies matches($dateString,concat('^(\d\d-',$i,'-\d\d\d\d)$'))">
            <xsl:variable name="d" select="substring($dateString,1,2)"/>
            <xsl:variable name="m"
                select="format-number(index-of($month,substring($dateString,4,3)),'00')"/>
            <xsl:variable name="y" select="substring($dateString,8,4)"/>
            <xsl:value-of select="format-date( xs:date( string-join(($y, $m, $d), '-')), $datePattern )"
            />
        </xsl:when>
        <xsl:otherwise>
            <xsl:message>Unsupported dateString: <xsl:value-of select="$dateString"/></xsl:message>
            <xsl:value-of select="$dateString"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:function>

</xsl:stylesheet>