Pull to refresh

Преобразование даты в международный формат(UTC). Шаблон для версий XSLT от 1.0

Reading time17 min
Views15K
Потребовалось переводить дату в xml файлах из московского времени в международное. Изначально проблема решалась вставкой Java скрипа, но потребовалось сделать используя только встроенные возможности XSLT 1.0.
Сразу предупреждаю комментирующих, версия 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.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+16
Comments13

Articles