File Coverage

blib/lib/Time/Duration/Concise.pm
Criterion Covered Total %
statement 139 139 100.0
branch 40 42 95.2
condition 7 10 70.0
subroutine 25 25 100.0
pod 16 16 100.0
total 227 232 97.8


line stmt bran cond sub pod time code
1             package Time::Duration::Concise;
2              
3 9     9   131675 use 5.006;
  9         19  
4 9     9   27 use strict;
  9         8  
  9         157  
5 9     9   19 use warnings FATAL => 'all';
  9         9  
  9         860  
6              
7 9     9   3632 use Time::Seconds;
  9         23405  
  9         514  
8 9     9   3635 use POSIX qw(ceil);
  9         39857  
  9         33  
9 9     9   7194 use Carp;
  9         10  
  9         368  
10 9     9   3480 use Tie::Hash::LRU;
  9         2896  
  9         11959  
11              
12             our %popular;
13             our $lru = tie %popular, 'Tie::Hash::LRU', 100;
14              
15             =head1 NAME
16              
17             Time::Duration::Concise
18              
19             =head1 DESCRIPTION
20              
21             Time::Duration::Concise is an improved approach to convert concise time duration to string representation.
22              
23             =head1 VERSION
24              
25             Version 1.3
26              
27             =cut
28              
29             our $VERSION = '1.3';
30              
31             our %LENGTH_TO_PERIOD = (
32             86400 => 'day',
33             3600 => 'hour',
34             60 => 'minute',
35             1 => 'second',
36             );
37              
38             our %PERIOD_SIZES = (
39             'd' => 86400,
40             'h' => 3600,
41             'm' => 60,
42             's' => 1,
43             );
44              
45             our @KNOWN_UNITS = qw[d h m s];
46              
47             our $KNOWN_UNITS_ = 'dhms';
48              
49             =head1 SYNOPSIS
50              
51             use Time::Duration::Concise;
52              
53             my $duration = Time::Duration::Concise->new(
54             interval => '1h20m'
55             );
56              
57             # Intervals can have decimal values
58             1.5h etc
59              
60             my $duration = Time::Duration::Concise->new(
61             interval => '1.5h'
62             );
63              
64             =head1 FIELDS
65              
66             =head2 interval (REQUIRED)
67              
68             Concise Format
69              
70             The format is an integer followed immediatley by its duration
71             identifier. White-space will be ignored.
72              
73             The following table explains the format.
74              
75             identifier duration
76             ---------- --------
77             d day
78             h hour
79             m minute
80             s second
81              
82             # Intervals can have decimal values
83             Example : 1.5h
84              
85             =cut
86              
87             =head1 METHODS
88              
89             =head2 interval
90              
91             Returns the given time interval.
92              
93             =cut
94              
95             sub interval {
96 1     1 1 4 my ($self) = @_;
97 1         4 return $self->{'_interval'};
98             }
99              
100             =head2 seconds
101              
102             The number of seconds represented by this time interval.
103              
104             =cut
105              
106             sub seconds {
107 133     133 1 310 my ($self) = @_;
108              
109 133 100       412 return $self->{'_seconds'} if $self->{'_seconds'};
110              
111 35         32 my $interval = $self->{'_interval'};
112 35         26 my $known_units = $KNOWN_UNITS_;
113              
114 35         31 my $seconds = 0;
115              
116             # These should be integers, but we might need to have 0.5m
117 35         413 while ( $interval =~ s/([+-]?\d*\.?\d+)([$known_units])// ) {
118 61         97 my $amount = $1;
119 61         58 my $units = $2;
120 61         266 $seconds += $amount * $PERIOD_SIZES{$units};
121             }
122              
123 35 100       68 if ( $interval ne '' ) {
124              
125             # We had something which didn't match the above, which renders this unparseable
126 1         13 Carp::croak( "Bad format supplied [" . $interval . "]: unknown key." );
127             }
128 34         48 $self->{'_seconds'} = int $seconds;
129 34         122 return $self->{'_seconds'};
130             }
131              
132             =head2 minutes
133              
134             The number of minutes represented by this time interval.
135              
136             =cut
137              
138             sub minutes {
139 22     22 1 22 my ($self) = @_;
140 22         30 return $self->duration->{'minutes'};
141             }
142              
143             =head2 hours
144              
145             The number of hours represented by this time interval.
146              
147             =cut
148              
149             sub hours {
150 20     20 1 21 my ($self) = @_;
151 20         27 return $self->duration->{'hours'};
152             }
153              
154             =head2 days
155              
156             The number of days represented by this time interval.
157              
158             =cut
159              
160             sub days {
161 20     20 1 20 my ($self) = @_;
162 20         36 return $self->duration->{'days'};
163             }
164              
165             =head2 weeks
166              
167             The number of week represented by this time interval.
168              
169             =cut
170              
171             sub weeks {
172 1     1 1 2 my ($self) = @_;
173 1         1 return $self->duration->{'weeks'};
174             }
175              
176             =head2 months
177              
178             The number of months represented by this time interval.
179              
180             =cut
181              
182             sub months {
183 2     2 1 3 my ($self) = @_;
184 2         3 return $self->duration->{'months'};
185             }
186              
187             =head2 as_string
188              
189             Concise time druation to string representation.
190              
191             =cut
192              
193             sub as_string {
194 5     5 1 6 my ( $self, $precision ) = @_;
195 5         7 my $time_frames = $self->_duration_array($precision);
196 5         19 return join( ' ', @$time_frames );
197             }
198              
199             =head2 as_concise_string
200              
201             Concise time druation to conscise string representation.
202              
203             =cut
204              
205             sub as_concise_string {
206 27     27 1 1419 my ( $self, $precision ) = @_;
207 27   100     93 $precision ||= 10;
208 27         47 my $time_frames = $self->_duration_array($precision);
209             my @concise_time_frames = map {
210 27         35 $_ =~ s/\s+//ig;
  47         90  
211 47         81 $_ =~ /([-|\+]?\d+[A-Za-z]{1})/ig;
212 47         88 $1;
213             } @$time_frames;
214 27         53 $self->{"_duration_array_$precision"} = undef;
215 27         107 return join( '', @concise_time_frames );
216             }
217              
218             =head2 normalized_code
219              
220             The largest division of Duration
221              
222             =cut
223              
224             sub normalized_code {
225 19     19 1 3702 my ($self) = @_;
226 19         63 my @keys = sort @KNOWN_UNITS;
227              
228 19         16 my $entry_code = '0s';
229 19   66     86 while ( $entry_code eq '0s' and my $period = shift @keys ) {
230 53         46 my $period_length = $PERIOD_SIZES{$period};
231 53 100       68 if ( not $self->seconds % $period_length ) {
232 19         20 my $period_size = $self->seconds / $period_length;
233 19         122 $entry_code = $period_size . $period;
234             }
235             }
236 19         54 return $entry_code;
237             }
238              
239             =head2 duration_array
240              
241             Concise time druation to array
242              
243             [ { value => 1, unit => 'day' }, { value => 2, unit => 'hours' } ]
244              
245             =cut
246              
247             sub duration_array {
248 29     29 1 28 my ( $self, $precision ) = @_;
249 29         45 my $durations = $self->_duration_array($precision);
250 29         20 my @duration_distribution;
251 29         38 foreach my $d (@$durations) {
252 62         78 my @d_value_unit = split( ' ', $d );
253 62         134 push(
254             @duration_distribution,
255             {
256             'value' => $d_value_unit[0],
257             'unit' => $d_value_unit[1]
258             }
259             );
260             }
261 29         76 return \@duration_distribution;
262             }
263              
264             sub _duration_array {
265 61     61   48 my ( $self, $precision ) = @_;
266              
267 61   100     113 $precision ||= 10;
268              
269             return $self->{"_duration_array_$precision"}
270 61 100       155 if $self->{"_duration_array_$precision"};
271              
272 51         72 my $pretty_format = $self->duration->{'time'}->pretty;
273 51         2824 $pretty_format =~ s/minus /-/ig;
274              
275 51         42 my @time_frame;
276 51         45 my $precision_counter = 1;
277 51         124 foreach my $frame ( split( ',', $pretty_format ) ) {
278 175 100       238 next if $precision_counter > $precision;
279 144         111 chomp $frame;
280 144         404 $frame =~ s/^\s+|\s+$//g;
281 144         228 $frame =~ s/s$//ig;
282 144         246 $frame =~ /^([-|\+]?\d+\s)/ig;
283              
284             # Make sure we gets the number
285             # to avoid Use of uninitialized warning
286 144         184 my $value = $1;
287 144 50 33     420 if ( defined $value && $value ) {
288              
289 144         231 $value =~ s/\s+//ig;
290              
291 144 100       236 $frame = '' if $value == 0;
292 144 100       192 $frame .= 's' if $value > 1;
293              
294 144 100       193 if ($frame) {
295 94         97 push( @time_frame, $frame );
296 94         101 $precision_counter++;
297             }
298             }
299             }
300 51 100       105 if ( !scalar @time_frame ) {
301 1         2 push( @time_frame, '0 second' );
302             }
303 51         97 $self->{"_duration_array_$precision"} = \@time_frame;
304 51         73 return \@time_frame;
305             }
306              
307             =head2 multiple_units_of
308              
309             Shorthand to call time methods
310              
311             =cut
312              
313             sub multiple_units_of {
314 7     7 1 693 my ( $self, $unit ) = @_;
315             # two is multiple!
316 7 100       9 return ($self->_minimum_number_of($unit) >= 2) ? 1 : 0;
317             }
318              
319             =head2 minimum_number_of
320              
321             Returns the minimum number of the given period.
322              
323             =cut
324              
325             sub minimum_number_of {
326 7     7 1 478 my ( $self, $unit ) = @_;
327 7         15 return ceil( $self->_minimum_number_of($unit) );
328             }
329              
330             sub _minimum_number_of {
331 14     14   16 my ( $self, $unit ) = @_;
332 14         14 my $orig_unit = $unit;
333 14 100       39 $unit =~ s/s$// if ( length($unit) > 1 ); # Chop plurals, but not 's' itself
334 14         20 $unit = substr( $unit, 0, 1 );
335 14 100       35 $unit = 'mo' if $orig_unit =~ /^months|^mo/ig;
336              
337 14         50 my %unit_maps = (
338             'mo' => 'months',
339             'w' => 'weeks',
340             'd' => 'days',
341             'h' => 'hours',
342             'm' => 'minutes',
343             's' => 'seconds',
344             );
345 14         14 my $method = $unit_maps{$unit};
346 14 100       31 confess "Cannot determine period for $orig_unit" unless ($method);
347              
348 13         40 return $self->$method;
349             }
350              
351             =head2 duration
352              
353             Returns HASH of duration with the following keys
354              
355             'time' # Time::Seconds object
356             'years'
357             'months'
358             'weeks'
359             'days'
360             'hours'
361             'minutes'
362             'seconds'
363              
364             =cut
365              
366             sub duration {
367 120     120 1 101 my ($self) = @_;
368 120 100       722 return $self->{'_duration'} if $self->{'_duration'};
369 34         66 my $time_ = Time::Seconds->new( $self->seconds );
370 34         194 my $duration = {
371             'time' => $time_,
372             'years' => $time_->years,
373             'months' => $time_->months,
374             'weeks' => $time_->weeks,
375             'days' => $time_->days,
376             'hours' => $time_->hours,
377             'minutes' => $time_->minutes,
378             'seconds' => $time_->seconds
379             };
380 34         947 $self->{'_duration'} = $duration;
381 34         102 return $duration;
382             }
383              
384             =head2 get_time_layout
385              
386             Return the duration hash with regards to precision
387              
388             =cut
389              
390             sub get_time_layout {
391 1     1 1 285 my ($self, $precision) = @_;
392 1         2 my $duration = $self->duration;
393             my $time_layout = {
394             duration => $self->seconds,
395             day => $duration->{'day'},
396             hour => $duration->{'hour'},
397 1         2 second => $duration->{'seconds'},
398             display_string => $self->as_string($precision)};
399 1         3 return $time_layout;
400             }
401              
402             =head2 new
403              
404             Object constructor
405              
406             =cut
407              
408             sub new {
409 43     43 1 78683 my $class = shift;
410 43 100       117 my %params_ref = ref( $_[0] ) ? %{ $_[0] } : @_;
  2         7  
411              
412 43         40 my $interval = $params_ref{'interval'};
413              
414 43 100       94 confess "Missing required arguments"
415             unless defined $interval;
416              
417 42 100       212 if ( $popular{$interval} ) {
418             ## Helps in multiple calling, it would really save the time
419 4         14 return $popular{$interval};
420             }
421              
422 38 50       83 if ( defined $interval ) {
423 38 100       77 Carp::croak("Invalid time interval") if $interval eq '';
424             }
425 37         30 my $known_units = $KNOWN_UNITS_;
426              
427             # Try our best to make it parseable.
428 37         53 $interval =~ s/\s//g;
429 37         43 $interval = lc $interval;
430              
431             # All numbers implies a number of seconds.
432 37 100       94 if ( $interval !~ /[A-Za-z]/ ) {
433 2         2 $interval .= 's';
434             }
435              
436 37         60 my $self = { _interval => $interval, };
437 37         50 my $obj = bless $self, $class;
438 37         168 $popular{$interval} = $obj;
439 37         96 return $obj;
440             }
441              
442             =head1 AUTHOR
443              
444             Binary.com, C<< >>
445              
446             =head1 BUGS
447              
448             Please report any bugs or feature requests to C, or through
449             the web interface at L. I will be notified, and then you'll
450             automatically be notified of progress on your bug as I make changes.
451              
452             =head1 SUPPORT
453              
454             You can find documentation for this module with the perldoc command.
455              
456             perldoc Time::Duration::Concise
457              
458              
459             You can also look for information at:
460              
461             =over 4
462              
463             =item * RT: CPAN's request tracker (report bugs here)
464              
465             L
466              
467             =item * AnnoCPAN: Annotated CPAN documentation
468              
469             L
470              
471             =item * CPAN Ratings
472              
473             L
474              
475             =item * Search CPAN
476              
477             L
478              
479             =back
480              
481             =head1 LICENSE AND COPYRIGHT
482              
483             Copyright 2014 Binary.com.
484              
485             This program is free software; you can redistribute it and/or modify it
486             under the terms of the the Artistic License (2.0). You may obtain a
487             copy of the full license at:
488              
489             L
490              
491             Any use, modification, and distribution of the Standard or Modified
492             Versions is governed by this Artistic License. By using, modifying or
493             distributing the Package, you accept this license. Do not use, modify,
494             or distribute the Package, if you do not accept this license.
495              
496             If your Modified Version has been derived from a Modified Version made
497             by someone other than you, you are nevertheless required to ensure that
498             your Modified Version complies with the requirements of this license.
499              
500             This license does not grant you the right to use any trademark, service
501             mark, tradename, or logo of the Copyright Holder.
502              
503             This license includes the non-exclusive, worldwide, free-of-charge
504             patent license to make, have made, use, offer to sell, sell, import and
505             otherwise transfer the Package with respect to any patent claims
506             licensable by the Copyright Holder that are necessarily infringed by the
507             Package. If you institute patent litigation (including a cross-claim or
508             counterclaim) against any party alleging that the Package constitutes
509             direct or contributory patent infringement, then this Artistic License
510             to you shall terminate on the date that such litigation is filed.
511              
512             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
513             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
514             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
515             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
516             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
517             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
518             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
519             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
520              
521              
522             =cut
523              
524             1; # End of Time::Duration::Concise