在 XSLT 中转换日期时间格式

4
我有一个XML文件中的字段,其中包含日期值,格式如下:
``` ```
我想将其转换为标准的XSD格式:
``` 2013-04-01T17:13:41.000Z ```
请问在我的XSL转换中如何实现?我可以使用1.0和2.0样式表版本。

有很多关于日期格式的问题和答案可供参考。你看过并尝试过它们吗? - Navin Rawat
1
这取决于4/1/2013是1月4日还是4月1日。但基本上没有简单的答案,您只需使用字符串操作(在XSLT 2.0中使用正则表达式)解析日期的组成部分,然后按正确顺序重新组装字符串。虽然有点繁琐,但并不难。 - Michael Kay
2
@MichaelKay:从ISO日期时间字符串中可以清楚地看出,日期是4月1日。 - Borodin
6个回答

5

所以...我感到无聊,之前从未使用过xsl:analyze-string。这里是一个基于正则表达式的解决方案:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">

  <xsl:template match="/item/date">
    <xsl:analyze-string select="@value" regex="([0-9]+)/([0-9]+)/([0-9]+) ([0-9]+):([0-9]+):([0-9]+) (PM|AM)">

      <xsl:matching-substring>
        <xsl:variable name="month" select="number(regex-group(1))"/>
        <xsl:variable name="day" select="number(regex-group(2))"/>
        <xsl:variable name="year" select="number(regex-group(3))"/>
        <xsl:variable name="hours">
          <xsl:choose>
            <xsl:when test="regex-group(7) = 'PM'">
              <xsl:value-of select="12 + number(regex-group(4))"/>
            </xsl:when>
            <xsl:otherwise><xsl:value-of select="number(regex-group(4))"/></xsl:otherwise>
          </xsl:choose>
        </xsl:variable>
        <xsl:variable name="minutes" select="number(regex-group(5))"/>
        <xsl:variable name="seconds" select="number(regex-group(6))"/>
        <xsl:variable name="dateTime" select="xs:dateTime( concat($year, '-', format-number($month, '00'), '-', format-number($day, '00'), 'T', format-number($hours, '00'), ':', format-number($minutes, '00'), ':', format-number($seconds, '00'), 'Z')  )" />

        <reformattedDate>
          <xsl:value-of select="$dateTime"/>
        </reformattedDate>
      </xsl:matching-substring>
    </xsl:analyze-string>
  </xsl:template>
</xsl:stylesheet>

我将其应用于像这样的测试 XML 文件:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<item>
    <date value="4/1/2013 5:13:41 PM"/>
</item>

输出结果如下:
<?xml version="1.0" encoding="UTF-8"?>
    <reformattedDate xmlns:xs="http://www.w3.org/2001/XMLSchema">2013-04-01T17:13:41Z</reformattedDate>

如果你想更精确地格式化输出,就像已经建议的那样,你可以使用format-date函数

我会将 <xsl:variable name="dateTime"><xsl:value-of select="..."/></xsl:variable> 改为 <xsl:variable name="dateTime" select="..."/> - Martin Honnen
1
除了一个问题,你的回答很好。你错过了12AM -> 00HH的转换。你可以在otherwise部分将"number(regex-group(4))"替换为"number(regex-group(4)) mod 12"来表示小时。 - Fernando Miguélez
1.0 版本有问题,你做得很糟糕。就像你平常一样,直接做得很糟糕。 - Robeezy

4

这里是使用substring-beforesubstring-after的XSLT 1.0版本。

感谢adhocgeek提供XML输入。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/item">
    <xsl:copy>
      <xsl:apply-templates select="date"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="date">
    <xsl:copy>

      <xsl:variable name="date" select="substring-before(@value, ' ')"/>
      <xsl:variable name="M" select="substring-before($date, '/')"/>
      <xsl:variable name="D-Y" select="substring-after($date, '/')"/>
      <xsl:variable name="D" select="substring-before($D-Y, '/')"/>
      <xsl:variable name="Y" select="substring-after($D-Y, '/')"/>

      <xsl:variable name="time-ampm" select="substring-after(@value, ' ')"/>
      <xsl:variable name="time" select="substring-before($time-ampm, ' ')"/>
      <xsl:variable name="ampm" select="substring-after($time-ampm, ' ')"/>
      <xsl:variable name="h" select="substring-before($time, ':')"/>
      <xsl:variable name="m-s" select="substring-after($time, ':')"/>
      <xsl:variable name="m" select="substring-before($m-s, ':')"/>
      <xsl:variable name="s" select="substring-after($m-s, ':')"/>

      <xsl:variable name="hh">
        <xsl:choose>
          <xsl:when test="$ampm = 'PM'">
            <xsl:value-of select="format-number($h + 12, '00')"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="format-number($h, '00')"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:variable>          

      <xsl:value-of select="concat($Y, '-', $M, '-', $D, 'T', $hh, ':', $m, ':', $s)"/>

    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

XML

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<item>
  <date value="4/1/2013 5:13:41 PM"/>
</item>

输出

<?xml version="1.0" encoding="utf-8"?>
<item>
   <date>2013-4-1T05:13:41</date>
</item>

在我这里:这个结果是不正确的:5:13:41 PM 需要被转换为 17:13:41。否则输出将会比输入早12小时。 - michael.hor257k

3

请看ESXLT。这些函数已经被许多XSL实现内置支持,如果你的实现不支持,它们还提供回退选项。

如果你的实现完全符合标准,你可以这样做:

<?xml ...
  xmlns.date="http://exslt.org/dates-and-times"
  ...

<xsl:variable name="xsdate" select="date:parse-date(Date/@value, 'M/d/yyyy h:mm:ss a')" />

<xsl:value-of select="$xsdate" />

我作弊了,直接使用了`date:parse`的结果。通常使用`date:format`函数将日期格式化为特定格式。
老实说,我一直在努力让EXSLT的“回退”函数在Apache Xalan中工作。(在Xalan Java中使用非核心EXSLT日期函数
在我看来,最好一开始就使用`xs:dateTime`格式的日期,然后再转换为某种本地化格式。您可以使用`date:format`函数根据需要格式化日期,并且`date:format`函数在XSLT处理器中得到广泛支持。

为什么不发表一个实际的答案,即一些代码,可以从给定的输入返回所需的输出?恰好,我不知道任何可以做到这一点的EXSLT函数,但是我并不知道所有事情。 - michael.hor257k
恭喜,我承认我错了。但是哪个处理器可以“开箱即用”地支持这个功能呢?我不知道有哪个。 - michael.hor257k
@michael.hor257k,老实说,我不认为有任何方法。我还没有检查过ESXLT提供的“fallback”选项是否适用于parse-date。如果使用Xalan-Java,则可以使用我在http://stackoverflow.com/questions/22081400/using-non-core-exslt-date-functions-with-xalan-java#22157540中使用的技术来解析和重新格式化日期。最好在XML中首先使用`xs:dateTime`日期。 - Christopher Schultz
<xsl:fallback> 元素旨在处理 元素,而不是 函数。对于函数,请使用 function-available() 函数。 - michael.hor257k
@michael.hor257k,我的命名方式有些懒散:我指的是ESXLT的“其他选项”,用于在XSLT实现不提供它们时使用它们的函数...而不是应该使用<xsl:fallback> - Christopher Schultz

1
使用XSLT 2.0,您可以使用字符串函数从中提取组件构造一个xs:dateTime值,然后如果需要格式化,可以使用format-dateTimehttp://www.w3.org/TR/xslt20/#format-date。提取年、月、日等日期组件应该很简单,但对于时间,您需要更多的工作来注意PMAM12小时制。

我们可以在这里使用一个实际的例子 :) - Kristof Jozsa

0

使用substring-before和substring-after的代码可以工作,但应该添加更多的检查: 12AM => 00,12PM => 12之类的:

 <xsl:variable name="hh">
    <xsl:choose>
      <xsl:when test="$ampm = 'AM' and $h='12'">00</xsl:when>
      <xsl:when test="$ampm = 'PM' and $h!='12'">
        <xsl:value-of select="format-number($h + 12, '00')"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="format-number($h, '00')"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:variable>

0

@adhockgeek的答案对我几乎完美地起到了作用。 我只需要添加一个条件,以便它不会在值当前为12时将12加到小时数。 比如,12:15 PM!= 24:15 AM。

我从他的代码中创建了一个可在转换中重复调用的模板:

<xsl:template name="FormatDate">
    <xsl:param name="dt"/>
    <xsl:analyze-string select="$dt" regex="([0-9]+)/([0-9]+)/([0-9]+) ([0-9]+):([0-9]+):([0-9]+) (PM|AM)">
        <xsl:matching-substring>
            <xsl:variable name="month" select="number(regex-group(1))"/>
            <xsl:variable name="day" select="number(regex-group(2))"/>
            <xsl:variable name="year" select="number(regex-group(3))"/>
            <xsl:variable name="hours">
                <xsl:choose>
                    <xsl:when test="regex-group(7) = 'PM' and regex-group(4) != '12'">
                        <xsl:value-of select="12 + number(regex-group(4))"/>
                    </xsl:when>
                    <xsl:otherwise><xsl:value-of select="number(regex-group(4))"/></xsl:otherwise>
                </xsl:choose>
            </xsl:variable>
            <xsl:variable name="minutes" select="number(regex-group(5))"/>
            <xsl:variable name="seconds" select="number(regex-group(6))"/>
            <xsl:variable name="dateTime" select="xs:dateTime(concat($year, '-', format-number($month, '00'), '-', format-number($day, '00'), 'T', format-number($hours, '00'), ':', format-number($minutes, '00'), ':', format-number($seconds, '00')))" />
            <xsl:value-of select="$dateTime"/>
        </xsl:matching-substring>
    </xsl:analyze-string>
</xsl:template>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接