to write my first serious module. There were two solutions already provided that didn't require wheel inventing, but I figured getting shredded by you fellow monks would be the best way to get better. I have
even though this module does have some functionality the others do not. I would however like comments as though it were being posted. Please be
. I have already placed things down at the __end__ that I know are lacking and would love pointers on them as well.
package Seconds2English;
use strict;
use Carp;
our $VERSION = '0.01';
sub new {
my $class = shift;
croak "Improper option use" if (@_ % 2);
my %opts = @_;
my $self = bless {}, $class;
$self->_build(\%opts);
return $self;
}
sub _build {
my ($self, $opt_ref) = @_;
if (exists $opt_ref->{start}) {
my $secs = $opt_ref->{start};
$self->{time} = int(abs($secs)) if _is_number($secs);
delete $opt_ref->{start};
}
$self->{_croak} = sub {croak "$_[0] is not a valid option"};
$self->{time} = 0 unless (exists $self->{time});
@{$self->{_list}} = qw(years months weeks days hours minutes secon
+ds);
@{$self->{_table}}{@{$self->{_list}}} = (31536000, 2592000, 604800
+, 86400, 3600, 60, 1);
$self->_update_vals;
$self->_update_table($opt_ref) if (%{$opt_ref});
}
sub _is_number {
# From Scalar::Util
local $_ = shift;
return 0 unless defined;
return 1 if (/^[+-]?\d+$/);
return 1 if (/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/);
0;
}
sub _update_vals {
my ($self, $max) = @_;
my $seconds = exists $self->{time} ? $self->{time} : 0;
$self->{units} = ();
my ($key, @units) = $max ? ('_english', @{$max}) : ('english', @{$
+self->{_list}});
for my $unit (@units) {
if ($self->{_table}{$unit} > $seconds ) {
$self->{$unit} = 0;
next;
}
my $key = $max ? "_$unit" : $unit;
$self->{$key} = int($seconds / $self->{_table}{$unit});
push @{$self->{units}}, "$self->{$key} $unit";
chop ${$self->{units}}[-1] if ($self->{$key} < 2);
$seconds %= $self->{_table}{$unit};
}
unless (defined $self->{units}) {
$self->{$key} = '';
return;
}
if (@{$self->{units}} > 2) {
my $last = pop @{$self->{units}};
$self->{$key} = join ', ', @{$self->{units}}, "and $last";
}
elsif (@{$self->{units}} > 1) {
$self->{$key} = join ' and ' , @{$self->{units}};
}
elsif (@{$self->{units}} > 0) {
$self->{$key} = "${$self->{units}}[0]";
}
}
sub _update_table {
my ($self, $opt_ref) = @_;
while (my ($key, $val) = each %{$opt_ref}) {
unless ($key eq 'months' || $key eq 'years') {
$self->{_croak}($key);
}
if (_is_number($val)) {
$val = int(abs($val));
$self->{_table}{$key} = $val if ($val > 0);
}
}
$self->_update_vals;
}
sub seconds {return $_[0]->{seconds}}
sub minutes {return $_[0]->{minutes}}
sub hours {return $_[0]->{hours}}
sub days {return $_[0]->{days}}
sub weeks {return $_[0]->{weeks}}
sub months {return $_[0]->{months}}
sub years {return $_[0]->{years}}
sub english {return $_[0]->{english}}
sub time {return $_[0]->{time}}
sub in_seconds {return $_[0]->{time}}
sub in_minutes {return $_[0]->{time} / $_[0]->{_table}{minutes}}
sub in_hours {return $_[0]->{time} / $_[0]->{_table}{hours}}
sub in_days {return $_[0]->{time} / $_[0]->{_table}{days}}
sub in_weeks {return $_[0]->{time} / $_[0]->{_table}{weeks}}
sub in_months {return $_[0]->{time} / $_[0]->{_table}{months}}
sub in_years {return $_[0]->{time} / $_[0]->{_table}{years}}
sub english_max {
my ($self, $val) = @_;
$self->_custom_english('max', $val);
}
sub english_min {
my ($self, $val) = @_;
$self->_custom_english('min', $val);
}
sub _custom_english {
my ($self, $type, $val) = @_;
return $self->english unless $val;
for my $index (0 .. $#{$self->{_list}}) {
if ($val eq ${$self->{_list}}[$index]) {
my ($low, $high) = $type eq 'max' ? ($index, $#{$self->{_l
+ist}}): (0, $index);
$self->_update_vals([@{$self->{_list}}[$low .. $high]]);
return $self->{_english};
}
}
$self->{_croak}($val);
}
sub get {
my ($self, @subs) = @_;
my @values;
for my $sub (@subs) {
$self->{_croak}($sub) if ($sub =~ /^(_|get|new|set|modify)/);
eval {push @values, $self->$sub};
$self->{_croak}($sub) if $@;
}
return wantarray ? @values : \@values;
}
sub set {
my $self = shift;
croak "Improper option use" if (@_ % 2);
my %mods = @_;
while (my ($key, $val) = each %mods) {
if ($val =~ /^([-+])(.*)/) {
my ($op, $amount) = ($1, $2);
$self->{_croak}($amount) unless _is_number($amount);
unless (grep /\b$key\b/ , (@{$self->{_list}})) {
$self->{_croak}($key);
}
$amount *= $self->{_table}{$key};
$op eq '+' ? ($self->{time} += $amount) : ($self->{time} -
+= $amount);
}
else {
$self->{_croak}("$key => $val");
}
}
$self->{time} = int(abs($self->{time}));
$self->_update_vals;
}
sub modify_interval {
my $self = shift;
croak "Improper option use" if (@_ % 2);
my %opts = @_;
$self->_update_table(\%opts) if %opts;
}
1;
__END__
To Do
1. POD - it can't be that hard, see [id://252477] or [id://107642]
2. Tests - not sure what to test, you tried to break it as you wrote
+it
3. Comments! Stop thinking it is obvious, remember looking at it agai
+n 10 minutes later
4. Code a way to allow more user friendly options instead of just raw
+ seconds
5. Consider other time intervals - decades, centuries, etc?
Notes to self for later use in POD
1. Think up a good name
2. Caveats - seconds are truncated to nearest positive whole integer
+first
3. Compare this module to others that do similar things
4. Methods
A. new
my $obj = Seconds2English->new('start' => time);
my $obj = Seconds2English->new('start' => 144_000, 'months' =>
+ 60 * 60 * 24 * 31);
my $obj = Seconds2English->new('years' => 31556930);
B. years, months, weeks, days, hours, minutes, seconds
print $obj->hours, "\n"; # Returns number of hours currently
+stored in object
C. time
print $obj->time, "\n"; # Returns total number of seconds to
+create object
D. english
my $obj = Seconds2English->new('start' => 144_144);
print $obj->english, "\n"; # Returns user-friendly english str
+ing
# output = "1 day, 16 hours, 2 minutes, and 24 seconds"
E. in_years, in_months, in_weeks, in_days, in_hours, in_minutes,
+in_seconds
print $obj->in_months, "\n"; # Returns object time in just spe
+cified unit (integer)
F. english_max
my $obj = Seconds2English->new('start' => 144_144_144);
print $obj->english_max('weeks'), "\n"; # no interval above un
+it in output
# output = 238 weeks, 2 days, 8 hours, 2 minutes, and 24 secon
+ds
G. english_min
my $obj = Seconds2English->new('start' => 144_144_144);
print $obj->english_min('weeks'), "\n"; # no interval below un
+it in output
# output = 4 years, 6 months, and 4 weeks
H. get
my $obj = Seconds2English->new('start' => 144_144_144);
my ($years, $days, $minutes) = $obj->get('years', 'days', 'min
+utes');
my $array_ref = $obj->get('years', 'days', 'minutes');
I. set
my $obj = Seconds2English->new('start' => 456_789);
print $obj->english, "\n";
# ouput = 5 days, 6 hours, 53 minutes, and 9 seconds
$obj->set('days' => '-3', 'hours' => '+4', 'seconds' => '-8');
print $obj->english, "\n";
# ouput = 2 days, 10 hours, 53 minutes, and 1 second
J. modify_interval
my $obj = Seconds2English->new('start' => 144_144_144);
print $obj->english, "\n";
# output = 4 years, 6 months, 4 weeks, 8 hours, 2 minutes, and
+ 24 seconds
$obj->modify_interval('years' => 31556930, 'months' => 3155693
+0 / 12);
print $obj->english, "\n";
# output = 4 years, 6 months, 3 weeks, 3 days, 17 hours, 52 mi
+nutes, and 40 seconds
Again, this was a toy project to improve, so feel free to beat me about the head for anything you think could/should be better.