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 seconds); @{$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->{_list}}): (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 again 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 string # 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 specified unit (integer) F. english_max my $obj = Seconds2English->new('start' => 144_144_144); print $obj->english_max('weeks'), "\n"; # no interval above unit in output # output = 238 weeks, 2 days, 8 hours, 2 minutes, and 24 seconds G. english_min my $obj = Seconds2English->new('start' => 144_144_144); print $obj->english_min('weeks'), "\n"; # no interval below unit 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', 'minutes'); 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' => 31556930 / 12); print $obj->english, "\n"; # output = 4 years, 6 months, 3 weeks, 3 days, 17 hours, 52 minutes, and 40 seconds