xquery or xslt to convert time hours to PT*H*M format - xslt

I am new to XQuery and XSLT.
I'm looking for an XQuery or XQuery function to convert the timetotal i.e. 5 Hours to PT5H or 5.5 hours to PT5H30M
Not sure if XQuery can do that. If not any XSLT 1.0 function is ok too.

You can't define an xs:dayTimeDuration with a fractional number of hours, or minutes, but you can use a fractional number of seconds, so just multiply the hours by 3600 and create a duration in seconds:
xs:dayTimeDuration(
concat(
'PT',
$hours * 3600,
'S'
)
)
e.g.
let
$hours:= 5.551245
return
xs:dayTimeDuration(
concat(
'PT',
$hours * 3600,
'S'
)
)
returns: PT5H33M4.482S

Consider the following example:
XML
<input>
<hours>5</hours>
<hours>5.5</hours>
<hours>5.555</hours>
</input>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/input">
<output>
<xsl:for-each select="hours">
<xsl:variable name="seconds" select=". * 3600" />
<xsl:variable name="h" select="floor($seconds div 3600)"/>
<xsl:variable name="m" select="floor($seconds div 60) mod 60"/>
<xsl:variable name="s" select="$seconds mod 60"/>
<duration>
<xsl:text>PT</xsl:text>
<xsl:value-of select="$h"/>
<xsl:text>H</xsl:text>
<xsl:value-of select="$m"/>
<xsl:text>M</xsl:text>
<xsl:value-of select="$s"/>
<xsl:text>S</xsl:text>
</duration>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<duration>PT5H0M0S</duration>
<duration>PT5H30M0S</duration>
<duration>PT5H33M18S</duration>
</output>
This assumes the input value cannot be negative.
Do note that PT5H30Mand PT330M are equally valid expressions of the same duration - so you could just multiply the input value by 60 (assuming you only need precision to a minute).

Related

How to calculate time difference between two given time in xslt?

Given below is the xml.
I want to display time difference like: "2h 52m".
START TIME - 09:02
STOP TIME - 11:45
TOTAL -2h 52m
<Surgery>
<SURGERY_START_TIME>9:02</SURGERY_START_TIME>
<SURGERY_STOP_TIME>11:45</SURGERY_STOP_TIME>
</Surgery>
I tried the below code but that doesn't work,
<xsl:variable name="surgStartTime">
<xsl:value-of select="SURGERY_START_TIME/."/>
</xsl:variable>
<xsl:variable name="surgStopTime">
<xsl:value-of select="SURGERY_STOP_TIME/."/>
</xsl:variable>
<xsl:value-of select="$surgStartTime -$surgStopTime)/>
There is no support for date/time arithmetic in XSLT 1.0; you need to do the calculation yourself - e.g:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="Surgery">
<!-- calculate the duration in minutes -->
<xsl:variable name="duration" select="60 * substring-before(SURGERY_STOP_TIME, ':') + substring-after(SURGERY_STOP_TIME, ':') - 60 * substring-before(SURGERY_START_TIME, ':') - substring-after(SURGERY_START_TIME, ':')" />
<xsl:copy>
<xsl:copy-of select="*"/>
<DURATION>
<!-- output whole hours -->
<xsl:value-of select="floor($duration div 60)"/>
<xsl:text>h </xsl:text>
<!-- output remaining minutes -->
<xsl:value-of select="$duration mod 60"/>
<xsl:text>m</xsl:text>
</DURATION>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<Surgery>
<SURGERY_START_TIME>9:02</SURGERY_START_TIME>
<SURGERY_STOP_TIME>11:45</SURGERY_STOP_TIME>
<DURATION>2h 43m</DURATION>
</Surgery>
and not 2h 52m as stated in your question.

XSLT Convert milliseconds to timecode

Hello and have a nice day!
There are two steps that I'm trying to figure out:
(But not in priority) to take integer from xml file with tag "duration" in milliseconds
I am trying to convert "duration" milliseconds into timecode that will looks like hh:mm:ss:ff , where h - hours, m - minutes, s - seconds and f - frames (25frames=1second).
As I see the algorithm is:
z=milliseconds
h=z idiv (60*60*25)
m=(z-h*60*60*25) idiv (60*25)
s=(z-h*60*60*25-m*60*25) idiv 25
f=(z-h*60*60*25-m*60*25-s*25)
Have any idea how to make a calculation within XSLT properly?
Consider the following example:
XML
<input>
<milliseconds>45045500</milliseconds>
</input>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="input">
<output>
<xsl:variable name="f" select="floor(milliseconds div 40) mod 25" />
<xsl:variable name="s" select="floor(milliseconds div 1000) mod 60" />
<xsl:variable name="m" select="floor(milliseconds div 60000) mod 60" />
<xsl:variable name="h" select="floor(milliseconds div 3600000)" />
<timecode>
<xsl:value-of select="format-number($h, '00')"/>
<xsl:value-of select="format-number($m, ':00')"/>
<xsl:value-of select="format-number($s, ':00')"/>
<xsl:value-of select="format-number($f, ':00')"/>
</timecode>
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<timecode>12:30:45:12</timecode>
</output>
Note that this truncates fractional frames. If you want to round to the nearest frame, then change the variables to:
<xsl:variable name="totalFrames" select="round(milliseconds div 40)" />
<xsl:variable name="f" select="$totalFrames mod 25" />
<xsl:variable name="s" select="floor($totalFrames div 25) mod 60" />
<xsl:variable name="m" select="floor($totalFrames div 1500) mod 60" />
<xsl:variable name="h" select="floor($totalFrames div 90000)" />
Here the result will be:
<timecode>12:30:45:13</timecode>
In XSLT 2.0 or higher, you can shorten the expression floor($a div $b) to $a idiv $b.

Convert week number date YY-WW-DW into standard xslt date format YYYY-MM-DD

How can be converted week date YYWWWD, where YY - year, WW - week number and WD - week day 1-7 to a standard format YYYY-MM-DD for ISO 8601 using xslt 3.
Example: 21133
Year: 21
Week: 13
Week Day: 3 (Wednesday)
converted to 2021-03-31
Input:
<Line>
<week>2113</week>
<day>3</day>
</Line>
Output:
<Line>
<Date>2021-03-31</Date>
</Line>
I tried calculation rules, but struggling with the logic for the dates around New Year like the case 20537 to be converted to 2021-01-03.
Here is an example you could use as your starting point. It converts dates in ISO week date format to standard YYYY-MM-DD date format:
XML
<input>
<ISO-week-date>2006-W02-7</ISO-week-date>
<ISO-week-date>2021-W13-3</ISO-week-date>
<ISO-week-date>2020-W53-7</ISO-week-date>
</input>
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/input">
<output>
<xsl:for-each select="ISO-week-date">
<!-- extract ISO-week-date components -->
<xsl:variable name="year" select="substring(., 1, 4)"/>
<xsl:variable name="week" select="xs:integer(substring(., 7, 2))"/>
<xsl:variable name="weekday" select="xs:integer(substring(., 10, 1))"/>
<!-- calculate base Sunday -->
<xsl:variable name="base" select="xs:date(concat($year, '-01-04'))"/>
<xsl:variable name="base-weekday" select="xs:integer(format-date($base, '[F1]'))"/>
<xsl:variable name="base-sunday" select="$base - $base-weekday * xs:dayTimeDuration('P1D')"/>
<!-- calculate standard date -->
<xsl:variable name="target-date" select="$base-sunday + ($week - 1) * xs:dayTimeDuration('P7D') + $weekday * xs:dayTimeDuration('P1D')"/>
<date>
<xsl:value-of select="$target-date"/>
</date>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="utf-8"?>
<output>
<date>2006-01-15</date>
<date>2021-03-31</date>
<date>2021-01-03</date>
</output>
If your week numbers follow the same convention as the ISO 8601 date and time standard, then you only need to adjust the parts that extract the input components.
Of course, if you need this in more than one place, you can turn this into a function.

XML to Fixed Length File Using XSLT (Complex Position Logic)

We have a requirement in which need to convert XML into Fixed Length File.
First record is as header and after that we have actual records..From 2 record onwards we need to apply the logic which is mentioned below:
1.After length 45, consider 10 numbers 0000001000, what ever be the
last digit we need to check and replace by following the below
table:
"For Positive Amount: (0000001000) - (000000100{)
{= 0
A = 1
B = 2
c = 3
D = 4
E = 5
F = 6
G = 7
H = 8
I = 9
I have not that much idea so created the small XSLT , request anyone please help on the same.
Input:
<?xml version='1.0' encoding='utf-8'?>
<ZR>
<INPUT>
<I_FIL>ERES</I_FIL>
</INPUT>
<TABLES>
<T_ER>
<item>
<DATA> HEADER1111111122222222333333344456</DATA>
</item>
<item>
<DATA>778944 D4E2 EA 1234567891 2018-11-060000001000EA
0000000000000100001020D04YA30TRE0000000XXXYYY 300{ P 2018-11-05</DATA>
</item>
<item>
<DATA>987654 D4E2 EA 1987654321 2018-11-060000002001EA
0000000000000100001020D04YA30UUU0000000XXXLRB 100{ P 2018-11-05</DATA>
</item>
.
.
.
.
.
.
.
.
</T_ER>
</TABLES>
</ZR>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions" >
<xsl:output omit-xml-declaration="yes"/>
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
<xsl:value-of select="ZR/TABLES/T_ER/item[1]/DATA"/>
<xsl:value-of select="$break" />
</xsl:template>
</xsl:stylesheet>
Expected Output:
HEADER1111111122222222333333344456
778944 D4E2 EA 1234567891 2018-11-06000000100{EA
0000000000000100001020D04YA30TRE0000000XXXYYY 300{ P 2018-11-05
987654 D4E2 EA 1987654321 2018-11-06000000200AEA
0000000000000100001020D04YA30UUU0000000XXXLRB 100{ P 2018-11-05
.
.
.
.
It looks like you just want to replace the 55th character based on your map, so you could do this...
<xsl:template match="/">
<xsl:value-of select="ZR/TABLES/T_ER/item[1]/DATA"/>
<xsl:value-of select="$break" />
<xsl:for-each select="ZR/TABLES/T_ER/item[position() > 1]/DATA">
<xsl:variable name="char" select="substring('{ABCDEFGHI', number(substring(., 55, 1)) + 1, 1)" />
<xsl:value-of select="concat(substring(., 1, 54), $char, substring(., 56))" />
<xsl:value-of select="$break" />
</xsl:for-each>
</xsl:template>
This would work in XSLT 1.0.
An XSLT 2.0 solution could be this...
<xsl:template match="/">
<xsl:value-of select="ZR/TABLES/T_ER/item[1]/DATA,
ZR/TABLES/T_ER/item[position() > 1]/DATA/concat(substring(., 1, 54), substring('{ABCDEFGHI', number(substring(., 55, 1)) + 1, 1), substring(., 56))"
separator="
" />
</xsl:template>
In XSLT 3.0, you could potentially make use of a map with has the advantage of easily being extended if you wanted to consider two or more characters instead:
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:fn="http://www.w3.org/2005/xpath-functions" >
<xsl:output omit-xml-declaration="yes"/>
<xsl:param name="break" select="'
'" />
<xsl:variable name="chars" as="map(xs:string, xs:string)">
<xsl:map>
<xsl:map-entry key="'0'" select="'{'"/>
<xsl:map-entry key="'1'" select="'A'"/>
<xsl:map-entry key="'2'" select="'B'"/>
<xsl:map-entry key="'3'" select="'C'"/>
<xsl:map-entry key="'4'" select="'D'"/>
<xsl:map-entry key="'5'" select="'E'"/>
<xsl:map-entry key="'6'" select="'F'"/>
<xsl:map-entry key="'7'" select="'G'"/>
<xsl:map-entry key="'8'" select="'H'"/>
<xsl:map-entry key="'9'" select="'I'"/>
</xsl:map>
</xsl:variable>
<xsl:template match="/">
<xsl:value-of select="ZR/TABLES/T_ER/item[1]/DATA,
ZR/TABLES/T_ER/item[position() > 1]/DATA/concat(substring(., 1, 54), $chars(substring(., 55, 1)), substring(., 56))" separator="
" />
</xsl:template>
</xsl:stylesheet>
There's probably a much nicer way in XSLT 3.0, so hopefully Martin Honnen will be along soon to say....
Well, using functions string-length, substring and translate for your specifications it can be achieved as:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output omit-xml-declaration="yes" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
<xsl:value-of select="ZR/TABLES/T_ER/item[1]/DATA" />
<xsl:value-of select="$break" />
<xsl:for-each select="ZR/TABLES/T_ER/item[position() != 1]">
<xsl:variable name="length" select="string-length(substring(DATA,0,46))" />
<xsl:variable name="tenNumbers" select="substring(DATA, ($length + 1), 10)"/>
<xsl:variable name="charToReplace" select="translate(substring($tenNumbers, string-length($tenNumbers), 1),'0123456789','{ABCDEFGHI')" />
<xsl:value-of select="concat(substring(DATA,0,46), substring(DATA, ($length + 1), 9), $charToReplace, substring(DATA,($length+11),(string-length(DATA) + 1)))"/>
<xsl:value-of select="$break" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 how increment a date

UPDATE: Cannot use any EXSLT extensions.
Also I'm using date in two different places and I only want to update one of them and not both.
I need to increment a date in my XSLT transformation. I'm using XSLT 1.0.
In source XML I have a date like this
<XML>
<Date>4/22/2011 3:30:43 PM</Date>
</XML>
Then I need to add 10 years to the output. Like this
<Output>
<Odate>4/22/2011 3:30:43 PM</Odate>
<Cdate>4/22/2021 3:30:43 PM</Cdate>
</Output>
How this can be done in XSLT 1.0. Thanks in advance.
The following is no general date arithmetic implementation but might suffice to increment the year part:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="year-inc" select="10"/>
<xsl:template match="XML">
<Output>
<xsl:apply-templates/>
</Output>
</xsl:template>
<xsl:template match="Date">
<xsl:variable name="d0" select="substring-before(., '/')"/>
<xsl:variable name="d1" select="substring-before(substring-after(., '/'), '/')"/>
<xsl:variable name="d2" select="substring-after(substring-after(., '/'), '/')"/>
<xsl:variable name="new-year" select="substring($d2, 1, 4) + $year-inc"/>
<Cdate>
<xsl:value-of select="concat($d0, '/', $d1, '/', $new-year, substring($d2, 5))"/>
</Cdate>
</xsl:template>
</xsl:stylesheet>
Depends a little how pernickety you want to be, e.g. what's the date 10 years after 29 Feb 2004? There are a number of useful XSLT 1.0 date-handling routines you can download at www.exslt.org, I think they include both a parse-date template which will convert your US-format date into a standard ISO date, date arithmetic templates which will allow you to add a duration to an ISO-format date, and a format-date function that will turn it back into US format.
I have figured it with the help of #Martin. I extend on #Martin's code and only called the template when I need to modify the date.
XSLT:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="year-inc" select="10"/>
<xsl:template match="XML">
<Output>
<Odate>
<xsl:value-of select="Date"/>
</Odate>
<Cdate>
<xsl:call-template name="increment"/>
</Cdate>
</Output>
</xsl:template>
<xsl:template name="increment">
<xsl:variable name="d0" select="substring-before(Date, '/')"/>
<xsl:variable name="d1" select="substring-before(substring-after(Date, '/'), '/')"/>
<xsl:variable name="d2" select="substring-after(substring-after(Date, '/'), '/')"/>
<xsl:variable name="new-year" select="substring($d2, 1, 4) + $year-inc"/>
<xsl:value-of select="concat($d0, '/', $d1, '/', $new-year, substring($d2, 5))"/>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="utf-8"?>
<Output>
<Odate>4/22/2011 3:30:43 PM</Odate>
<Cdate>4/22/2021 3:30:43 PM</Cdate>
</Output>