#!/usr/bin/perl -w
#
# $Id: sumtime,v 1.9 2003/08/01 02:10:57 jmates Exp $
#
# The author disclaims all copyrights and releases this script into the
# public domain.
#
# Totals up short-hand time durations such as 3h20m.
#
# Run perldoc(1) on this file for additional documentation.

require 5;
use strict;

my $VERSION;
($VERSION = '$Revision: 1.9 $ ') =~ s/[^0-9.]//g;

my $duration;

# how to convert various time units to seconds
my %factor = (
  y => 31536000,
  M => 2592000,
  w => 604800,
  d => 86400,
  h => 3600,
  m => 60,
  s => 1,
);

# tie y, M to current year and month if possible
eval { require Date::Calc; };
unless ($@) {
  my ($month, $year) = (localtime)[4, 5];
  $year += 1900;
  $month++;

  $factor{y} = Date::Calc::Days_in_Year($year, 12) * $factor{d}
   or die "error: could not calculate days in year\n";
  $factor{M} = Date::Calc::Days_in_Month($year, $month) * $factor{d}
   or die "error: could not calculate days in month\n";
}

my $factors = join '', keys %factor;

# parse command-line options
use Getopt::Std;
my %opts;
getopts('h?', \%opts);
help() if exists $opts{'h'} or exists $opts{'?'};

if (@ARGV) {
  $_ = "@ARGV";
  parse_line();
} else {
  while (<STDIN>) {
    parse_line();
  }
}

sub parse_line {
 LOOP: {

    # match "3h" or "45 m"
    $duration += $1 * $factor{$2}, redo LOOP
     if /\G(\d+) ?([$factors])(?=\b|\d)/gco;

    # treat unmatched numbers as seconds
    $duration += $1, redo LOOP if /\G(\d+)/gc;

    # or skip to next interesting bit
    redo LOOP if /\G\D+/gc;
  }
}

die "error: no parseable duration found\n"
 unless defined $duration
 and $duration =~ /^\d+$/;

print deltatimefmt($duration), "\n";
exit;

sub deltatimefmt {
  my $difference = int(shift);

  return "0s" if $difference == 0;

  my $seconds = $difference % 60;
  $difference = ($difference - $seconds) / 60;
  my $minutes = $difference % 60;
  $difference = ($difference - $minutes) / 60;

  #  my $hours = $difference;
  my $hours = $difference % 24;
  $difference = ($difference - $hours) / 24;
  my $days  = $difference % 7;
  my $weeks = ($difference - $days) / 7;

  # better way to do this?
  my $temp = ($weeks) ? "${weeks}w " : '';
  $temp .= ($days)    ? "${days}d "    : '';
  $temp .= ($hours)   ? "${hours}h "   : '';
  $temp .= ($minutes) ? "${minutes}m " : '';
  $temp .= ($seconds) ? "${seconds}s"  : '';
  return $temp;
}

# a generic help blarb
sub help {
  print <<"HELP";
Usage: $0 [opts] [time specifications]

Totals up short hand time durations.

Options for version $VERSION:
  -h/-?  Display this message

Run perldoc(1) on this script for additional documentation.

HELP
  exit;
}

__END__

=head1 NAME

sumtime - totals up short-hand time durations

=head1 SYNOPSIS

  $ sumtime 27h 71s
  1d 3h 1m 11s

  $ (echo 1m 2m; echo 3m 4m) | sumtime
  10m

=head1 DESCRIPTION

=head2 Overview

Short description of what script is intended to do.

=head2 Normal Usage

  $ sumtime [options] [time specifications]

Input is either taken from the data on the command line, or failing
that, from STDIN.

See L<"OPTIONS"> for details on the command line switches supported.

=head1 OPTIONS

This script currently supports the following command line switches:

=over 4

=item B<-h>, B<-?>

Prints a brief usage note.

=back

=head1 BUGS

=head2 Reporting Bugs

Newer versions of this script may be available from:

http://sial.org/code/perl/

If the bug is in the latest version, send a report to the author.
Patches that fix problems or add new features are welcome.

=head2 Known Issues

No known bugs.

=head1 TODO

Output formatting, e.g. raw seconds, or ability to select output time
format (e.g. hours only, and so forth).  Ability to set a default
input factor, e.g. when passing in unadorned hours or minutes.

=head1 SEE ALSO

perl(1)

=head1 AUTHOR

Jeremy Mates, http://sial.org/contact/

=head1 COPYRIGHT

The author disclaims all copyrights and releases this script into the
public domain.

=head1 VERSION

  $Id: sumtime,v 1.9 2003/08/01 02:10:57 jmates Exp $

=head1 SCRIPT CATEGORIES

Utilities

=cut
