File Coverage

blib/lib/Time/Elapsed.pm
Criterion Covered Total %
statement 130 155 83.8
branch 32 60 53.3
condition 9 22 40.9
subroutine 31 32 96.8
pod 1 1 100.0
total 203 270 75.1


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