如何使用XSLT将本地时间结合时区名称转换为UTC时间?
这个问题我之前也碰到过!XSLT的adjust-dateTime-to-timezone函数确实有点局限——它只认时区偏移值(比如PT0H、P-8H),不直接支持America/Los_Angeles这种时区标识符。不过根据你使用的XSLT版本和处理器,有几种可行的解决方案:
方案1:用XSLT 3.0 + 支持时区数据库的处理器(推荐)
如果你的项目能升级到XSLT 3.0,并且使用支持时区名称解析的处理器(比如Saxon-PE/EE,或者某些其他现代处理器),那事情就简单多了。XSLT 3.0规范允许将时区名称直接转换为xs:timezone类型,然后传给adjust-dateTime-to-timezone函数:
<!-- 定义本地时间和时区名称 --> <xsl:variable name="localTime" select="xs:dateTime('2019-12-02T06:46:24.000')"/> <xsl:variable name="tzName" select="'America/Los_Angeles'"/> <!-- 将时区名称转换为xs:timezone对象 --> <xsl:variable name="timezone" select="xs:timezone($tzName)"/> <!-- 转换为UTC时间 --> <xsl:variable name="utcTime" select="adjust-dateTime-to-timezone($localTime, $timezone)"/>
注意:这个功能需要处理器内置时区数据库(比如IANA时区数据库),不是所有XSLT 3.0处理器都支持,所以要确认你的处理器版本是否兼容。
方案2:手动维护时区偏移映射(适合XSLT 2.0及以下)
如果没法升级XSLT版本,你可以手动维护目标时区的偏移规则——尤其是要考虑夏令时(DST)的变化,因为像America/Los_Angeles这种时区,一年中有两个不同的偏移值(标准时间-8小时,夏令时-7小时)。
下面是一个针对America/Los_Angeles的示例实现:
<!-- 定义时区偏移映射 --> <xsl:variable name="tzDefinitions"> <tz name="America/Los_Angeles" standard="-PT8H" daylight="-PT7H"/> <!-- 可以添加其他时区的规则 --> </xsl:variable> <!-- 判断给定时间是否处于夏令时 --> <xsl:function name="local:isDaylightSaving"> <xsl:param name="targetDateTime"/> <xsl:variable name="year" select="year-from-dateTime($targetDateTime)"/> <!-- 计算夏令时开始时间:3月第二个周日的2点(本地时间) --> <xsl:variable name="dstStartBase" select="xs:dateTime(concat($year, '-03-01T02:00:00'))"/> <xsl:variable name="dstStart" select="$dstStartBase + ((7 - (day-from-date($dstStartBase) mod 7)) mod 7) * xs:dayTimeDuration('P1D')"/> <!-- 计算夏令时结束时间:11月第一个周日的2点(本地时间) --> <xsl:variable name="dstEndBase" select="xs:dateTime(concat($year, '-11-01T02:00:00'))"/> <xsl:variable name="dstEnd" select="$dstEndBase + ((7 - (day-from-date($dstEndBase) mod 7)) mod 7) * xs:dayTimeDuration('P1D')"/> <xsl:sequence select="$targetDateTime >= $dstStart and $targetDateTime < $dstEnd"/> </xsl:function> <!-- 获取对应时区的偏移值 --> <xsl:function name="local:getTimezoneOffset"> <xsl:param name="tzName"/> <xsl:param name="targetDateTime"/> <xsl:variable name="tzDef" select="$tzDefinitions/tz[@name=$tzName]"/> <xsl:sequence select="if (local:isDaylightSaving($targetDateTime)) then $tzDef/@daylight else $tzDef/@standard"/> </xsl:function> <!-- 使用示例 --> <xsl:variable name="localTime" select="xs:dateTime('2019-12-02T06:46:24.000')"/> <xsl:variable name="tzOffset" select="local:getTimezoneOffset('America/Los_Angeles', $localTime)"/> <xsl:variable name="utcTime" select="adjust-dateTime-to-timezone($localTime, xs:dayTimeDuration($tzOffset))"/>
这种方法的缺点是需要自己维护每个时区的夏令时规则,规则变化时还要手动更新,适合只处理少数特定时区的场景。
方案3:使用处理器扩展函数(比如Saxon调用Java)
如果你的处理器支持调用外部代码(比如Saxon可以调用Java类),可以借助语言本身的时区处理能力来获取偏移。比如用Java的java.time包来解析时区名称:
<!-- 需要在XSLT中声明Java命名空间 --> <xsl:stylesheet xmlns:java="http://saxon.sf.net/java-type" ...> <xsl:variable name="localTime" select="xs:dateTime('2019-12-02T06:46:24.000')"/> <xsl:variable name="tzName" select="'America/Los_Angeles'"/> <!-- 调用Java的ZoneId获取时区对象 --> <xsl:variable name="zoneId" select="java:java.time.ZoneId.of($tzName)"/> <!-- 把本地时间转换为ZonedDateTime --> <xsl:variable name="zonedDateTime" select="java:java.time.ZonedDateTime.of(java:java.time.LocalDateTime.parse(string($localTime)), $zoneId)"/> <!-- 获取UTC偏移值 --> <xsl:variable name="offset" select="java:getOffset($zonedDateTime, java:java.time.Instant.now())"/> <!-- 将偏移转换为XSLT的dayTimeDuration格式 --> <xsl:variable name="offsetSeconds" select="java:getTotalSeconds($offset)"/> <xsl:variable name="offsetStr"> <xsl:value-of select="concat( 'P', if ($offsetSeconds >= 0) then '+' else '-', floor(abs($offsetSeconds) div 3600), 'H', floor((abs($offsetSeconds) mod 3600) div 60), 'M' )"/> </xsl:variable> <!-- 转换为UTC时间 --> <xsl:variable name="utcTime" select="adjust-dateTime-to-timezone($localTime, xs:dayTimeDuration($offsetStr))"/> </xsl:stylesheet>
这种方法灵活性高,能自动处理夏令时和时区规则变化,但需要配置处理器允许调用外部代码,并且依赖对应语言的环境。
内容的提问来源于stack exchange,提问作者DMi

