user1905354
user1905354

Reputation: 39

Converting time presented in hours and minutes into decimal and calculate the sum in hours using xslt 1.0

I want to achieve this using XSLT 1.0. Hours is represented in Hours and minutes(for ex: 10:30 is basically 10 hours 30 minutes). I need to convert it into only hours(10.50) and then calculate the sum of all hours nodes.

Here is a source XML document:

<Record>
   <hours>10:30</hours> 
   <hours>20:30</hours>
   <hours>10:60</hours> 
 </Record>

Output:

 <Record>
   <TotalHours>42.0</TotalHours>
 </Record>

Upvotes: 1

Views: 1076

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243449

Do note that the solution in Joepie's answer causes compile-time syntax error in any compliant XSLT 1.0 processor (it can only be executed without compilation errors if one uses an XSLT 2.0 processor):

SAXON 6.5.4 from Michael Kay
Java version 1.6.0_31
Error at xsl:value-of on line 11 of file:/(Untitled):
  Error in expression                      sum(hours/number(substring-before(., ':'))) +                     sum(hours/number(substring-after(., ':'))) div 60                 : Unexpected token [<function>] in path expression
Transformation failed: Failed to compile stylesheet. 1 error detected.
Press any key to continue . . . 

Here is a truly working, true XSLT 1.0 solution:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/*">
     <xsl:copy>
       <xsl:variable name="vrtfTimes">
         <xsl:for-each select="*">
           <t>
               <xsl:value-of select=
               "substring-before(., ':') + substring-after(.,':') div 60"/>
           </t>
         </xsl:for-each>
       </xsl:variable>
       <TotalHours>
         <xsl:value-of select="floor(sum(ext:node-set($vrtfTimes)/*))"/>
       </TotalHours>
     </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<Record>
    <hours>10:30</hours>
    <hours>20:30</hours>
    <hours>10:60</hours>
</Record>

the wanted, correct result is produced:

<Record>
   <TotalHours>42</TotalHours>
</Record>

Note:

In case you want to get not only the hours from the sum, but also the remaining minute -- as minutes:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/*">
   <xsl:copy>
     <xsl:variable name="vrtfTimes">
       <xsl:for-each select="*">
         <t>
           <xsl:value-of select=
             "substring-before(., ':') + substring-after(.,':') div 60"/>
         </t>
       </xsl:for-each>
     </xsl:variable>

     <xsl:variable name="vSum" select="sum(ext:node-set($vrtfTimes)/*)"/>
     <TotalHours>
             <xsl:value-of select="floor($vSum)"/>
             <xsl:text>:</xsl:text>
             <xsl:value-of select="round(60* ($vSum - floor($vSum)))"/>
     </TotalHours>
   </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the following (slightly more complex) XML document:

<Record>
    <hours>10:30</hours>
    <hours>20:30</hours>
    <hours>10:60</hours>
    <hours>1:15</hours>
    <hours>1:03</hours>
</Record>

the correct result is produced:

<Record>
   <TotalHours>44:18</TotalHours>
</Record>

Upvotes: 2

Joep
Joep

Reputation: 4133

In XSLT 1.0:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output indent="yes"/>

    <xsl:template match="Record">
        <Record>
            <TotalHours>
                <xsl:value-of select="
                    sum(hours/number(substring-before(., ':'))) + 
                    sum(hours/number(substring-after(., ':'))) div 60
                "/>
            </TotalHours>
        </Record>
    </xsl:template>

</xsl:transform>

Working example

If you also want the minutes in stead of an fraction of an hour:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output indent="yes"/>

    <xsl:template match="Record">
        <Record>
            <TotalHours>
                <xsl:value-of select="
                    concat(
                        sum(hours/number(substring-before(., ':'))) + floor(sum(hours/number(substring-after(., ':'))) div 60),   
                    '.',
                        format-number(floor(sum(hours/number(substring-after(., ':'))) mod 60), '00')
                    )
                "/>
            </TotalHours>
        </Record>
    </xsl:template>

</xsl:transform>

**Working example

Upvotes: 0

Related Questions