File Coverage

blib/lib/Time/Duration.pm
Criterion Covered Total %
statement 113 113 100.0
branch 36 36 100.0
condition 6 6 100.0
subroutine 24 24 100.0
pod 11 13 84.6
total 190 192 98.9


line stmt bran cond sub pod time code
1             package Time::Duration;
2             $Time::Duration::VERSION = '1.21';
3 2     2   11581 use 5.006;
  2         13  
4 2     2   11 use strict;
  2         4  
  2         39  
5 2     2   10 use warnings;
  2         3  
  2         75  
6 2     2   11 use constant DEBUG => 0;
  2         10  
  2         1737  
7              
8             require Exporter;
9              
10             our @ISA = ('Exporter');
11             our @EXPORT = qw( later later_exact earlier earlier_exact
12             ago ago_exact from_now from_now_exact
13             duration duration_exact
14             concise
15             );
16             our @EXPORT_OK = ('interval', @EXPORT);
17             our $MILLISECOND = 0;
18              
19             # ALL SUBS ARE PURE FUNCTIONS
20              
21             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
22              
23             sub concise ($) {
24 31     31 1 49 my $string = $_[0];
25 31         39 DEBUG and print "in : $string\n";
26 31         76 $string =~ tr/,//d;
27 31         145 $string =~ s/\band\b//;
28 31         183 $string =~ s/\b(year|day|hour|minute|second)s?\b/substr($1,0,1)/eg;
  46         186  
29 31         77 $string =~ s/\b(millisecond)s?\b/ms/g;
30 31         169 $string =~ s/\s*(\d+)\s*/$1/g;
31 31         117 return $string;
32             }
33              
34 74     74 1 5311 sub later { interval( $_[0], $_[1], ' earlier', ' later', 'right then'); }
35 33     33 1 2575 sub later_exact { interval_exact($_[0], $_[1], ' earlier', ' later', 'right then'); }
36 8     8 1 451 sub earlier { interval( $_[0], $_[1], ' later', ' earlier', 'right then'); }
37 27     27 1 2036 sub earlier_exact { interval_exact($_[0], $_[1], ' later', ' earlier', 'right then'); }
38 8     8 1 437 sub ago { interval( $_[0], $_[1], ' from now', ' ago', 'right now'); }
39 27     27 1 1963 sub ago_exact { interval_exact($_[0], $_[1], ' from now', ' ago', 'right now'); }
40 8     8 1 426 sub from_now { interval( $_[0], $_[1], ' ago', ' from now', 'right now'); }
41 27     27 1 2130 sub from_now_exact { interval_exact($_[0], $_[1], ' ago', ' from now', 'right now'); }
42              
43             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
44             sub duration_exact {
45 4     4 1 235 my $span = $_[0]; # interval in seconds
46 4   100     24 my $precision = int($_[1] || 0) || 2; # precision (default: 2)
47 4 100       23 return '0 seconds' unless $span;
48 2         9 _render('',
49             _separate(abs $span));
50             }
51              
52             sub duration {
53 21     21 1 1420 my $span = $_[0]; # interval in seconds
54 21   100     109 my $precision = int($_[1] || 0) || 2; # precision (default: 2)
55 21 100       49 return '0 seconds' unless $span;
56 18         46 _render('',
57             _approximate($precision,
58             _separate(abs $span)));
59             }
60              
61             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
62              
63             sub interval_exact {
64 119     119 0 174 my $span = $_[0]; # interval, in seconds
65             # precision is ignored
66 119 100       308 my $direction = ($span < 0) ? $_[2] # what a neg number gets
    100          
67             : ($span > 0) ? $_[3] # what a pos number gets
68             : return $_[4]; # what zero gets
69 114         224 _render($direction,
70             _separate($span));
71             }
72              
73             sub interval {
74 103     103 0 157 my $span = $_[0]; # interval, in seconds
75 103   100     422 my $precision = int($_[1] || 0) || 2; # precision (default: 2)
76 103 100       264 my $direction = ($span < 0) ? $_[2] # what a neg number gets
    100          
77             : ($span > 0) ? $_[3] # what a pos number gets
78             : return $_[4]; # what zero gets
79 92         173 _render($direction,
80             _approximate($precision,
81             _separate($span)));
82             }
83              
84             #~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#
85             #
86             # The actual figuring is below here
87              
88 2     2   17 use constant MINUTE => 60;
  2         4  
  2         138  
89 2     2   12 use constant HOUR => 3600;
  2         4  
  2         123  
90 2     2   12 use constant DAY => 24 * HOUR;
  2         3  
  2         105  
91 2     2   12 use constant YEAR => 365 * DAY;
  2         8  
  2         1573  
92              
93             sub _separate {
94             # Breakdown of seconds into units, starting with the most significant
95            
96 226     226   328 my $remainder = abs $_[0]; # remainder
97 226         317 my $this; # scratch
98             my @wheel; # retval
99            
100             # Years:
101 226         456 $this = int($remainder / (365 * 24 * 60 * 60));
102 226         499 push @wheel, ['year', $this, 1_000_000_000];
103 226         402 $remainder -= $this * (365 * 24 * 60 * 60);
104            
105             # Days:
106 226         309 $this = int($remainder / (24 * 60 * 60));
107 226         372 push @wheel, ['day', $this, 365];
108 226         327 $remainder -= $this * (24 * 60 * 60);
109            
110             # Hours:
111 226         323 $this = int($remainder / (60 * 60));
112 226         375 push @wheel, ['hour', $this, 24];
113 226         321 $remainder -= $this * (60 * 60);
114            
115             # Minutes:
116 226         324 $this = int($remainder / 60);
117 226         351 push @wheel, ['minute', $this, 60];
118 226         314 $remainder -= $this * 60;
119            
120 226         351 push @wheel, ['second', int($remainder), 60];
121              
122             # Thanks to Steven Haryanto (http://search.cpan.org/~sharyanto/) for the basis of this change.
123 226 100       462 if ($MILLISECOND) {
124 18         26 $remainder -= int($remainder);
125 18         118 push @wheel, ['millisecond', sprintf("%0.f", $remainder * 1000), 1000];
126             }
127              
128 226         578 return @wheel;
129             }
130              
131             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
132             sub _approximate {
133             # Now nudge the wheels into an acceptably (im)precise configuration
134 110     110   225 my($precision, @wheel) = @_;
135              
136             Fix:
137             {
138             # Constraints for leaving this block:
139             # 1) number of nonzero wheels must be <= $precision
140             # 2) no wheels can be improperly expressed (like having "60" for mins)
141            
142 110         144 my $nonzero_count = 0;
  175         228  
143 175         230 my $improperly_expressed;
144              
145 175         198 DEBUG and print join ' ', '#', (map "${$_}[1] ${$_}[0]", @wheel), "\n";
146 175         339 for(my $i = 0; $i < @wheel; $i++) {
147 864         1189 my $this = $wheel[$i];
148 864 100       1753 next if $this->[1] == 0; # Zeros require no attention.
149 386         459 ++$nonzero_count;
150 386 100       648 next if $i == 0; # the years wheel is never improper or over any limit; skip
151            
152 324 100       820 if($nonzero_count > $precision) {
    100          
153             # This is one nonzero wheel too many!
154 45         59 DEBUG and print '', $this->[0], " is one nonzero too many!\n";
155              
156             # Incr previous wheel if we're big enough:
157 45 100       120 if($this->[1] >= ($this->[-1] / 2)) {
158 26         33 DEBUG and printf "incrementing %s from %s to %s\n",
159             $wheel[$i-1][0], $wheel[$i-1][1], 1 + $wheel[$i-1][1], ;
160 26         40 ++$wheel[$i-1][1];
161             }
162              
163             # Reset this and subsequent wheels to 0:
164 45         101 for(my $j = $i; $j < @wheel; $j++) { $wheel[$j][1] = 0 }
  76         152  
165 45         84 redo Fix; # Start over.
166             } elsif($this->[1] >= $this->[-1]) {
167             # It's an improperly expressed wheel. (Like "60" on the mins wheel)
168 20         29 $improperly_expressed = $i;
169 20         426 DEBUG and print '', $this->[0], ' (', $this->[1],
170             ") is improper!\n";
171             }
172             }
173            
174 130 100       248 if(defined $improperly_expressed) {
175             # Only fix the least-significant improperly expressed wheel (at a time).
176 20         27 DEBUG and printf "incrementing %s from %s to %s\n",
177             $wheel[$improperly_expressed-1][0], $wheel[$improperly_expressed-1][1],
178             1 + $wheel[$improperly_expressed-1][1], ;
179 20         25 ++$wheel[ $improperly_expressed - 1][1];
180 20         27 $wheel[ $improperly_expressed][1] = 0;
181             # We never have a "150" in the minutes slot -- if it's improper,
182             # it's only by having been rounded up to the limit.
183 20         38 redo Fix; # Start over.
184             }
185            
186             # Otherwise there's not too many nonzero wheels, and there's no
187             # improperly expressed wheels, so fall thru...
188             }
189              
190 110         260 return @wheel;
191             }
192              
193             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
194             sub _render {
195             # Make it into English
196              
197 226     226   379 my $direction = shift @_;
198             my @wheel = map
199 226         395 {;
200 1148 100       1996 ( $_->[1] == 0) ? () # zero wheels
    100          
201 198         367 : ($_->[1] == 1) ? "${$_}[1] ${$_}[0]" # singular
  198         413  
202 321         584 : "${$_}[1] ${$_}[0]s" # plural
  321         746  
203             }
204             @_
205             ;
206 226 100       477 return "just now" unless @wheel; # sanity
207 225         416 $wheel[-1] .= $direction;
208 225 100       622 return $wheel[0] if @wheel == 1;
209 160 100       534 return "$wheel[0] and $wheel[1]" if @wheel == 2;
210 89         160 $wheel[-1] = "and $wheel[-1]";
211 89         425 return join q{, }, @wheel;
212             }
213              
214             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
215             1;
216              
217             __END__