Сразу предупреждаю комментирующих, версия XSLT 2.0, где есть тип данных Дата, не годится, требуется реализация именно в 1.0.
Скачать пример, шаблон, результат и парсер можно здесь.
Итак, шаги решения задачи:
- Разбиение исходной даты в нашем формате на составляющие.
- Определение смещения часового пояса относительно UTC учитывая переход на зимнее/летнее время, для москва летом смещение -4, зимой -3. Переход на летнее время происходит в два часа ночи последнего воскресенья марта, переход на зимнее время в три часа ночи последнего воскресенья октября.
- Перевод всех частей даты в UTC время
- Сбор даты в UTC формате.
Подзадачи:
- Поиск дня недели, используется формула Зеллера
- Поиск количества дней в месяце
- Проверка года на високосность
Исходный шаблон даты:
DD.MM.YYYY hh:mm:ss
Подправить XSL под ваш формат можно без проблем, достаточно изменить последовательность разрезания и указать разделители.
Формат UTC даты:
YYYY-MM-DD hh:mm:ss
Подправляется еще проще, при сборе конечной строки.
Если требуется другие значения смещения часового пояса, достаточно исправить их в функции MoscowOffsetTime.
Использование шаблона:
<xsl:call-template name="GetTime">
<xsl:with-param name="time" select="10.15.2010 14:10:17"></xsl:with-param>
</xsl:call-template>
* This source code was highlighted with Source Code Highlighter.Собственно сам код шаблона:
<xsl:template name="GetTime">
<xsl:param name="time"></xsl:param>
<xsl:call-template name="MoscowDataToUTC">
<xsl:with-param name="data" select="$time"></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="MoscowDataToUTC">
<xsl:param name="data"/>
<xsl:variable name="vData">
<xsl:call-template name="splitEqFirst">
<xsl:with-param name="str" select="$data"></xsl:with-param>
<xsl:with-param name="worddiv" select="' '"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vDataDD">
<xsl:call-template name="splitEqFirst">
<xsl:with-param name="str" select="$vData"></xsl:with-param>
<xsl:with-param name="worddiv" select="'.'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vDataBuf">
<xsl:call-template name="splitEqSecond">
<xsl:with-param name="str" select="$vData"></xsl:with-param>
<xsl:with-param name="worddiv" select="'.'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vDataMM">
<xsl:call-template name="splitEqFirst">
<xsl:with-param name="str" select="$vDataBuf"></xsl:with-param>
<xsl:with-param name="worddiv" select="'.'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vDataYYYY">
<xsl:call-template name="splitEqSecond">
<xsl:with-param name="str" select="$vDataBuf"></xsl:with-param>
<xsl:with-param name="worddiv" select="'.'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vTime">
<xsl:call-template name="splitEqSecond">
<xsl:with-param name="str" select="$data"></xsl:with-param>
<xsl:with-param name="worddiv" select="' '"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vTimeHH">
<xsl:call-template name="splitEqFirst">
<xsl:with-param name="str" select="$vTime"></xsl:with-param>
<xsl:with-param name="worddiv" select="':'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vTimeBuf">
<xsl:call-template name="splitEqSecond">
<xsl:with-param name="str" select="$vTime"></xsl:with-param>
<xsl:with-param name="worddiv" select="':'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vTimeMM">
<xsl:call-template name="splitEqFirst">
<xsl:with-param name="str" select="$vTimeBuf"></xsl:with-param>
<xsl:with-param name="worddiv" select="':'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vTimeSS">
<xsl:call-template name="splitEqSecond">
<xsl:with-param name="str" select="$vTimeBuf"></xsl:with-param>
<xsl:with-param name="worddiv" select="':'"/>
</xsl:call-template>
</xsl:variable>
<!--Вычисление смещения часового пояса-->
<xsl:variable name="deltaTime">
<xsl:call-template name="MoscowOffsetTime">
<xsl:with-param name="day" select="$vDataDD"></xsl:with-param>
<xsl:with-param name="hh" select="$vTimeHH"></xsl:with-param>
<xsl:with-param name="month" select="$vDataMM"></xsl:with-param>
<xsl:with-param name="year" select="$vDataYYYY"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<!-- Собираем UTC время -->
<!--Переводим дату, проеряя если первое января и время до трех ночи, то убавляем год на 1-->
<xsl:variable name="vDataYYYYUTC">
<xsl:call-template name="MoscowYearToUTCYear">
<xsl:with-param name="hh" select="$vTimeHH"></xsl:with-param>
<xsl:with-param name="day" select="$vDataDD"></xsl:with-param>
<xsl:with-param name="month" select="$vDataMM"></xsl:with-param>
<xsl:with-param name="year" select="$vDataYYYY"></xsl:with-param>
<xsl:with-param name="delta" select="$deltaTime"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<!--Переводим месяц, если первое число и время до трех ночи, переводим на предыдущий месяц-->
<xsl:variable name="vDataMMUTC">
<xsl:call-template name="MoscowMonthToUTCMonth">
<xsl:with-param name="hh" select="$vTimeHH"></xsl:with-param>
<xsl:with-param name="day" select="$vDataDD"></xsl:with-param>
<xsl:with-param name="month" select="$vDataMM"></xsl:with-param>
<xsl:with-param name="delta" select="$deltaTime"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<!--Переводим день, если время до трех ночи, проставляем прошедший день-->
<xsl:variable name="vDataDDUTC">
<xsl:call-template name="MoscowDayToUTCDay">
<xsl:with-param name="hh" select="$vTimeHH"></xsl:with-param>
<xsl:with-param name="day" select="$vDataDD"></xsl:with-param>
<xsl:with-param name="month" select="$vDataMM"></xsl:with-param>
<xsl:with-param name="year" select="$vDataYYYY"></xsl:with-param>
<xsl:with-param name="delta" select="$deltaTime"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<!--Переводит время, если время до трех ночи отнимаем delta, если после, прибавляем 20-->
<xsl:variable name="vTimeHHUTC">
<xsl:call-template name="MoscowHoutToUTCHour">
<xsl:with-param name="hh" select="$vTimeHH"></xsl:with-param>
<xsl:with-param name="hl" select="$deltaTime"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<!--Подготавливаем значения к выводу, добавляем 0 если состоит из одной цифры-->
<xsl:variable name="vTimeHHOUT">
<xsl:call-template name="ZeroBeforeOneNumber">
<xsl:with-param name="num" select="$vTimeHHUTC"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vDateDDOUT">
<xsl:call-template name="ZeroBeforeOneNumber">
<xsl:with-param name="num" select="$vDataDDUTC"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vDataMMOUT">
<xsl:call-template name="ZeroBeforeOneNumber">
<xsl:with-param name="num" select="$vDataMMUTC"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<!--Вывод даты в UTC формате-->
<xsl:value-of select="$vDataYYYYUTC"/>-<xsl:value-of select="$vDataMMOUT"/>-<xsl:value-of select="$vDateDDOUT"/><xsl:value-of select="' '"/><xsl:value-of select="$vTimeHHOUT"/>:<xsl:value-of select="$vTimeMM"/>:<xsl:value-of select="$vTimeSS"/>
</xsl:template>
<!--Подстановка нуля перед одиночной цифрой-->
<xsl:template name="ZeroBeforeOneNumber">
<xsl:param name="num"></xsl:param>
<xsl:choose>
<xsl:when test="number($num) < 10">0<xsl:value-of select="$num"/></xsl:when>
<xsl:otherwise><xsl:value-of select="$num"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--Проверка на уменьшение года при создании даты-->
<xsl:template name="MoscowYearToUTCYear">
<xsl:param name="hh"></xsl:param>
<xsl:param name="day"></xsl:param>
<xsl:param name="month"></xsl:param>
<xsl:param name="year"></xsl:param>
<xsl:param name="delta"></xsl:param>
<xsl:choose>
<xsl:when test="(number($hh) + $delta) < 0 and ($day = '01') and ($month='01')">
<xsl:value-of select="number($year)-number(1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="number($year)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--Проверка на уменьшение месяца при создании даты-->
<xsl:template name="MoscowMonthToUTCMonth">
<xsl:param name="hh"></xsl:param>
<xsl:param name="day"></xsl:param>
<xsl:param name="month"></xsl:param>
<xsl:param name="delta"></xsl:param>
<xsl:choose>
<xsl:when test="(number($hh) + $delta) < 0 and ($day = '01')">
<xsl:choose>
<xsl:when test="number($month) = 1">12</xsl:when>
<xsl:otherwise><xsl:value-of select="number($month)-number(1)"/></xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="number($month)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--Проверка на уменьшение дня при создании даты-->
<xsl:template name="MoscowDayToUTCDay">
<xsl:param name="hh"></xsl:param>
<xsl:param name="day"></xsl:param>
<xsl:param name="month"></xsl:param>
<xsl:param name="year"></xsl:param>
<xsl:param name="delta"></xsl:param>
<xsl:choose>
<xsl:when test="(number($hh) + $delta) < 0">
<xsl:choose>
<xsl:when test="number($day) = 1">
<xsl:call-template name="CountDaysInMonth">
<xsl:with-param name="month" select="number($month)-1"></xsl:with-param>
<xsl:with-param name="year" select="$year"></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise><xsl:value-of select="number($day)-number(1)"/></xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$day"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--Получение количества дней в месяце-->
<xsl:template name="CountDaysInMonth">
<xsl:param name="month"></xsl:param>
<xsl:param name="year"></xsl:param>
<xsl:choose>
<xsl:when test="($month = 1) or ($month = 3) or ($month = 5) or ($month = 7) or ($month = 8) or ($month = 10) or ($month = 12) or ($month = 0)">31</xsl:when>
<xsl:when test="($month = 4) or ($month = 6) or ($month = 9) or ($month = 11)">30</xsl:when>
<xsl:when test="($month = 2)">
<!--Февраль. Проверяем високосный ли год-->
<xsl:choose>
<xsl:when test="(number($year) mod 4 = 0) and (number($year) mod 100 != 0 or number($year) mod 400 = 0)">29</xsl:when>
<xsl:otherwise>28</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>30</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--Уменьшение времени-->
<xsl:template name="MoscowHoutToUTCHour">
<xsl:param name="hh"/>
<xsl:param name="hl"/>
<xsl:variable name="t" select="(number($hh)+number($hl))"></xsl:variable>
<xsl:choose>
<xsl:when test="number($t) < 0">
<xsl:value-of select="number($t)+24"/>
</xsl:when>
<xsl:when test="number($t) > 23">
<xsl:value-of select="number($t)-24"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="number($t)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--Определение смещения времени от зимнего/летнего времени-->
<xsl:template name="MoscowOffsetTime">
<xsl:param name="hh"></xsl:param>
<xsl:param name="day"></xsl:param>
<xsl:param name="month"></xsl:param>
<xsl:param name="year"></xsl:param>
<!--День недели-->
<xsl:variable name="dayOfWeek">
<xsl:call-template name="DayOfWeek">
<xsl:with-param name="day" select="$day"></xsl:with-param>
<xsl:with-param name="month" select="$month"></xsl:with-param>
<xsl:with-param name="year" select="$year"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<!--Количество дней в текущем месяце-->
<xsl:variable name="countDayOfMonth">
<xsl:call-template name="CountDaysInMonth">
<xsl:with-param name="month" select="$month"></xsl:with-param>
<xsl:with-param name="year" select="$year"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<!--Смещение от конца месяца до текущего дня-->
<xsl:variable name="diffDay" select="number($countDayOfMonth)-number($day)"/>
<!--Смещение до ближайшего воскресенья-->
<xsl:variable name="offsetToSun" select="6-number($dayOfWeek)"/>
<!--Определяем смещение-->
<xsl:choose>
<xsl:when test="number($month) > 3 and number($month) < 10">-4</xsl:when>
<!--Если до конца месяца осталось больше 7 дней-->
<xsl:when test="number($month) = 3 and $diffDay > 7">
-3
</xsl:when>
<xsl:when test="number($month) = 10 and $diffDay > 7">
-4
</xsl:when>
<!--Если до конца месяца меньше 7 дней и в этот промежуток попадает воскресенье и сегодня не оно-->
<xsl:when test="number($month) = 3 and $offsetToSun > $diffDay ">
-4
</xsl:when>
<xsl:when test="number($month) = 10 and $offsetToSun > $diffDay ">
-3
</xsl:when>
<!--Если сегодня день переключения времени-->
<xsl:when test="number($month) = 3 and ($offsetToSun = 0) and ($diffDay < 7)">
<xsl:choose>
<xsl:when test="$hh >= 2">-4</xsl:when>
<xsl:otherwise>-3</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="number($month) = 10 and ($offsetToSun = 0) and ($diffDay < 7)">
<xsl:choose>
<xsl:when test="$hh >= 3">-3</xsl:when>
<xsl:otherwise>-4</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>-3</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--Вычислить день недели по дате(формула Зеллера)-->
<xsl:template name="DayOfWeek">
<xsl:param name="day"></xsl:param>
<xsl:param name="month"></xsl:param>
<xsl:param name="year"></xsl:param>
<xsl:variable name="gMonth">
<xsl:choose>
<xsl:when test="number($month) = 1">
<xsl:value-of select="number(13)"/>
</xsl:when>
<xsl:when test="number($month) = 2">
<xsl:value-of select="number(14)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="number($month)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="Y" select="number($year) mod 100"></xsl:variable>
<xsl:variable name="Ydiv4" select="floor($Y div 4)"/>
<xsl:variable name="M26div10" select="floor(((number($gMonth)+1)*26) div 10)"/>
<xsl:variable name="C" select="floor($year div 100)" />
<xsl:variable name="Cdiv4" select="floor($C div 4)"/>
<xsl:variable name="F">
<xsl:value-of select="(number($day)+number($M26div10)+$Y+number($Ydiv4)+number($Cdiv4)+5*$C) mod 7"></xsl:value-of>
</xsl:variable>
<!--Более удобный формат для последующих вычислений, Пн=0, ..., Сб=5, Вс=6-->
<xsl:choose>
<xsl:when test="$F=0">5</xsl:when>
<xsl:when test="$F=1">6</xsl:when>
<xsl:otherwise>
<xsl:value-of select="number($F)-2"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--Разрезание строки по пробелам-->
<xsl:template name="tokenEq">
<xsl:param name="token"/>
<xsl:value-of select="$token"/>
</xsl:template>
<xsl:template name="splitEqFirst" match="text()" mode="splitEqFirst">
<xsl:param name="str" select="."/>
<xsl:param name="worddiv" select="' '"/>
<xsl:if test="contains($str,$worddiv)">
<xsl:call-template name="tokenEq">
<xsl:with-param name="token" select="substring-before($str, $worddiv)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="splitEqSecond" match="text()" mode="splitEqSecond">
<xsl:param name="str" select="."/>
<xsl:param name="worddiv" select="' '"/>
<xsl:if test="contains($str,$worddiv)">
<xsl:call-template name="tokenEq">
<xsl:with-param name="token" select="substring-after($str, $worddiv)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!--————————————————————————————————————————————————-->
* This source code was highlighted with Source Code Highlighter.