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   38021 use strict;
  1         4  
  1         262  
3 1     1   14 use warnings;
  1         4  
  1         52  
4 1     1   8 use utf8;
  1         8  
  1         9  
5 1     1   45 use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS );
  1         3  
  1         159  
6             # time constants
7 1     1   8 use constant SECOND => 1;
  1         2  
  1         161  
8 1     1   8 use constant MINUTE => 60 * SECOND;
  1         2  
  1         91  
9 1     1   8 use constant HOUR => 60 * MINUTE;
  1         2  
  1         87  
10 1     1   7 use constant DAY => 24 * HOUR;
  1         2  
  1         92  
11 1     1   7 use constant WEEK => 7 * DAY;
  1         2  
  1         86  
12 1     1   8 use constant MONTH => 30 * DAY;
  1         3  
  1         100  
13 1     1   7 use constant YEAR => 365 * DAY;
  1         2  
  1         63  
14             # elapsed data fields
15 1     1   7 use constant INDEX => 0;
  1         2  
  1         53  
16 1     1   7 use constant MULTIPLIER => 1;
  1         2  
  1         63  
17 1     1   24 use constant FIXER => 2;
  1         3  
  1         72  
18 1     1   7 use base qw( Exporter );
  1         3  
  1         278  
19 1     1   9 use Carp qw( croak );
  1         2  
  1         98  
20              
21 1     1   8 use constant T_SECOND => 60;
  1         3  
  1         82  
22 1     1   7 use constant T_MINUTE => T_SECOND;
  1         2  
  1         72  
23 1     1   6 use constant T_HOUR => T_SECOND;
  1         2  
  1         61  
24 1     1   8 use constant T_DAY => 24;
  1         2  
  1         57  
25 1     1   6 use constant T_WEEK => 7;
  1         2  
  1         56  
26 1     1   6 use constant T_MONTH => 30;
  1         3  
  1         80  
27 1     1   6 use constant T_MONTHW => 4;
  1         2  
  1         67  
28 1     1   7 use constant T_YEAR => 12;
  1         2  
  1         130  
29              
30             BEGIN {
31 1     1   4 $VERSION = '0.31';
32 1         4 @EXPORT = qw( elapsed );
33 1         2979 %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   10 my($class, @raw) = @_;
74 1         3 my @exports;
75 1         3 foreach my $e ( @raw ) {
76 1 50 0     5 _compile_all() && next if $e eq '-compile';
77 1         4 push @exports, $e;
78             }
79 1         188 return $class->export_to_level( 1, $class, @exports );
80             }
81              
82             sub elapsed {
83 24     24 1 185 my $sec = shift;
84 24 100       80 return if ! defined $sec;
85 22   100     62 my $opt = shift || {};
86 22 100       82 $opt = { lang => $opt } if ! ref $opt;
87 22 100       59 $sec = 0 if !$sec; # can be empty string
88 22         32 $sec += 0; # force number
89              
90 22   100     81 my $l = _get_lang( $opt->{lang} || 'EN' ); # get language keys
91 22 100       101 return $l->{other}{zero} if ! $sec;
92              
93 10   50     56 my $w = $opt->{weeks} || 0;
94 10         36 my @rv = _populate(
95             $l,
96             _fixer(
97             $w,
98             _parser(
99             $w,
100             _examine( abs($sec), $w )
101             )
102             )
103             );
104              
105 10         167 my $last_value = pop @rv;
106              
107 10 50       98 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 10     10   27 my($l, @parsed) = @_;
113 10         16 my @buf;
114 10         28 foreach my $e ( @parsed ) {
115 40 100       109 next if ! $e->[MULTIPLIER]; # disable zero values
116 30 100       69 my $type = $e->[MULTIPLIER] > 1 ? 'plural' : 'singular';
117 30         151 push @buf, join q{ }, $e->[MULTIPLIER], $l->{ $type }{ $e->[INDEX] };
118             }
119 10         49 return @buf;
120             }
121              
122             sub _fixer {
123             # There can be values like "60 seconds". _fixer() corrects this kind of error
124 10     10   27 my($weeks, @raw) = @_;
125 10         15 my(@fixed,$default,$add);
126              
127 10 50       27 my $f = $weeks ? $FIXER_W : $FIXER;
128 10 50       21 my $e = $weeks ? $ELAPSED_W : $ELAPSED;
129 10 50       22 my $n = $weeks ? $NAMES_W : $NAMES;
130              
131 10         17 my @top;
132 10         38 foreach my $i ( reverse 0..$#raw ) {
133 40         69 my $r = $raw[$i];
134 40         168 $default = $f->{ $r->[INDEX] };
135 40 50       101 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 40 50 33     693 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 40         164 unshift @fixed, [ $r->[INDEX], $r->[MULTIPLIER] ];
153             }
154              
155 10         29 unshift @fixed, @top;
156 10         51 return @fixed;
157             }
158              
159             sub _parser { # recursive formatter/parser
160 40     40   77 my($weeks, $id, $mul) = @_;
161 40 50       91 my $e = $weeks ? $ELAPSED_W : $ELAPSED;
162 40 50       273 my $n = $weeks ? $NAMES_W : $NAMES;
163 40         87 my $xmid = $e->{ $id }[INDEX];
164 40 100       179 my @parsed = [ $id, $xmid ? int $mul : sprintf '%.0f', $mul ];
165              
166 40 100       113 if ( $xmid ) {
167 30         239 push @parsed, _parser(
168             $weeks,
169             $n->[ $xmid - 1 ],
170             ($mul - int $mul) * $e->{$id}[MULTIPLIER]
171             );
172             }
173              
174 40         146 return @parsed;
175             }
176              
177             sub _examine {
178 10     10   24 my($sec, $weeks) = @_;
179             return
180 10 0 33     116 $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 22   33 22   57 my $lang = shift || croak '_get_lang(): Language ID is missing';
192 22         42 $lang = uc $lang;
193 22 100       62 if ( ! exists $LCACHE->{ $lang } ) {
194 3 50 33     20 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 3         7 _set_lang_cache( $lang );
198             }
199 22         178 return $LCACHE->{ $lang };
200             }
201              
202             sub _set_lang_cache {
203 3     3   5 my($lang) = @_;
204 3         10 my $class = join q{::}, __PACKAGE__, 'Lang', $lang;
205 3         13 my $file = join(q{/} , split m{::}xms, $class ) . '.pm';
206 3         17303 require $file;
207 3         25 $LCACHE->{ $lang } = {
208             singular => { $class->singular },
209             plural => { $class->plural },
210             other => { $class->other },
211             };
212 3         17 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__