3

The format of the file is as follows:

aaa 1-00:12:43.122
aaa 0-21:14:43.133
bbb 3-11:14:54.433
bbb 2-00:00:10

The numbers on the second column are of the format d-hh:mm:ss.nnn. The last three decimal digits are absent in some cases. I am trying to figure how to sum them grouped by first column to get the total duration for each user. So, for this example, the result would be:

aaa 1-21:27:26.255
bbb 5-11:15:04.433
4

With perl, using a hash and the DateTime::Format::Duration module, with a regex substitution to regularize the nanoseconds:

perl -MDateTime::Format::Duration -ane '
  BEGIN {
    $p = DateTime::Format::Duration->new(pattern => "%e-%H:%M:%S.%N");
  }

  $F[1] =~ s/\d+(\.\d+)?$/sprintf "%02.9f", $&/e;  
  $h{$F[0]} = $h{$F[0]} ? $dt->add_duration($h{$F[0]}) : $dt if $dt = $p->parse_duration($F[1]);

  END { 
    $p = DateTime::Format::Duration->new(pattern => "%e-%H:%M:%S.%3N", normalize => 1);
    for $k (sort keys %h) {printf "%s %s\n", $k, $p->format_duration($h{$k})}
  }
' file
aaa 1-21:27:26.255
bbb 5-11:15:04.433
6

Use this awk script:

BEGIN {
   FS=" |-|:"
}
{
   data[$1][2]+=$2
   data[$1][3]+=$3
   data[$1][4]+=$4
   data[$1][5]+=$5
}
END {
   for( record in data ) {
      if( data[record][5]>=60 ) {
         data[record][4]+=1
         data[record][5]-=60.0
      }
      if( data[record][4]>=60 ) {
         data[record][3]+=1
         data[record][4]-=60
      }
      if( data[record][3]>=24 ) {
         data[record][2]+=1
         data[record][3]-=24
      }
      printf( "%s %d-%02.0f:%02.0f:%06.3f\n", record, data[record][2], data[record][3], data[record][4], data[record][5] )
   }
}

Usage:

~/scratch/se/379631$ cat input
aaa 1-00:12:43.122
aaa 0-21:14:43.133
bbb 3-11:14:54.433
bbb 2-00:00:10
~/scratch/se/379631$ gawk -f 379631.awk input
aaa 1-21:27:26.255
bbb 5-11:15:04.433
  • Thanks! The script works but I am getting wrong results on my file. The hours are going beyond 24, minutes and seconds too go beyond 60 in many cases. – mkc Jul 20 '17 at 2:08
  • That's odd because I am deliberately correcting for that. Though I am only doing one iteration. Change those ifs to whiles perhaps? – DopeGhoti Jul 20 '17 at 15:32
1
perl -F'\h+|[-.:]' -lane '
   $h[keys %h]=$F[0] unless $h{$F[0]};
   $h{$F[0]}[$_-1] += $F[$_] for 1..$#F}{for ( @h )
   {
      my @Arefs = map { \$_ } my($days, $hrs, $mins, $secs, $msec) = @{$h{$_}};
      while ( $msec >= 1000 ) { $secs++; $msec -= 1000; }
      while ( $secs >=   60 ) { $mins++; $secs -=   60; }
      while ( $mins >=   60 ) { $hrs++;  $mins -=   60; }
      while ( $hrs  >=   24 ) { $days++; $hrs  -=   24; }
      print $_, sprintf " %d-%02d:%02d:%02d.%03d", map $$_, @Arefs;
   }
' text.file

perl -F'\h+|[-.:]' -lane '
   $h[keys %h]=$F[0] unless $h{$F[0]};
   $h{$F[0]}[$_-1] += $F[$_] for 1..$#F}{for ( @h )
   {
      use integer;
      my @A = @{$h{$_}};
      $A[0] += ($A[1] += ($A[2] += ($A[3] += $A[4]/1000)/60)/60)/24;
      $A[4] %= 1000; $A[3] %= 60;$A[2] %= 60; $A[1] %= 24;
      print $_, sprintf " %d-%02d:%02d:%02d.%03d", @A;
   }
' text_file

Results

aaa 1-21:27:26.255
bbb 5-11:15:04.433

Explanation

  • Field separator is set to take apart the milliseconds, seconds, minutes, hours, days, and first field key.
  • The input line is split up and stored in an array @F with the elements: $F[0] -> key (aaa/bbb/etc.) $F[1] -> days, $F[2] -> hours, $F[3] -> minutes, $F[4] -> seconds, $F[5] -> milliseconds.
  • Hash keys by their very nature are accessed not in the order they were created, hence we have an array @h whose elements are hash %h keys in the order they were seen.
  • Hash %h is constituted this manner:

%h = (
    aaa => [ days, hours, minutes, seconds, milliseconds ],
    bbb => [                    ...                      ],
    ... 
 );

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.