File Coverage

blib/lib/DateTimeX/Moment/Duration.pm
Criterion Covered Total %
statement 141 149 94.6
branch 43 50 86.0
condition 3 6 50.0
subroutine 50 52 96.1
pod 0 34 0.0
total 237 291 81.4


line stmt bran cond sub pod time code
1             package DateTimeX::Moment::Duration;
2 33     33   105 use strict;
  33         38  
  33         746  
3 33     33   95 use warnings;
  33         32  
  33         575  
4              
5 33     33   99 use Carp;
  33         32  
  33         1939  
6 33     33   109 use List::Util qw/first/;
  33         39  
  33         2647  
7 33     33   130 use Scalar::Util qw/blessed/;
  33         39  
  33         2599  
8              
9 33     33   119 use constant ALL_UNITS => qw/months days minutes seconds nanoseconds/;
  33         38  
  33         2700  
10              
11             use overload (
12 33         159 fallback => 1,
13             '+' => '_add_overload',
14             '-' => '_subtract_overload',
15             '*' => '_multiply_overload',
16             '<=>' => '_compare_overload',
17             'cmp' => '_compare_overload',
18 33     33   29054 );
  33         29579  
19              
20             sub isa {
21 32     32 0 31 my ($invocant, $a) = @_;
22 32 100       131 return !!1 if $a eq 'DateTime::Duration';
23 7         59 return $invocant->SUPER::isa($a);
24             }
25              
26             sub new {
27 69     69 0 3600 my $class = shift;
28 69 50 33     243 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
29              
30 69         55 my %params;
31 69         87 for my $key (qw/years months weeks days hours minutes seconds nanoseconds/) {
32 552 100       751 $params{$key} = exists $args{$key} ? delete $args{$key} : 0;
33             }
34 69 50       102 if (%args) {
35 0         0 my $msg = 'Invalid args: '.join ',', keys %args;
36 0         0 Carp::croak $msg;
37             }
38              
39             my $self = bless {
40             months => $params{months} + $params{years} * 12,
41             days => $params{days} + $params{weeks} * 7,
42             minutes => $params{minutes} + $params{hours} * 60,
43             seconds => $params{seconds},
44             nanoseconds => $params{nanoseconds},
45 69         247 } => $class;
46 69         102 return $self->_normalize_nanoseconds();
47             }
48              
49             # make the signs of seconds, nanos the same; 0 < abs(nanos) < MAX_NANOS
50             # NB this requires nanoseconds != 0 (callers check this already)
51             sub _normalize_nanoseconds {
52 87     87   62 my $self = shift;
53 87 100       373 return $self unless $self->{nanoseconds};
54              
55 31         46 my $seconds = $self->{seconds} + $self->{nanoseconds} / 1_000_000_000;
56 31         43 $self->{seconds} = int($seconds);
57 31         31 $self->{nanoseconds} = $self->{nanoseconds} % 1_000_000_000;
58 31 100       49 $self->{nanoseconds} -= 1_000_000_000 if $seconds < 0;
59              
60 31         81 return $self;
61             }
62              
63 28     28 0 23 sub clone { bless {%{$_[0]}} => ref $_[0] }
  28         104  
64              
65 4     4 0 250 sub years { abs(shift->in_units(qw/years/)) }
66 4     4 0 15 sub months { abs(shift->in_units(qw/months years/)) }
67 4     4 0 249 sub weeks { abs(shift->in_units(qw/weeks/)) }
68 4     4 0 260 sub days { abs(shift->in_units(qw/days weeks/)) }
69 10     10 0 285 sub hours { abs(shift->in_units(qw/hours/)) }
70 7     7 0 401 sub minutes { abs(shift->in_units(qw/minutes hours/) ) }
71 7     7 0 254 sub seconds { abs(shift->in_units(qw/seconds/)) }
72 4     4 0 248 sub nanoseconds { abs(shift->in_units(qw/nanoseconds seconds/)) }
73              
74 7 100   7 0 1821 sub is_positive { $_[0]->_has_positive && !$_[0]->_has_negative }
75 9 100   9 0 16 sub is_negative { !$_[0]->_has_positive && $_[0]->_has_negative }
76 59 100   59   101 sub _has_positive { (first { $_ > 0 } values %{$_[0]}) ? 1 : 0 }
  16     16   42  
  16         70  
77 36 100   36   70 sub _has_negative { (first { $_ < 0 } values %{$_[0]}) ? 1 : 0 }
  11     11   21  
  11         23  
78              
79             sub is_zero {
80 24     24 0 22 my $self = shift;
81 24 100   71   138 return 0 if first { $_ != 0 } values %$self;
  71         112  
82 1         5 return 1;
83             }
84              
85 22     22 0 18 sub deltas { %{$_[0]} }
  22         71  
86              
87 32     32 0 401 sub delta_months { shift->{months} }
88 36     36 0 131 sub delta_days { shift->{days} }
89 21     21 0 63 sub delta_minutes { shift->{minutes} }
90 34     34 0 113 sub delta_seconds { shift->{seconds} }
91 34     34 0 99 sub delta_nanoseconds { shift->{nanoseconds} }
92              
93             sub in_units {
94 63     63 0 65 my $self = shift;
95 63         94 my @units = @_;
96              
97 63         80 my %units = map { $_ => 1 } @units;
  93         188  
98              
99 63         54 my %ret;
100              
101 63         90 my ($months, $days, $minutes, $seconds) = @$self{qw/months days minutes seconds/};
102 63 100       107 if ($units{years}) {
103 12         20 $ret{years} = int($months / 12);
104 12         16 $months -= $ret{years} * 12;
105             }
106              
107 63 100       77 if ($units{months}) {
108 8         10 $ret{months} = $months;
109             }
110              
111 63 100       121 if ($units{weeks}) {
112 11         22 $ret{weeks} = int($days / 7);
113 11         14 $days -= $ret{weeks} * 7;
114             }
115              
116 63 100       78 if ($units{days}) {
117 8         10 $ret{days} = $days;
118             }
119              
120 63 100       80 if ($units{hours}) {
121 21         31 $ret{hours} = int($minutes / 60);
122 21         28 $minutes -= $ret{hours} * 60;
123             }
124              
125 63 100       86 if ($units{minutes}) {
126 10         13 $ret{minutes} = $minutes;
127             }
128              
129 63 100       77 if ($units{seconds}) {
130 15         15 $ret{seconds} = $seconds;
131 15         13 $seconds = 0;
132             }
133              
134 63 100       74 if ($units{nanoseconds}) {
135 8         13 $ret{nanoseconds} = $seconds * 1_000_000_000 + $self->{nanoseconds};
136             }
137              
138 63 100       275 return wantarray ? @ret{@units} : $ret{$units[0]};
139             }
140              
141             # XXX: limit mode only
142 0     0 0 0 sub is_wrap_mode { 0 }
143 24     24 0 55 sub is_limit_mode { 1 }
144 0     0 0 0 sub is_preserve_mode { 0 }
145 2     2 0 8 sub end_of_month_mode { 'limit' }
146              
147             sub calendar_duration {
148 1     1 0 5 my $self = shift;
149 1         2 my $clone = $self->clone;
150 1         4 $clone->{$_} = 0 for qw/minutes seconds nanoseconds/;
151 1         2 return $clone;
152             }
153              
154             sub clock_duration {
155 1     1 0 2 my $self = shift;
156 1         2 my $clone = $self->clone;
157 1         4 $clone->{$_} = 0 for qw/months days/;
158 1         2 return $clone;
159             }
160              
161             sub inverse {
162 15     15 0 16 my $self = shift;
163 15         19 my $clone = $self->clone;
164 15         64 $clone->{$_} *= -1 for keys %$clone;
165 15         27 return $clone;
166             }
167              
168             sub add_duration {
169 15     15 0 14 my ($lhs, $rhs) = @_;
170 15         64 $lhs->{$_} += $rhs->{$_} for ALL_UNITS;
171 15         20 return $lhs->_normalize_nanoseconds();
172             }
173              
174             sub add {
175 4     4 0 6 my $self = shift;
176 4         4 my $class = ref $self;
177              
178 4         4 my $lhs = $self;
179 4         5 my $rhs = $class->new(@_);
180 4         6 return $lhs->add_duration($rhs);
181             }
182              
183 10     10 0 15 sub subtract_duration { $_[0]->add_duration($_[1]->inverse) }
184              
185             sub subtract {
186 4     4 0 5 my $self = shift;
187 4         4 my $class = ref $self;
188              
189 4         4 my $lhs = $self;
190 4         5 my $rhs = $class->new(@_);
191 4         6 return $lhs->subtract_duration($rhs);
192             }
193              
194             sub multiply {
195 3     3 0 2 my ($lhs, $rhs) = @_;
196 3         14 $lhs->{$_} *= $rhs for ALL_UNITS;
197 3         5 return $lhs->_normalize_nanoseconds();
198             }
199              
200             sub compare {
201 5     5 0 350 my ($class, $lhs, $rhs, $base) = @_;
202 5   66     16 $base ||= DateTimeX::Moment->now;
203 5         12 return DateTimeX::Moment->compare(
204             $base->clone->add_duration($lhs),
205             $base->clone->add_duration($rhs)
206             );
207             }
208              
209 7 50   7   38 sub _isa_datetime { blessed $_[0] && $_[0]->isa('DateTime') }
210              
211             sub _add_overload {
212 1     1   4 my ($lhs, $rhs, $flip) = @_;
213 1 50       3 ($lhs, $rhs) = ($rhs, $lhs) if $flip;
214              
215 1 50       2 if (_isa_datetime($rhs)) {
216 0         0 $rhs->add_duration($lhs);
217 0         0 return;
218             }
219              
220             # will also work if $lhs is a DateTime.pm object
221 1         3 return $lhs->clone->add_duration($rhs);
222             }
223              
224             sub _subtract_overload {
225 6     6   12 my ($lhs, $rhs, $flip) = @_;
226 6 50       9 ($lhs, $rhs) = ($rhs, $lhs) if $flip;
227              
228 6 50       9 if (_isa_datetime($rhs)) {
229 0         0 Carp::croak('Cannot subtract a DateTimeX::Moment object from a DateTimeX::Moment::Duration object');
230             }
231              
232 6         11 return $lhs->clone->subtract_duration($rhs);
233             }
234              
235             sub _multiply_overload {
236 2     2   6 my ($lhs, $rhs) = @_;
237 2         3 return $lhs->clone->multiply($rhs);
238             }
239              
240             sub _compare_overload {
241 1     1   125 Carp::croak('DateTimeX::Moment::Duration does not overload comparison. See the documentation on the compare() method for details.');
242             }
243              
244             1;
245             __END__