# Sum the time durations

## Challenge

Write the shortest code that can sum all the time durations that appear in the stdin. The program must only consider the strings that match with one of the following patterns and ignore the rest.

    HH:MM:SS     (it will be interpreted as HH hours, MM minutes and SS seconds)
H:MM:SS      (it will be interpreted as H hours, MM minutes and SS seconds)
MM:SS        (it will be interpreted as MM minutes, SS seconds)
M:SS         (it will be interpreted as M minutes, SS seconds)


examples of strings that match with the enumerated patterns:

    12:00:01
2:03:22
00:53
9:13


The output should be of the form

    HHh MMm SSs      (that means HH hours, MM minutes and SS seconds with non-zero-padding)


## Example

STDIN

View the Welcome video.
Video: 10:37 min.
View the video introduction to the course.
Video: 3:30 min. View the video of how to use the Lesson Overview.
Video: 9:13 min.
View the video overview of how to use the Epsilen system to share your work.
Video: 03:15 min.
Video: 1:05:26 min.

STDOUT

1h 32m 1s

• What about strings like 10:4:56? According current specification they have to be treated as 4m 56s, part 10 will be ignored. Same question about 10:12:7 does it mean 10m 12s with ignoring of 7? Or handling of such strings can be implementation defined? – Qwertiy Dec 11 '14 at 10:19
• The program should only consider time durations with zero-padding in the minute and second fields. In your example the string "10:4:56" will be treated as 4m 56s. Also the string "10:12:7" will be interpreted as 10m 12s. – Alfredo Diaz Dec 11 '14 at 10:27
• Strange, but ok :) – Qwertiy Dec 11 '14 at 10:31
• How did you get 1h 19m 18s in the output? 37+30+13+15+26==121, 10+3+9+3+5==30, 1==1, so I expect 1h 32m 01s. What's wrong in this logic? Also, such output format is that one which is expected, isn't it? – Qwertiy Dec 11 '14 at 10:58
• You are right. Sorry :S – Alfredo Diaz Dec 11 '14 at 11:05

## Pyth 105

K"smh"J"\D\D+|\d+:(?=\d:)|:\d\D"W:QJ1=Q:QJd;FN_msdCfn2lTm+*]0</k\:2msbck\:cQ)~k+d+hK_%+NZ60=Z/N60=KtK;_k


Try it online.

This requires input from STDIN in the same way that the Javascript answer does, as quoted text with newlines as \ns.

Sample:

"View the Welcome video.\nVideo: 10:37 min.\nView the video introduction to the course.\nVideo: 3:30 min. View the video of how to use the Lesson Overview.\nVideo: 9:13 min.\nView the video overview of how to use the Epsilen system to share your work.\nVideo: 03:15 min.\nView the video to learn about the State of Texas Assessment of Academic Readiness (STAAR).\nVideo: 1:05:26 min."


Output

1h 32m 1s


Example working with weirder dates:

"10:10:5 and 5:1:10 and 27 or 16: or 1:1:1 or 11:1\n"


Output

0h 11m 20s


(Only the 10:10 and the 1:10 are legitimate times)

The main reason that this is so long is that Pyth won't let you extract positive matches. This instead matches everything that isn't a valid time, and replaces it with a space character. Then, splitting on whitespace leaves only times and some wayward numbers. The excess numbers are removed by checking for : characters, which will have been removed from non-valid times. This could almost certainly be golfed further ;)

• Lucky bastard that Pyth has regex too! – Optimizer Dec 12 '14 at 14:31
• @Optimizer :D It was a real pain though. I'm thinking of suggesting changing the "is match" behaviour to change based on the arg you give it (currently it only checks that it is a non-string) – FryAmTheEggman Dec 12 '14 at 14:36

## Javascript ES6, 138 chars

### Function, 139

Takes string as an argument and writes output to console:

f=s=>(r=0,s.replace(/(\d\d?):(\d\d)(:(\d\d))?/g,(m,a,b,x,c)=>r+=x?+c+b*60+a*3600:+b+a*60),console.log("%dh %dm %ds",r/3600,r%3600/60,r%60))


### Program, 138

prompt(r=0).replace(/(\d\d?):(\d\d)(:(\d\d))?/g,(m,a,b,x,c)=>r+=x?+c+b*60+a*3600:+b+a*60),console.log("%dh %dm %ds",r/3600,r%3600/60,r%60)


### Test for function

f("View the Welcome video.\n\
Video: 10:37 min.\n\
View the video introduction to the course.\n\
Video: 3:30 min. View the video of how to use the Lesson Overview.\n\
Video: 9:13 min.\n\
View the video overview of how to use the Epsilen system to share your work.\n\
Video: 03:15 min.\n\
Video: 1:05:26 min.")


### Output

"1h 32m 1s"

• Ok. Works fine in Firefox Developer Edition 36.0a2, formatting fails only in Firefox 34.0. – manatwork Dec 11 '14 at 11:40
• Promt doesn't allow multiline strings. But I can add version with prompt() call in same number of chars :) I even shorten it 1 symbol))) – Qwertiy Dec 11 '14 at 11:43
• @Optimizer How to enter them? – Qwertiy Dec 11 '14 at 11:49
• @Optimizer Inserting a new line doesn't work in my FF 35.0. – Qwertiy Dec 11 '14 at 11:51
• I cannot get it to work. I tried it on ideone.com ideone.com/56EHgV – Alfredo Diaz Dec 11 '14 at 12:39

# JavaScript, ES6, 208 200 197 bytes

I know this is super long, but I wanted to explore the latest features of ES6, reverse, map-reduce, arrow functions and array comprehension (spread operator).

alert(prompt().match(/\d\d?:\d\d(:\d\d)?/g).map(x=>[...x.split(":").reverse(),z=0].slice(0,3)).reduce((a,b)=>b.map((y,i)=>+y+ +a[i])).map((x,i)=>(z=(t=x+z|0)/60,t%60+"smh"[i])).reverse().join(" "))

Just run the snippet in a latest Firefox.

How it works (ungolfed a bit)

alert(                              // Alert the final result
prompt()                          // Take the input via prompt
.match(/\d\d?:\d\d(:\d\d)?/g)     // Match only correct time formats
.map(                             // Map all matches using this method
x=>[                            // Take each element as argument x
...x.split(":").reverse(),    // split x on ":" and reverse the array, then spread it
z=0                           // put 0 as last element of return array
].slice(0,3)                    // Take only first 3 elements of the array
).reduce(                         // Reduce the result using this method
(a,b)=>                         // Pairwise elements of the array
b.map(                          // Map array b
(y,i)=>~~y+~~a[i]             // Convert b[i] to b[i]+a[i]
)                               // Now we have array like [SS, MM, HH]
).map(                            // Map these three values for carry over calculation
(x,i)=>(
t=x+z,                        // z contains carryover amount, add it to this value
z=(t/60)|0,                   // Carryover is now floor(t/60)
t%60+"smh"[i]                 // Remove overflow from t and add "s", "m" or "h"
)                               // Now we have array like ["SSs", "MMm", "HHh"]
).reverse().join(" ")             // Reverse it and join by space
)


### Bash (with grep, sed, awk and date): 124 bytes, 120 bytes

Just pipe the text into this:

grep -o '[:0-9]*'|sed 's/^[^:]*:[^:]*$/:\0/'|awk -F: '{T+=3600*$1+60*$2+$3}END{print"@"T}'|xargs date +"%Hh %Mm %Ss" -ud


How it works

• grep: outputs strings from the input containing only 0123456789:
• sed: turns MM:SS and M:SS into :M:SS
• awk: calculates the seconds, empty string is 0
• xargs: passes input as argument to date
• date: converts seconds since epoch (prefixed with @) to the required format
• Isn't this hour related to your timezone? – Qwertiy Dec 11 '14 at 20:52
• You are right, nice catch :) Added -u flag. – pgy Dec 12 '14 at 0:56

## Perl - 228 201

use integer;$h=0,$m=0,$s=0;while(<>){if(/(\d+:){1,2}\d+/){@a=reverse(split(/:/,$&));push @a,(0)x(3-@a);$s+=@a;$m+=@a;$h+=@a;}}$m+=$s/60;$s=$s%60;$h+=$m/60;$m=$m%60;print$h."h ".$m."m ".$s."s"


It happens to be the same algorithm as Optimizer's (grep,split,reverse,add).

I am no Perl expert, so maybe the byte count can be reduced.

## Ungolfed

use integer;                              # will do integer division
$h=0,$m=0,$s=0; while(<>){ if(/(\d+:){1,2}\d+/) { # extract date formats @a = reverse(split(/:/,$&));      # split by ":" and reverse
push @a,(0)x(3-@a);               # pad with zeros (minutes and hours)
$s+=@a; # sum seconds$m+=@a;                        # sum minutes
$h+=@a; # sum hours } } # convert seconds as minutes$m += $s / 60;$s = $s % 60; # convert minutes as hours$h += $m / 60;$m = $m % 60; print$h."h ".$m."m ".$s."s";

• As for me, it's strange to see perl solution longer than javascript one :) – Qwertiy Dec 11 '14 at 13:47
• Well, if even the shebang is counted, is normal to be longer. – manatwork Dec 11 '14 at 13:48
• @Qwertiy I agree. My hope is that some Perl guru will help me fix that. – coredump Dec 11 '14 at 13:49
• @manatwork Why does it count? – Qwertiy Dec 11 '14 at 14:03
• @Qwertiy, because coredump forgotten to exclude it from the count. :S Could be just removed (together with all those my keywords). – manatwork Dec 11 '14 at 14:32

# Rebol - 174

n: charset"1234567890"a:[1 2 n]b:[":"2 n]c: 0 parse input[any[copy x[a b b](c: c + do x)| copy x[a b](c: c + do join"0:"x)| skip]]print reword"$1h$2m $3s"[1 c/1 2 c/2 3 c/3]  Ungolfed + annotated: n: charset "1234567890" ; setup \d regex equiv a: [1 2 n] ; parse rule for \d{1,2} b: [":" 2 n] ; parse rule for :\d\d c: 0 ; time counter parse input [ ; parse the input (STDIN) ; (no regex in Rebol) any [ ; match zero or more... ; copy x [a b b] (c: c + do x) ; HH:MM:SS or H:MM:SS ; - copy match to x ; - increment time (c) by x ; OR | copy x [a b] (c: c + do join "0:" x) ; MM:SS or M:SS ; - copy match to x ; - "MM:SS" into "0:MM:SS" (join) ; - then increment time (c) ; OR | skip ; no match so move through input ] ] print reword "$1h $2m$3s" [1 c/1 2 c/2 3 c/3]


Rebol comes with its own time! datatype. You can see how the above code makes use of this from example below (from within the Rebol console):

>> 0:10:37 + 0:3:30 + 0:9:13 + 0:3:15 + 1:05:26
== 1:32:01

;; Rebol would treat 10:37 as 10 hours & 37 minutes (and not MM:SS)
;; So we have to prefix the "0:"

>> join "0:" 10:37
== "0:10:37"

;; This is a string so we use Rebol DO evaluator to convert to time!

>> do join "0:" 10:37
== 0:10:37

>> type? do join "0:" 10:37
== time!

>> hms: do join "0:" 10:37
== 0:10:37

>> hms/hour
== 0

>> hms/second
== 37

>> hms/minute
== 10


## Groovy - 195

M=60
r=(System.in.text=~/((\d?\d):)?(\d\d):(\d\d)/).collect{it[2..4]*.toInteger().inject{s,i->(s?:0)*M+i}}.inject{s,i->s+=i}
f=[];(2..0).each{j=M**it;s=r%j;f<<(r-s)/j;r=s}
printf("%sh %sm %ss",f)


I can't figure out how to compress it more.

## Ungolfed

M=60
r=(System.in.text=~/((\d?\d):)?(\d\d):(\d\d)/).collect{  // extract dates
it[2..4]*.toInteger().inject{ s,i ->                 // convert to seconds
(s?:0)*M+i
}
}.inject{s,i ->
s+=i                                                 // sum seconds
}

f=[];
(2..0).each{                                             // convert to h,m,s
j=M**it;
s=r%j;
f<<(r-s)/j;
r=s
}

printf("%sh %sm %ss",f)


# Mathematica 300 chars

This little exercise took up a lot of code, even for Mathematica. Surely there are more efficient ways to do this.

Golfed

Assuming that the input is stored in txt,

n=NumberString;
t=ToExpression;
o=TimeObject;

QuotientRemainder[QuantityMagnitude[Plus@@((o[#]-o[{0,0,0}])&/@
(StringSplit[StringCases[w,{(n~~":"~~n~~":"~~n),(n~~":"~~n)}],":"]
/.{{a_,b_}:> {0,t@a,t@b},{a_,b_,c_}:> {t@a,t@b,t@c}}))],60]/.{h_,m_}:>
Row[{h,"h ",IntegerPart@m,"m ",Round[60 FractionalPart[m]],"s "}]


How it works (using unGolfed code):

1-Find the times.

StringCases[txt,{(NumberString~~":"~~NumberString~~":"~~NumberString),
(NumberString~~":"~~NumberString)}];


{"10:37", "3:30", "9:13", "03:15", "1:05:26"}

2-Break into hours, minutes, seconds

StringSplit[%,":"]/.{{a_,b_}:> {0,ToExpression@a,ToExpression@b},{a_,b_,c_}:>
{ToExpression@a,ToExpression@b,ToExpression@c}}


{{0, 10, 37}, {0, 3, 30}, {0, 9, 13}, {0, 3, 15}, {1, 5, 26}}

3-Sum the times. Time objects are clock times. Subtracting one time object from another returns a duration, in this case 92.0167 minutes. QuantityMagnitude drops the unit of measure.

q=QuantityMagnitude[Plus@@((TimeObject[#]-TimeObject[{0,0,0}])&/@%)]


92.0167

4-Convert 92.0167 minutes into hours, minutes, seconds.

QuotientRemainder[q,60]/.{h_,m_}:> Row[{h,"h ",IntegerPart@m,"m ",
Round[60 FractionalPart[m]],"s "}]


1h 32m 1s

# Perl, 146

My entry prints the output with a trailing space - I hope that's ok

while(<>){for(/(\d?\d(?::\d\d){1,2})/g){$m=1;for(reverse split/:/,$_){$t+=$m*$_;$m*=60}}}for('s','m'){$o=($t%60)."$_$o";$t/=60}print int$t,"h $o"  If we can assume there will be only one time per line of input, we can chop 4 characters: while(<>){if(/(\d?\d(:\d\d){1,2})/){$m=1;for(reverse split/:/,$&){$t+=$m*$_;$m*=60}}}for('s','m'){$o=($t%60)."$_ $o";$t/=60}print int$t,"h$o"
`

These work by accumulating the total seconds elapsed and formatting that value afterwards.