File Coverage

blib/lib/Time/Elapsed.pm
Criterion Covered Total %
statement 136 161 84.4
branch 32 60 53.3
condition 9 22 40.9
subroutine 33 34 97.0
pod 1 1 100.0
total 211 278 75.9


line stmt bran cond sub pod time code
1             package Time::Elapsed;
2 1     1   16397 use strict;
  1         1  
  1         26  
3 1     1   3 use warnings;
  1         1  
  1         26  
4 1     1   3 use utf8;
  1         6  
  1         5  
5 1     1   31 use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS );
  1         2  
  1         74  
6             # time constants
7 1     1   4 use constant SECOND => 1;
  1         3  
  1         84  
8 1     1   4 use constant MINUTE => 60 * SECOND;
  1         1  
  1         48  
9 1     1   3 use constant HOUR => 60 * MINUTE;
  1         1  
  1         35  
10 1     1   3 use constant DAY => 24 * HOUR;
  1         1  
  1         39  
11 1     1   3 use constant WEEK => 7 * DAY;
  1         1  
  1         40  
12 1     1   3 use constant MONTH => 30 * DAY;
  1         2  
  1         40  
13 1     1   8 use constant YEAR => 365 * DAY;
  1         1  
  1         35  
14             # elapsed data fields
15 1     1   3 use constant INDEX => 0;
  1         1  
  1         32  
16 1     1   3 use constant MULTIPLIER => 1;
  1         1  
  1         39  
17 1     1   13 use constant FIXER => 2;
  1         2  
  1         52  
18 1     1   4 use base qw( Exporter );
  1         1  
  1         92  
19 1     1   3 use Carp qw( croak );
  1         1  
  1         57  
20              
21 1     1   3 use constant T_SECOND => 60;
  1         2  
  1         38  
22 1     1   3 use constant T_MINUTE => T_SECOND;
  1         1  
  1         40  
23 1     1   3 use constant T_HOUR => T_SECOND;
  1         1  
  1         32  
24 1     1   3 use constant T_DAY => 24;
  1         1  
  1         32  
25 1     1   3 use constant T_WEEK => 7;
  1         2  
  1         27  
26 1     1   2 use constant T_MONTH => 30;
  1         1  
  1         32  
27 1     1   2 use constant T_MONTHW => 4;
  1         2  
  1         32  
28 1     1   3 use constant T_YEAR => 12;
  1         1  
  1         64  
29              
30             BEGIN {
31 1     1   2 $VERSION = '0.32';
32 1         1 @EXPORT = qw( elapsed );
33 1         1219 %EXPORT_TAGS = ( all => [ @EXPORT, @EXPORT_OK ] );
34             }
35              
36             # elapsed time formatter keys
37             my $EC = 0;
38             my $ELAPSED = {
39             # name index multiplier fixer
40             second => [ $EC++, T_SECOND, T_MINUTE ],
41             minute => [ $EC++, T_MINUTE, T_HOUR ],
42             hour => [ $EC++, T_HOUR, T_DAY ],
43             day => [ $EC++, T_DAY, T_MONTH ],
44             month => [ $EC++, T_MONTH, T_YEAR ],
45             year => [ $EC++, T_YEAR, 1 ],
46             };
47              
48             my $EW = 0;
49             my $ELAPSED_W = {
50             # name index multiplier fixer
51             second => [ $EW++, T_SECOND, T_MINUTE ],
52             minute => [ $EW++, T_MINUTE, T_HOUR ],
53             hour => [ $EW++, T_HOUR, T_DAY ],
54             day => [ $EW++, T_DAY, T_WEEK ],
55             week => [ $EW++, T_WEEK, T_MONTHW ],
56             month => [ $EW++, T_MONTHW, T_YEAR ],
57             year => [ $EW++, T_YEAR, 1 ],
58             };
59              
60             # formatters for _fixer()
61             my $FIXER = { map { $_ => $ELAPSED->{$_}[FIXER] } keys %{ $ELAPSED } };
62             my $FIXER_W = { map { $_ => $ELAPSED_W->{$_}[FIXER] } keys %{ $ELAPSED_W } };
63              
64             my $NAMES = [ sort { $ELAPSED->{ $a }[INDEX] <=> $ELAPSED->{ $b }[INDEX] }
65             keys %{ $ELAPSED } ];
66              
67             my $NAMES_W = [ sort { $ELAPSED_W->{ $a }[INDEX] <=> $ELAPSED_W->{ $b }[INDEX] }
68             keys %{ $ELAPSED_W } ];
69              
70             my $LCACHE; # language cache
71              
72             sub import {
73 1     1   8 my($class, @raw) = @_;
74 1         1 my @exports;
75 1         2 foreach my $e ( @raw ) {
76 1 50 0     5 _compile_all() && next if $e eq '-compile';
77 1         2 push @exports, $e;
78             }
79 1         131 return $class->export_to_level( 1, $class, @exports );
80             }
81              
82             sub elapsed {
83 30     30 1 169 my $sec = shift;
84 30 100       63 return if ! defined $sec;
85 28   100     44 my $opt = shift || {};
86 28 100       68 $opt = { lang => $opt } if ! ref $opt;
87 28 100       48 $sec = 0 if !$sec; # can be empty string
88 28         23 $sec += 0; # force number
89              
90 28   100     66 my $l = _get_lang( $opt->{lang} || 'EN' ); # get language keys
91 28 100       72 return $l->{other}{zero} if ! $sec;
92              
93 12   50     37 my $w = $opt->{weeks} || 0;
94 12         29 my @rv = _populate(
95             $l,
96             _fixer(
97             $w,
98             _parser(
99             $w,
100             _examine( abs($sec), $w )
101             )
102             )
103             );
104              
105 12         32 my $last_value = pop @rv;
106              
107 12 50       67 return @rv ? join(', ', @rv) . " $l->{other}{and} $last_value"
108             : $last_value; # only a single value, no need for template/etc.
109             }
110              
111             sub _populate {
112 12     12   16 my($l, @parsed) = @_;
113 12         9 my @buf;
114 12         15 foreach my $e ( @parsed ) {
115 48 100       74 next if ! $e->[MULTIPLIER]; # disable zero values
116 36 100       46 my $type = $e->[MULTIPLIER] > 1 ? 'plural' : 'singular';
117 36         89 push @buf, join q{ }, $e->[MULTIPLIER], $l->{ $type }{ $e->[INDEX] };
118             }
119 12         31 return @buf;
120             }
121              
122             sub _fixer {
123             # There can be values like "60 seconds". _fixer() corrects this kind of error
124 12     12   13 my($weeks, @raw) = @_;
125 12         8 my(@fixed,$default,$add);
126              
127 12 50       15 my $f = $weeks ? $FIXER_W : $FIXER;
128 12 50       13 my $e = $weeks ? $ELAPSED_W : $ELAPSED;
129 12 50       14 my $n = $weeks ? $NAMES_W : $NAMES;
130              
131 12         9 my @top;
132 12         23 foreach my $i ( reverse 0..$#raw ) {
133 48         37 my $r = $raw[$i];
134 48         49 $default = $f->{ $r->[INDEX] };
135 48 50       53 if ( $add ) {
136 0         0 $r->[MULTIPLIER] += $add; # we need a fix
137 0         0 $add = 0; # reset
138             }
139              
140             # year is the top-most element currently does not have any limits (def=1)
141 48 50 33     80 if ( $r->[MULTIPLIER] >= $default && $r->[INDEX] ne 'year' ) {
142 0         0 $add = int $r->[MULTIPLIER] / $default;
143 0         0 $r->[MULTIPLIER] -= $default * $add;
144 0 0       0 if ( $i == 0 ) { # we need to add to a non-existent upper level
145 0         0 my $id = $e->{ $r->[INDEX] }[INDEX];
146 0   0     0 my $up = $n->[ $id + 1 ]
147             || die "Can not happen: unable to locate top-level\n";
148 0         0 unshift @top, [ $up, $add ];
149             }
150             }
151              
152 48         97 unshift @fixed, [ $r->[INDEX], $r->[MULTIPLIER] ];
153             }
154              
155 12         16 unshift @fixed, @top;
156 12         28 return @fixed;
157             }
158              
159             sub _parser { # recursive formatter/parser
160 48     48   42 my($weeks, $id, $mul) = @_;
161 48 50       53 my $e = $weeks ? $ELAPSED_W : $ELAPSED;
162 48 50       45 my $n = $weeks ? $NAMES_W : $NAMES;
163 48         44 my $xmid = $e->{ $id }[INDEX];
164 48 100       120 my @parsed = [ $id, $xmid ? int $mul : sprintf '%.0f', $mul ];
165              
166 48 100       84 if ( $xmid ) {
167             push @parsed, _parser(
168             $weeks,
169             $n->[ $xmid - 1 ],
170 36         77 ($mul - int $mul) * $e->{$id}[MULTIPLIER]
171             );
172             }
173              
174 48         73 return @parsed;
175             }
176              
177             sub _examine {
178 12     12   12 my($sec, $weeks) = @_;
179             return
180 12 0 33     77 $sec >= YEAR ? ( year => $sec / YEAR )
    0          
    50          
    50          
    50          
    50          
181             : $sec >= MONTH ? ( month => $sec / MONTH )
182             : $sec >= WEEK && $weeks ? ( week => $sec / WEEK )
183             : $sec >= DAY ? ( day => $sec / DAY )
184             : $sec >= HOUR ? ( hour => $sec / HOUR )
185             : $sec >= MINUTE ? ( minute => $sec / MINUTE )
186             : ( second => $sec )
187             ;
188             }
189              
190             sub _get_lang {
191 28   33 28   40 my $lang = shift || croak '_get_lang(): Language ID is missing';
192 28         29 $lang = uc $lang;
193 28 100       44 if ( ! exists $LCACHE->{ $lang } ) {
194 4 50 33     29 if ( $lang =~ m{[^a-z_A-Z_0-9]}xms || $lang =~ m{ \A [0-9] }xms ) {
195 0         0 croak "Bad language identifier: $lang";
196             }
197 4         8 _set_lang_cache( $lang );
198             }
199 28         32 return $LCACHE->{ $lang };
200             }
201              
202             sub _set_lang_cache {
203 4     4   6 my($lang) = @_;
204 4         9 my $class = join q{::}, __PACKAGE__, 'Lang', $lang;
205 4         27 my $file = join(q{/} , split m{::}xms, $class ) . '.pm';
206 4         1912 require $file;
207 4         24 $LCACHE->{ $lang } = {
208             singular => { $class->singular },
209             plural => { $class->plural },
210             other => { $class->other },
211             };
212 4         12 return;
213             }
214              
215             sub _compile_all {
216 0     0     require File::Spec;
217 0           require Symbol;
218 0           my($test, %lang);
219              
220             # search lib paths
221 0           foreach my $lib ( @INC ) {
222 0           $test = File::Spec->catfile( $lib, qw/ Time Elapsed Lang /);
223 0 0         next if not -d $test;
224 0           my $LDIR = Symbol::gensym();
225 0 0         opendir $LDIR, $test or croak "opendir($test): $!";
226              
227 0           while ( my $file = readdir $LDIR ) {
228 0 0         next if -d $file;
229 0 0         if ( $file =~ m{ \A (.+?) \. pm \z }xms ) {
230 0           $lang{ uc $1 }++;
231             }
232             }
233              
234 0           closedir $LDIR;
235             }
236              
237             # compile language data
238 0           foreach my $id ( keys %lang ) {
239 0           _get_lang( $id );
240             }
241              
242 0           return 1;
243             }
244              
245             1;
246              
247             __END__