[xsl] localtime (accounting for summmertime/DST) in XSLT 2.0

Subject: [xsl] localtime (accounting for summmertime/DST) in XSLT 2.0
From: Steven Hentschel <steven.hentschel@xxxxxxxxx>
Date: Wed, 4 May 2011 09:46:18 +0100
"adjust-dateTime-to-timezone" without the second parameter will convert a 
dateTime to the implicit timezone which, in Saxon 9.3 at least, seems to be 
the UTC offset currently applying on the computer running the stylesheet. Hence 
"adjust-dateTime-to-timezone(current-dateTime())" will return local time 
accurately.

However, if summertime or DST applies in a particular territory and a 
datetime, that falls outside the period the current UTC offset is in force, is 
passed into "adjust-dateTime-to-timezone", then the result is not the 
localtime representation of the datetime value. Is this a bug, a design 
decision or an ambiguity?

I did bingle around for how to convert an arbitrary datetime into localtime 
but I couldn't find any so I had a go at writing my own. It turns out writing a 
custom function for Summertime/DST isn't that difficult as the format-date 
function can return the day of the week for a given date which can then be 
used to extrapolate the dates at the weekends at which the UTC offset changes.

Here is my function:

<!-- adjusts a dateTime to UK local time account for GMT/BST -->
<xsl:function name="mynamespace:adjust-dateTime-to-uk-local-time" 
as="xs:dateTime">
  <xsl:param name="DATETIMEIN" as="xs:dateTime"/>
  <xsl:variable name="ADJUSTGMT" select="adjust-dateTime-to-
timezone($DATETIMEIN, xs:dayTimeDuration('PT0H'))"/>
  <!-- BST starts at 1 a.m UTC on last Sunday in March -->
  <xsl:variable name="BSTSTARTS" select="xs:dateTime(concat(year-from-
dateTime($ADJUSTGMT), '-03-', 
    31 - xs:integer(replace(replace(replace(replace(replace(replace(replace(
    format-date(xs:date(concat(year-from-dateTime($ADJUSTGMT), '-03-31Z')), 
'[Fn, 2-2]')
    , 'su', '0'), 'mo', '1'), 'tu', '2'), 'we', '3'), 'th', '4'), 'fr', '5'), 
'sa', '6')),
    'T01:00:00Z'))" as="xs:dateTime"/>
<!-- BST ends at 1 a.m UTC on last Sunday in October -->
  <xsl:variable name="BSTENDS" select="xs:dateTime(concat(year-from-
dateTime($ADJUSTGMT), '-10-', 
    31 - xs:integer(replace(replace(replace(replace(replace(replace(replace(
    format-date(xs:date(concat(year-from-dateTime($ADJUSTGMT), '-10-31Z')), 
'[Fn, 2-2]')
    , 'su', '0'), 'mo', '1'), 'tu', '2'), 'we', '3'), 'th', '4'), 'fr', '5'), 
'sa', '6')),
    'T01:00:00Z'))" as="xs:dateTime"/>
  <xsl:choose>
    <xsl:when test="$ADJUSTGMT ge $BSTSTARTS and $ADJUSTGMT lt $BSTENDS">    
<!--return UTC +1 in summer-->
      <xsl:value-of select="adjust-dateTime-to-timezone($DATETIMEIN, 
xs:dayTimeDuration('PT1H'))"/>
    </xsl:when>
    <xsl:otherwise>    <!--return UTC +0 in winter -->
      <xsl:value-of select="$ADJUSTGMT"/>
    </xsl:otherwise> 
  </xsl:choose>
</xsl:function>

It could easily be modified for other countries with summertime/DST.

Note that the function won't necessarily work for historical dates as the 
present method of determining the datetime of the changeovers has only been in 
force since 2002.

Current Thread