问题
I couldn't find an existing answer to this problem, so I am posting it here.
Note that input dateTimes:
- may or may not have fractional seconds;
- may already be in UTC/GMT,
which complicates the process.
Example input:
<data>
<datetime>2020-06-01T04:45:15+05:30</datetime>
<datetime>2020-06-01T04:45:15.123+05:30</datetime>
<datetime>2020-05-31T19:45:15-08:00</datetime>
<datetime>2020-05-31T19:45:15.123-08:00</datetime>
<datetime>2020-05-31T19:45:15Z</datetime>
<datetime>2020-05-31T19:45:15.123Z</datetime>
</data>
Expected output:
<data>
<datetime>2020-05-31T23:15:15Z</datetime>
<datetime>2020-05-31T23:15:15.123Z</datetime>
<datetime>2020-06-01T03:45:15Z</datetime>
<datetime>2020-06-01T03:45:15.123Z</datetime>
<datetime>2020-05-31T19:45:15Z</datetime>
<datetime>2020-05-31T19:45:15.123Z</datetime>
</data>
回答1:
The following solution starts by testing if the given dateTime is already in UTC/GMT. If it is, it is passed to the output as is. Otherwise, the individual components of the given dateTime:
- year;
- month;
- day;
- hour;
- minute;
- seconds;
- offset hour;
- offset minute;
- offset sign,
are extracted.
Next, the local dateTime is converted to total number of seconds elapsed since midnight of November 24, 4714 BCE (the Julian Day Number epoch). The offset from UTC/GMT is also converted to seconds and subtracted from the total.
The result is then converted back to Gregorian date and time-of-day and formatted as an ISO 8601 dateTime.
XSLT 1.0
<xsl:template name="dateTime-to-UTC">
<xsl:param name="dateTime"/>
<xsl:choose>
<xsl:when test="contains($dateTime, 'Z')">
<xsl:value-of select="$dateTime"/>
</xsl:when>
<xsl:otherwise>
<!-- extract components -->
<xsl:variable name="date" select="substring-before($dateTime, 'T')" />
<xsl:variable name="time" select="substring-after($dateTime, 'T')" />
<!-- date components -->
<xsl:variable name="year" select="substring($date, 1, 4)" />
<xsl:variable name="month" select="substring($date, 6, 2)" />
<xsl:variable name="day" select="substring($date, 9, 2)" />
<!-- time components -->
<xsl:variable name="local-time" select="substring($time, 1, string-length($time) - 6)" />
<xsl:variable name="hour" select="substring($local-time, 1, 2)" />
<xsl:variable name="minute" select="substring($local-time, 4, 2)" />
<xsl:variable name="second" select="substring($local-time, 7)" />
<!-- offset components -->
<xsl:variable name="offset" select="substring-after($time, $local-time)"/>
<xsl:variable name="offset-sign" select="1 - 2 * starts-with($offset, '-')" />
<xsl:variable name="offset-hour" select="substring($offset, 2, 2) * $offset-sign" />
<xsl:variable name="offset-minute" select="substring($offset, 5, 2) * $offset-sign" />
<!-- convert to seconds -->
<xsl:variable name="a" select="floor((14 - $month) div 12)"/>
<xsl:variable name="y" select="$year + 4800 - $a"/>
<xsl:variable name="m" select="$month + 12*$a - 3"/>
<xsl:variable name="jd" select="$day + floor((153*$m + 2) div 5) + 365*$y + floor($y div 4) - floor($y div 100) + floor($y div 400) - 32045" />
<xsl:variable name="total-seconds" select="86400*$jd + 3600*$hour + 60*$minute + $second - 3600*$offset-hour - 60*$offset-minute" />
<!-- convert to date -->
<xsl:variable name="new-jd" select="floor($total-seconds div 86400)"/>
<xsl:variable name="new-hour" select="floor($total-seconds mod 86400 div 3600)"/>
<xsl:variable name="new-minute" select="floor($total-seconds mod 3600 div 60)"/>
<xsl:variable name="new-second" select="$total-seconds mod 60"/>
<xsl:variable name="f" select="$new-jd + 1401 + floor((floor((4 * $new-jd + 274277) div 146097) * 3) div 4) - 38"/>
<xsl:variable name="e" select="4*$f + 3"/>
<xsl:variable name="g" select="floor(($e mod 1461) div 4)"/>
<xsl:variable name="h" select="5*$g + 2"/>
<xsl:variable name="D" select="floor(($h mod 153) div 5 ) + 1"/>
<xsl:variable name="M" select="(floor($h div 153) + 2) mod 12 + 1"/>
<xsl:variable name="Y" select="floor($e div 1461) - 4716 + floor((14 - $M) div 12)"/>
<!-- output -->
<xsl:value-of select="$Y"/>
<xsl:value-of select="format-number($M, '-00')"/>
<xsl:value-of select="format-number($D, '-00')"/>
<xsl:text>T</xsl:text>
<xsl:value-of select="format-number($new-hour, '00')"/>
<xsl:value-of select="format-number($new-minute, ':00')"/>
<xsl:value-of select="format-number($new-second, ':00.###')"/>
<xsl:text>Z</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Demo: https://xsltfiddle.liberty-development.net/aiyne6
See also: https://stackoverflow.com/a/46196920/3016153
来源:https://stackoverflow.com/questions/64963722/convert-datetime-to-utc-gmt-using-pure-xslt-1-0