[Neworleans-pm] Meeting Friday

Dave Cash dave at gnofn.org
Wed Jun 8 07:52:37 PDT 2005


On Wed, 8 Jun 2005, Simon Dorfman wrote:

> I had an idea for the meeting.  I'd like to publicly ask Dave Cash
> (oh, the pressure of asking in public ;-) if he'd be willing to
> share the perl script he uses to keep track of his hours for
> billing clients.  If yes, maybe I'll start using it for keeping
> track of my hours.  My current system is to manually enter start
> and stop times in excel.  Ugh.  Help is needed and maybe I'd learn
> a thing or two along the way.  Would anyone else be interested in
> learning this?  Whad'ya'say Dave?  Wanna package up your script,
> put it online and give a little demo on how you use it?

Simon,

I'm happy to share my little script.  And I'll be happy to explain
how it works.  It's not too fancy, but I have been making little
modifications over the years and it does have a few nice features.
And some strange uses of Perl, especially in the way I implemented
(mostly for fun) format and write stuff.  Code is below.  I
apologize for the lack of docs and comments, but I never imagined
I'd publish it.

> Another thing we might work on is trying to come up with or find
> and configure an existing web-based calendar system for FairGrinds
> Coffeehouse. I was just looking at their calendar:
> http://www.fairgrinds.com/fgnews.html

Good idea, though I'm guessing the bests-of-show will be PHP apps.

> ...and it looks like they may have forgotten us.  We're supposed
> to be meeting Friday, June 10th at 5:30pm - 8pm I don't see us on
> there and I even see two other things scheduled at times that
> conflict with ours.
>
> If I remember, I'll call tomorrow when they're open (948-3222) and
> ask them to make sure we're on the schedule.

Zoinks!  Not again!  I'll call them if you don't want to.  Or I can
even drop by if necessary.  Let me know how attempts at contact go.

Thanks!

Dave

/L\_/E\_/A\_/R\_/N\_/T\_/E\_/A\_/C\_/H\_/L\_/E\_/A\_/R\_/N\
Dave Cash                              Power to the People!
Frolicking in Fields of Garlic               Right On-Line!
dave at gnofn.org                                  Dig it all.




timeclock.pl:

#!/usr/bin/perl

use strict;
use warnings;

use POSIX qw(strftime);
use Time::ParseDate;

my $file_base = $ENV{ HOME } . '/.timeclock';
mkdir $file_base unless -e $file_base;
die "$file_base is not a directory$/" unless -d $file_base;

my ( $opt, $client ) = @ARGV;
if ( $opt !~ /^-/ ) {
	$client = $opt;
	$opt = '';
}
elsif ( $opt !~ /^-(calc|status|edit)$/ ) {
	die "unknown option: $opt";
}
die "usage: clock.pl [-calc | -status | -edit] client\n" if ! $client;

my ( $client_name ) = $client =~ m#([^.]+)\.[^.]+$#;
$client_name ||= $client;
$client =~ s/\.clk$//;

my $client_file = $client =~ m#/# ? "$client.clk" : "$file_base/$client.clk";

my %codes;
if ( -f "$file_base/$client_name.codes" ) {
	open CODES, "< $file_base/$client_name.codes";
	while ( <CODES> ) {
		chomp;
		my ( $code, $description ) = m/^(\S+)\s+(.*)$/;
		$codes{ $code } = $description;
	}
	close CODES;
}

if ( $opt eq '-calc' ) {
	print calc( $client_file ), $/;
}
elsif ( $opt eq '-status' ) {
	print get_status( $client_file ), ".$/";
}
elsif ( $opt eq '-edit' ) {
	edit( $client_file );
	print calc( $client_file ), $/;
}
else {
	my $status = get_status( $client_file );

	open (LOG, ">> $client_file") || die "$client_file: $!";

	if ( $status eq 'stopped' ) {
		print "starting work.\n";
		print LOG 'start: ' . POSIX::strftime('%A %d %B %Y %H:%M:%S', localtime(time)) . $/;
		close LOG;
	}
	else {
		print "stopping work.\n";
		print LOG 'stop:  ' . POSIX::strftime('%A %d %B %Y %H:%M:%S', localtime(time)) . $/;
		if ( %codes ) {
			my $code = get_code( %codes ) if %codes;
			print LOG 'code:  ', $code, $/;
		}
		close LOG;
		print 'Add a comment? [Y/n] ';
		my $answer = <STDIN>;
		chomp $answer;
		edit( $client_file ) if (! $answer) || $answer =~ /^y/i;
		print calc( $client_file ), $/;
	}

}

sub calc {
	my ( $client_file ) = @_;

	$client_file =~ s/\.clk$// unless -e $client_file;
	open ( CLIENT, "< $client_file" ) || die "$client_file: $!";
	my $total_seconds = 0;
	my %subtotals;
	my $hold;
	my $span;
	while ( <CLIENT> ) {
		chomp;
		next unless /^(start|stop|code):\s/;
		if ( $1 eq 'code' ) {
			my ( $coding ) = m/^code:\s+(.*)$/;
			$subtotals{ $coding } += $span;
		}
		else {
			my ( $time ) = m/[tp]:\s+(.*)$/;
			$time =~ s/^(...)\S+\s+(\d+)\s+(...)\S*\s+(\d+)\s+([0-9:]+)/$1 $3 $2 $5 $4/;
			my $seconds = parsedate($time, NO_RELATIVE => 1);
			if ( /^start:/ ) {
				$hold = $seconds;
			}
			elsif ( /^stop:/ ) {
				$span = $seconds - $hold;
				$total_seconds += $span;
				$hold = undef;
			}
		}
	}
	close CLIENT;

	my $report;
	my $bas_fmt = '@>>>>>>>>>>>>  ^<<<<<<<<<<<<<<<<<<<<<<<<  @>>>>>>>  @>>>>>>>>>  @>>>>' . $/;
	my $tab_fmt = '                                          --------  ----------' . $/;
	my $ext_fmt = '~              ^<<<<<<<<<<<<<<<<<<<<<<<<' . $/;

	$report .= scalar keys %subtotals ?
		"Coded Subtotals for $client_name ($client_file):$/" :
		"Total for $client_name ($client_file):$/";
	foreach my $code ( sort keys %subtotals ) {
		my $hours = int( $subtotals{ $code } / 3600 );
		my $minutes = sprintf '%02d', int( ( $subtotals{ $code } % 3600 ) / 60 );
		my $seconds = sprintf '%02d', $subtotals{ $code } % 60;
		my $percentage = int( ( ( $subtotals{ $code } / $total_seconds ) * 100 ) + 0.5 );
		my $dollars = sprintf( '%0.02f', int( ( $subtotals{ $code } / 3600 ) * 4 + 0.5 ) / .04 );
		1 while $dollars =~ s/^(-?\d+)(\d{3})/$1,$2/;
		my $time = "$hours:$minutes:$seconds";
		formline $bas_fmt, $code, $codes{ $code }, $time, '$' . $dollars, $percentage . '%';
		formline $ext_fmt, $codes{ $code } while $codes{ $code };
	}

	my $hours = int( $total_seconds / 3600 );
	my $minutes = sprintf '%02d', int( ( $total_seconds % 3600 ) / 60 );
	my $seconds = sprintf '%02d', $total_seconds % 60;
	my $dollars = int( ( $total_seconds / 3600 ) * 4 + 0.5 ) / .04;
	my $total_time = "$hours:$minutes:$seconds";
	my $total_dollars = sprintf( '%0.02f', int( ( $total_seconds / 3600 ) * 4 + 0.5 ) / .04 );
	1 while $total_dollars =~ s/^(-?\d+)(\d{3})/$1,$2/;
	formline $tab_fmt if $^A;
	my $nothing = '';
	formline $bas_fmt, $nothing, $nothing, $total_time, '$' . $total_dollars, $nothing;

	$report .= $^A;
	chomp $report;
	return $report;
}

sub edit {
	my ( $client_file ) = @_;
	die "$client_file: $!" unless stat $client_file;

	my $lines = int( `wc $client_file` ) - 3;
	my $command_args = $lines ? "+$lines $client_file" : $client_file;
	system "vi $command_args";
}

sub get_code {
	my ( %codes ) = @_;

	my $choice = 97;
	my %choice_map;
	foreach my $code ( sort keys %codes ) {
		printf "%s. %s (%s)$/", chr( $choice ), $code, $codes{ $code };
		$choice_map{ chr( $choice ) } = $code;
		$choice ++;
	}
	print "Choose a code: ";
	my $chosen_code = <STDIN>;
	$chosen_code = lc( substr $chosen_code, 0, 1 );
	print "CHOSEN CODE: '$chosen_code'", $/;

	return exists $choice_map{ $chosen_code } ? $choice_map{ $chosen_code } : get_code( %codes );
}

sub get_status {
	my ( $client_file ) = @_;

	system "touch $client_file" if ! -e $client_file;
	open ( CLIENT, qq!cat $client_file | grep "^st[ao][rp]" | tail -1 |! ) || die "$client_file: $!";
	my $last_line = <CLIENT> || '';
	close CLIENT;

	return $last_line =~ /^stop:/ || ! $last_line ? 'stopped' : 'started';
}


More information about the NewOrleans-pm mailing list