File Coverage

blib/lib/Time/ETA.pm
Criterion Covered Total %
statement 144 147 97.9
branch 60 60 100.0
condition n/a
subroutine 26 27 96.3
pod 15 15 100.0
total 245 249 98.3


line stmt bran cond sub pod time code
1             package Time::ETA;
2             $Time::ETA::VERSION = '1.2.0';
3             # ABSTRACT: calculate estimated time of accomplishment
4              
5              
6 9     9   123163 use warnings;
  9         18  
  9         259  
7 9     9   42 use strict;
  9         19  
  9         181  
8              
9 9     9   40 use Carp;
  9         17  
  9         779  
10 9         42 use Time::HiRes qw(
11             gettimeofday
12             tv_interval
13 9     9   7402 );
  9         13275  
14              
15 9     9   7355 use YAML;
  9         86463  
  9         15231  
16              
17             my $true = 1;
18             my $false = '';
19              
20             our $SERIALIZATION_API_VERSION = 3;
21              
22              
23             sub new {
24 23     23 1 10009 my ($class, %params) = @_;
25 23         39 my $self = {};
26 23         41 bless $self, $class;
27              
28 23 100       284 croak "Expected to get parameter 'milestones'. Stopped" if not defined $params{milestones};
29 22 100       69 croak "Parameter 'milestones' should be positive integer. Stopped" if not $self->_is_positive_integer($params{milestones});
30              
31 7         37 $self->{_milestones} = $params{milestones};
32 7         15 $self->{_passed_milestones} = 0;
33 7         15 $self->{_elapsed} = 0;
34 7         42 $self->{_start} = [gettimeofday];
35 7         21 $self->{_is_paused} = $false;
36              
37 7         18 return $self;
38             }
39              
40              
41             sub get_elapsed_seconds {
42 60     60 1 13300 my ($self) = @_;
43              
44 60         67 my $elapsed_seconds;
45              
46 60 100       130 if ($self->is_completed()) {
47 16         54 $elapsed_seconds = tv_interval($self->{_start}, $self->{_end});
48             } else {
49 44         125 $elapsed_seconds = tv_interval($self->{_start}, [gettimeofday]);
50             }
51              
52 60         776 return $elapsed_seconds;
53             }
54              
55              
56             sub get_elapsed_time {
57 0     0 1 0 my ($self) = @_;
58              
59 0         0 my $time = $self->_get_time_from_seconds($self->get_elapsed_seconds());
60              
61 0         0 return $time;
62             }
63              
64              
65             sub get_remaining_seconds {
66 65     65 1 4862 my ($self) = @_;
67              
68 65 100       137 croak "There is not enough data to calculate estimated time of accomplishment. Stopped" if not $self->can_calculate_eta();
69              
70 61 100       129 return 0 if $self->is_completed();
71              
72 37         108 my $elapsed_before_milestone = tv_interval($self->{_start}, $self->{_milestone_pass});
73 37         377 my $elapsed_after_milestone = tv_interval($self->{_milestone_pass}, [gettimeofday()]);
74              
75 37         300 my $remaining_milestones = $self->{_milestones} - $self->{_passed_milestones};
76              
77 37         57 my $one_milestone_completion_time = $elapsed_before_milestone/$self->{_passed_milestones};
78 37         59 my $remaining_seconds = ($one_milestone_completion_time * $remaining_milestones) - $elapsed_after_milestone;
79              
80 37         82 return $remaining_seconds;
81             }
82              
83              
84             sub get_remaining_time {
85 8     8 1 15 my ($self) = @_;
86              
87 8         18 my $time = $self->_get_time_from_seconds($self->get_remaining_seconds());
88              
89 8         34 return $time;
90             }
91              
92              
93             sub get_completed_percent {
94 61     61 1 1004 my ($self) = @_;
95              
96 61         158 my $completed_percent = (100 * $self->{_passed_milestones}) / $self->{_milestones};
97              
98 61         268 return $completed_percent;
99             }
100              
101              
102             sub is_completed {
103 177     177 1 14697 my ($self) = @_;
104              
105             return ($self->{_passed_milestones} == $self->{_milestones})
106 177 100       814 ? $true
107             : $false
108             ;
109             }
110              
111              
112             sub pass_milestone {
113 21     21 1 3766 my ($self) = @_;
114              
115 21 100       64 if ($self->{_passed_milestones} < $self->{_milestones}) {
116 13         23 $self->{_passed_milestones}++;
117             } else {
118 8         869 croak "You have already completed all milestones. It it incorrect to run pass_milestone() now. Stopped";
119             }
120              
121 13         33 my $dt = [gettimeofday];
122              
123 13         29 $self->{_milestone_pass} = $dt;
124              
125 13 100       44 if ($self->{_passed_milestones} == $self->{_milestones}) {
126 2         4 $self->{_end} = $dt;
127             }
128              
129 13         33 return $false;
130             }
131              
132              
133             sub can_calculate_eta {
134 125     125 1 13558 my ($self) = @_;
135              
136 125 100       288 if ($self->{_passed_milestones} > 0) {
137 113         394 return $true;
138             } else {
139 12         514 return $false;
140             }
141             }
142              
143              
144             sub pause {
145 4     4 1 541 my ($self) = @_;
146              
147 4 100       11 croak "The object is already paused. Can't pause paused. Stopped" if $self->is_paused();
148              
149 3         14 my $elapsed_seconds = tv_interval($self->{_start}, [gettimeofday]);
150 3         38 $self->{_elapsed} += $elapsed_seconds;
151 3         8 $self->{_start} = undef;
152              
153 3         12 $self->{_is_paused} = $true;
154              
155 3         8 return $false;
156             }
157              
158              
159             sub is_paused {
160 39     39 1 6806 my ($self) = @_;
161              
162 39         413 return $self->{_is_paused};
163             }
164              
165              
166             sub resume {
167 3     3 1 8 my ($self, $string) = @_;
168              
169 3 100       11 croak "The object isn't paused. Can't resume. Stopped" if not $self->is_paused();
170              
171             # Setting the start time
172             # Start time is the current time minus time that has already pass
173 2         8 my $timeofday = [gettimeofday];
174 2         9 my $start = ($timeofday->[0] * 1_000_000 + $timeofday->[1]) - int($self->{_elapsed} * 1_000_000);
175 2         10 $self->{_start} = [int($start / 1_000_000), $start % 1_000_000];
176              
177 2         4 $self->{_elapsed} = 0;
178 2         4 $self->{_is_paused} = $false;
179              
180 2         5 return $false;
181             }
182              
183              
184             sub serialize {
185 37     37 1 1146 my ($self) = @_;
186              
187             my $data = {
188             _version => $SERIALIZATION_API_VERSION,
189             _milestones => $self->{_milestones},
190             _passed_milestones => $self->{_passed_milestones},
191             _start => $self->{_start},
192             _milestone_pass => $self->{_milestone_pass},
193             _end => $self->{_end},
194             _is_paused => $self->{_is_paused},
195             _elapsed => $self->{_elapsed},
196 37         293 };
197              
198 37         133 my $string = Dump($data);
199              
200 37         137229 return $string;
201             }
202              
203              
204             sub spawn {
205 58     58 1 4550 my ($class, $string) = @_;
206              
207 58 100       434 croak "Can't spawn Time::ETA object. No serialized data specified. Stopped" if not defined $string;
208              
209 56         56 my $data;
210              
211 56         84 eval {
212 56         149 $data = Load($string);
213             };
214              
215 56 100       262054 if ($@) {
216 2         192 croak "Can't spawn Time::ETA object. Got error from YAML parser:\n" . $@ . "Stopped";
217             }
218              
219 54 100       377 croak "Can't spawn Time::ETA object. Got incorrect serialized data. Stopped" if ref $data ne "HASH";
220              
221 52 100       358 croak "Can't spawn Time::ETA object. Serialized data does not contain version. Stopped" if not defined $data->{_version};
222              
223 50         120 my $v = _get_version();
224             croak "Can't spawn Time::ETA object. Version $v can work only with serialized data version $SERIALIZATION_API_VERSION. Stopped"
225 50 100       719 if $data->{_version} ne $SERIALIZATION_API_VERSION;
226              
227             croak "Can't spawn Time::ETA object. Serialized data contains incorrect number of milestones. Stopped"
228 46 100       131 if not _is_positive_integer(undef, $data->{_milestones});
229              
230             croak "Can't spawn Time::ETA object. Serialized data contains incorrect number of passed milestones. Stopped"
231 44 100       117 if not _is_positive_integer_or_zero(undef, $data->{_passed_milestones});
232              
233 42 100       120 if (not $data->{_is_paused}) {
234             _check_gettimeofday(
235             undef,
236             value => $data->{_start},
237 41         118 name => "start time"
238             );
239             }
240              
241 36 100       94 if (defined $data->{_end}) {
242             _check_gettimeofday(
243             undef,
244             value => $data->{_end},
245 8         61 name => "end time"
246             );
247             }
248              
249 36 100       121 if (defined $data->{_milestone_pass}) {
250             _check_gettimeofday(
251             undef,
252             value => $data->{_milestone_pass},
253 29         82 name => "last milestone pass time"
254             );
255             }
256              
257             my $self = {
258             _milestones => $data->{_milestones},
259             _passed_milestones => $data->{_passed_milestones},
260             _start => $data->{_start},
261             _milestone_pass => $data->{_milestone_pass},
262             _end => $data->{_end},
263             _is_paused => $data->{_is_paused},
264             _elapsed => $data->{_elapsed},
265 36         219 };
266              
267 36         78 bless $self, $class;
268              
269 36         209 return $self;
270             }
271              
272              
273             sub can_spawn {
274 13     13 1 5790 my ($class, $string) = @_;
275              
276 13         21 eval {
277 13         34 my $eta = spawn($class, $string);
278             };
279              
280 13 100       51 if (not $@) {
281 3         37 return $true;
282             } else {
283 10         25 return $false;
284             }
285             }
286              
287             sub _check_gettimeofday {
288 80     80   767 my ($self, %params) = @_;
289              
290 80 100       393 croak "Expected to get 'name'" unless defined $params{name};
291              
292             croak "Can't spawn Time::ETA object. Serialized data contains incorrect data for $params{name}. Stopped"
293 79 100       456 if ref $params{value} ne "ARRAY";
294              
295             croak "Can't spawn Time::ETA object. Serialized data contains incorrect seconds in $params{name}. Stopped"
296 77 100       164 if not _is_positive_integer_or_zero(undef, $params{value}->[0]);
297              
298             croak "Can't spawn Time::ETA object. Serialized data contains incorrect microseconds in $params{name}. Stopped"
299 75 100       153 if not _is_positive_integer_or_zero(undef, $params{value}->[1]);
300              
301 73         163 return $false;
302             }
303              
304             sub _is_positive_integer_or_zero {
305 272     272   2077 my ($self, $maybe_number) = @_;
306              
307 272 100       481 return $false if not defined $maybe_number;
308              
309             # http://www.perlmonks.org/?node_id=614452
310 271         670 my $check_result = $maybe_number =~ m{
311             \A # beginning of string
312             \+? # optional plus sign
313             [0-9]+ # mandatory digits, including zero
314             \z # end of string
315             }xms;
316              
317 271         2987 return $check_result;
318             }
319              
320             sub _is_positive_integer {
321 74     74   1973 my ($self, $maybe_number) = @_;
322              
323 74 100       182 return $false if not defined $maybe_number;
324              
325 73 100       285 return $false if $maybe_number eq '0';
326 71 100       253 return $false if $maybe_number eq '+0';
327              
328 70         170 return _is_positive_integer_or_zero(undef, $maybe_number);
329             }
330              
331             sub _get_time_from_seconds {
332 17     17   3961 my ($self, $input_sec) = @_;
333              
334 17         22 my $text;
335             {
336             # This is a quick solution. This like make code more robust.
337             # With this like the code will fail if $input_sec is not a number.
338 9     9   71 use warnings FATAL => 'all';
  9         17  
  9         1201  
  17         22  
339              
340 17         18 my $left_sec;
341 17         50 my $hour = int($input_sec/3600);
342 16         42 $left_sec = $input_sec - ($hour * 3600);
343              
344 16         21 my $min = int($left_sec/60);
345 16         18 $left_sec = $left_sec - ($min * 60);
346              
347 16         86 $text =
348             sprintf("%01d", $hour) . ":"
349             . sprintf("%02d", $min) . ":"
350             . sprintf("%02d", $left_sec)
351             ;
352             }
353              
354 16         81 return $text;
355             }
356              
357              
358             sub _get_version {
359 9     9   47 no warnings 'uninitialized';
  9         18  
  9         489  
360 51     51   181 my $v = "$Time::ETA::VERSION";
361 51         111 return $v;
362             }
363              
364              
365             1;
366              
367             __END__