#!/export/opt/bin/perl -w # # accumtimmie # (begun 1996-08-15, Lutz Prechelt (prechelt@ira.uka.de) # changed 1997-10-06 # based on project_worktime by S. Haenssgen as of 1996-01-12) # see usage string below. #----- constants: $timmiedir = $ENV{"TIMMIEDIR"} ? $ENV{"TIMMIEDIR"} : ($ENV{"HOME"} . "/.timmie"); $timmiedir = "." if ! -d $timmiedir; # use local dir if other does not exist $century = 19; # current century cardinal number (missing in localtime()) $usagestring = "usage: accumtimmie [-q] [-p] project... from [to]) to sum the time spent on certain projects from timmie logfiles Examples: accumtimmie code test 1997-08-01 1997-08-31 sums time for projects 'code', 'test', and all their subprojects 'code.firstsub', 'code.2', 'test.me', 'test.__' and subsubprojects 'code.firstsub.sub' etc. over all of August 1997. accumtimmie code test 1997-08 ditto. accumtimmie code test 1997 ditto, but sums over all of 1997. accumtimmie code test 1997-08-01 ditto, but sums from August 1st, 1997, to today (inclusive) accumtimmie -p 'code' 1997-08 sums time for all projects containing the regular expression 'code', e.g. 'code.firstsub', 'code.2', 'code.firstsub.sub', 'myproj.code_it' etc. over all of August 1997. accumtimmie -p . 1997-08 sums time for ALL projects over all of August 1997. Given project names must not look like dates or numbers. Subprojects are identified by hierarchical names, separator is a dot. Years must always be specified using 2 or 4 digits. Protocolfiles are in directory \$TIMMIEDIR or '~/.timmie' or '.' -q means don't display projects, only (sub)totals. "; #----- variables: %timesum = (); # $timesum{$p} is sum of hours found in files for project $p @prefix = (); # list of projectname prefixes for which to sum times @regexp = (); # list of projectname regular expressions for w. to sum times $errorsN = 0; # during argument processing $daysN = 0; # how many days with timmiefiles are in time range #-------------------- here we go: ------------------------------ #----- determine 'from' and 'to' dates: @_ = localtime(time); $year = $_[5]+100*$century; $month = $_[4]+1; $day = $_[3]; $today = sprintf "%4d-%02d-%02d", $year, $month, $day; # date string! $toarg = pop @ARGV; # must be a date $fromarg = pop @ARGV; # assume as date. If not, we push this arg back later. if (($toarg =~ m/^\d\d/) && ($fromarg !~ m/^\d\d/)) { # only one date argument present, compute 'from' and 'to' from it: # if it is a day, use it as 'from' and today as 'to' # otherwise use it as month or year for both 'from' and 'to' push @ARGV, $fromarg; # push back non-date argument $fromarg = $toarg; $toarg = $today if $toarg =~ m/^\d\d\d?\d?-\d\d?-\d\d?$/; } else { $errorsN++ unless ($fromarg =~ m/^\d\d/) && ($toarg =~ m/^\d\d/); } $fromarg = "$century$fromarg" if $fromarg =~ m/^\d\d\D$/; $fromarg .= '-01' if $fromarg =~ m/^\d\d\d\d$/; $fromarg .= '-01' if $fromarg =~ m/^\d\d\d\d-\d\d?$/; $toarg = "$century$toarg" if $toarg =~ m/^\d\d\D$/; $toarg .= '-12' if $toarg =~ m/^\d\d\d\d$/; $toarg .= '-31' if $toarg =~ m/^\d\d\d\d-\d\d?$/; $fromarg =~ m/(\d\d\d\d)-(\d\d?)-(\d\d?)/; ($from_year, $from_month, $from_day) = ($1, int($2), int($3)); $toarg =~ m/(\d\d\d\d)-(\d\d?)-(\d\d?)/; ($to_year, $to_month, $to_day) = ($1, int($2), int($3)); #----- process other arguments: while ($_ = shift) { $use_regexps = 1, next if $_ eq "-p"; $quiet = 1, next if $_ eq "-q"; $errorsN++ if m/^-[^p]/; $use_regexps ? (push @regexp, $_) : (push @prefix, $_); } die $usagestring if $errorsN > 0; #----- sum times into %timesum: ($year, $month, $day) = ($from_year, $from_month, $from_day); while (($year < $to_year) || # as long as current date <= "to" date (($year == $to_year) && (($month < $to_month) || (($month == $to_month) && ($day <= $to_day))))) { my $filename = sprintf("%04d-%02d-%02d",$year,$month,$day); $day++; if ($day > 31) { # don't care for february 31th etc: $day = 1; # impossible dates just have no $month++; # corresponding file if ($month > 12) { $month = 1; $year++; } printf "%04d-%02d\n",$year,$month; # make some noise } open(FILE, "$timmiedir/$filename") || next; # just go ahead if no such file $daysN++; # another day with actual work done (and timmie'ed) found while () { chomp; if (/^\s*(\d+):(\d+) +(.*)/) { # Project line? my $project = $3; # (hours:minutes project_name) $hours = $1 + $2 / 60.0; $timesum{$project} = 0 if !defined($timesum{$project}); $timesum{$project} += $hours; } } } #----- generate report: &report_by_regexp if $use_regexps; &report_by_prefix if $#prefix > -1; exit 0; #------------------------------------------------------------------------ # subroutines #------------------------------------------------------------------------ sub report_by_regexp { # print values of all projects matching one of the @regexp # Report subtotals for each regexp and grand total. my ($totalsum, $projectsum, $match) = (0, 0, 0); my @projects = sort { $a cmp $b } keys %timesum; # sort alpha my %regexpsum; print "Hours worked (regexp match) $fromarg to $toarg ", "($daysN day(s) worked)\n"; foreach $exp (@regexp) { $regexpsum{$exp} = 0; # initialize } #----- print relevant projects: foreach $p (@projects) { my ($matchesN) = (0); $projectsum = $timesum{$p}; foreach $exp (@regexp) { $match = ($p =~ m/$exp/); $matchesN += $match; $regexpsum{$exp} += $projectsum if $match; } if ($matchesN > 0) { printf "%7.1f %s\n", $projectsum, $p unless $quiet; $totalsum += $projectsum; } } #----- print regexp subtotals: foreach $exp (@regexp) { printf "%7.1f Subtotal for '%s'\n", $regexpsum{$exp}, $exp; } #----- print total: printf "%7.1f TOTAL\n\n", $totalsum; } sub report_by_prefix { # print values of all projects matching one of the prefixes plus all # subtotals for superprojects on any subproject nesting level my ($totalsum, $projectsum, $match) = (0, 0, 0); my ($p, $pre, $subtot); # loop variables my @projects = sort { $a cmp $b } keys %timesum; # sort alpha my %prefixsum = (); my %subtotals = (); my @subtotals = (); print "Hours worked (by subprojects) $fromarg to $toarg ", "($daysN day(s) worked)\n"; #----- print relevant projects: print "Projects:\n" unless $quiet; foreach $p (@projects) { my ($matchesN) = (0); $projectsum = $timesum{$p}; foreach $pre (@prefix) { $match = (substr $p, 0, length $pre) eq $pre; $matchesN += $match; #$prefixsum{$pre} += $projectsum if $match; } if ($matchesN > 0) { my $superproject = $p; printf "%7.1f %s\n", $projectsum, $p unless $quiet; $totalsum += $projectsum; for (;;) { # extract a.bb.cfg from any a.bb.cfg.ddd or exit loop ($superproject =~ m/^(.+)(\.[^\.]*)$/) || last; $superproject = $1; $subtotals{$1} = 0 if !defined($subtotals{$1}); $subtotals{$1} += $projectsum; } } } #----- print superproject subtotals: print "Subtotals:\n"; @subtotals = sort keys %subtotals; foreach $subtot (@subtotals) { my $direct = defined ($timesum{$subtot}) ? $timesum{$subtot} : 0; # $direct is not yet counted in %subtotal! printf "%7.1f %s.*\n", $subtotals{$subtot} + $direct, $subtot; } #----- print total: printf "%7.1f TOTAL\n", $totalsum; } #!!! sub hour_as_string { my $hours = shift; return(sprintf("%3d:%02dh",$hours,(int($hours*60))%60)); }