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 35     35   117 use strict;
  35         35  
  35         777  
3 35     35   105 use warnings;
  35         36  
  35         625  
4              
5 35     35   108 use Carp;
  35         37  
  35         2117  
6 35     35   114 use List::Util qw/first/;
  35         37  
  35         2659  
7 35     35   131 use Scalar::Util qw/blessed/;
  35         36  
  35         2647  
8              
9 35     35   122 use constant ALL_UNITS => qw/months days minutes seconds nanoseconds/;
  35         36  
  35         2773  
10              
11             use overload (
12 35         178 fallback => 1,
13             '+' => '_add_overload',
14             '-' => '_subtract_overload',
15             '*' => '_multiply_overload',
16             '<=>' => '_compare_overload',
17             'cmp' => '_compare_overload',
18 35     35   31427 );
  35         31881  
19              
20             sub isa {
21 62     62 0 59 my ($invocant, $a) = @_;
22 62 100       257 return !!1 if $a eq 'DateTime::Duration';
23 7         48 return $invocant->SUPER::isa($a);
24             }
25              
26             sub new {
27 96     96 0 4985 my $class = shift;
28 96 50 33     346 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
29              
30 96         79 my %params;
31 96         115 for my $key (qw/years months weeks days hours minutes seconds nanoseconds/) {
32 768 100       1090 $params{$key} = exists $args{$key} ? delete $args{$key} : 0;
33             }
34 96 50       149 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 96         354 } => $class;
46 96         126 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 114     114   94 my $self = shift;
53 114 100       554 return $self unless $self->{nanoseconds};
54              
55 31         53 my $seconds = $self->{seconds} + $self->{nanoseconds} / 1_000_000_000;
56 31         51 $self->{seconds} = int($seconds);
57 31         36 $self->{nanoseconds} = $self->{nanoseconds} % 1_000_000_000;
58 31 100       70 $self->{nanoseconds} -= 1_000_000_000 if $seconds < 0;
59              
60 31         82 return $self;
61             }
62              
63 47     47 0 39 sub clone { bless {%{$_[0]}} => ref $_[0] }
  47         171  
64              
65 4     4 0 888 sub years { abs(shift->in_units(qw/years/)) }
66 4     4 0 22 sub months { abs(shift->in_units(qw/months years/)) }
67 4     4 0 537 sub weeks { abs(shift->in_units(qw/weeks/)) }
68 4     4 0 396 sub days { abs(shift->in_units(qw/days weeks/)) }
69 10     10 0 580 sub hours { abs(shift->in_units(qw/hours/)) }
70 7     7 0 540 sub minutes { abs(shift->in_units(qw/minutes hours/) ) }
71 7     7 0 496 sub seconds { abs(shift->in_units(qw/seconds/)) }
72 4     4 0 403 sub nanoseconds { abs(shift->in_units(qw/nanoseconds seconds/)) }
73              
74 7 100   7 0 4266 sub is_positive { $_[0]->_has_positive && !$_[0]->_has_negative }
75 9 100   9 0 20 sub is_negative { !$_[0]->_has_positive && $_[0]->_has_negative }
76 60 100   60   105 sub _has_positive { (first { $_ > 0 } values %{$_[0]}) ? 1 : 0 }
  16     16   48  
  16         78  
77 43 100   43   76 sub _has_negative { (first { $_ < 0 } values %{$_[0]}) ? 1 : 0 }
  11     11   24  
  11         26  
78              
79             sub is_zero {
80 56     56 0 55 my $self = shift;
81 56 100   142   284 return 0 if first { $_ != 0 } values %$self;
  142         267  
82 3         14 return 1;
83             }
84              
85 72     72 0 99 sub deltas { %{$_[0]} }
  72         250  
86              
87 32     32 0 715 sub delta_months { shift->{months} }
88 36     36 0 128 sub delta_days { shift->{days} }
89 24     24 0 84 sub delta_minutes { shift->{minutes} }
90 34     34 0 119 sub delta_seconds { shift->{seconds} }
91 34     34 0 119 sub delta_nanoseconds { shift->{nanoseconds} }
92              
93             sub in_units {
94 64     64 0 84 my $self = shift;
95 64         109 my @units = @_;
96              
97 64         87 my %units = map { $_ => 1 } @units;
  95         215  
98              
99 64         63 my %ret;
100              
101 64         124 my ($months, $days, $minutes, $seconds) = @$self{qw/months days minutes seconds/};
102 64 100       129 if ($units{years}) {
103 12         31 $ret{years} = int($months / 12);
104 12         16 $months -= $ret{years} * 12;
105             }
106              
107 64 100       100 if ($units{months}) {
108 8         12 $ret{months} = $months;
109             }
110              
111 64 100       115 if ($units{weeks}) {
112 11         24 $ret{weeks} = int($days / 7);
113 11         19 $days -= $ret{weeks} * 7;
114             }
115              
116 64 100       93 if ($units{days}) {
117 8         12 $ret{days} = $days;
118             }
119              
120 64 100       101 if ($units{hours}) {
121 21         40 $ret{hours} = int($minutes / 60);
122 21         26 $minutes -= $ret{hours} * 60;
123             }
124              
125 64 100       89 if ($units{minutes}) {
126 11         19 $ret{minutes} = $minutes;
127             }
128              
129 64 100       99 if ($units{seconds}) {
130 16         21 $ret{seconds} = $seconds;
131 16         13 $seconds = 0;
132             }
133              
134 64 100       97 if ($units{nanoseconds}) {
135 8         15 $ret{nanoseconds} = $seconds * 1_000_000_000 + $self->{nanoseconds};
136             }
137              
138 64 100       341 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 54     54 0 121 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 3     3 0 4 my $self = shift;
149 3         7 my $clone = $self->clone;
150 3         11 $clone->{$_} = 0 for qw/minutes seconds nanoseconds/;
151 3         6 return $clone;
152             }
153              
154             sub clock_duration {
155 3     3 0 3 my $self = shift;
156 3         6 my $clone = $self->clone;
157 3         11 $clone->{$_} = 0 for qw/months days/;
158 3         8 return $clone;
159             }
160              
161             sub inverse {
162 30     30 0 27 my $self = shift;
163 30         49 my $clone = $self->clone;
164 30         125 $clone->{$_} *= -1 for keys %$clone;
165 30         70 return $clone;
166             }
167              
168             sub add_duration {
169 15     15 0 13 my ($lhs, $rhs) = @_;
170 15         67 $lhs->{$_} += $rhs->{$_} for ALL_UNITS;
171 15         20 return $lhs->_normalize_nanoseconds();
172             }
173              
174             sub add {
175 4     4 0 7 my $self = shift;
176 4         4 my $class = ref $self;
177              
178 4         4 my $lhs = $self;
179 4         7 my $rhs = $class->new(@_);
180 4         6 return $lhs->add_duration($rhs);
181             }
182              
183 10     10 0 13 sub subtract_duration { $_[0]->add_duration($_[1]->inverse) }
184              
185             sub subtract {
186 4     4 0 4 my $self = shift;
187 4         3 my $class = ref $self;
188              
189 4         4 my $lhs = $self;
190 4         6 my $rhs = $class->new(@_);
191 4         6 return $lhs->subtract_duration($rhs);
192             }
193              
194             sub multiply {
195 3     3 0 4 my ($lhs, $rhs) = @_;
196 3         14 $lhs->{$_} *= $rhs for ALL_UNITS;
197 3         4 return $lhs->_normalize_nanoseconds();
198             }
199              
200             sub compare {
201 5     5 0 440 my ($class, $lhs, $rhs, $base) = @_;
202 5   66     19 $base ||= DateTimeX::Moment->now;
203 5         10 return DateTimeX::Moment->compare(
204             $base->clone->add_duration($lhs),
205             $base->clone->add_duration($rhs)
206             );
207             }
208              
209 7 50   7   41 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       4 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   10 my ($lhs, $rhs, $flip) = @_;
226 6 50       12 ($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         10 return $lhs->clone->subtract_duration($rhs);
233             }
234              
235             sub _multiply_overload {
236 2     2   6 my ($lhs, $rhs) = @_;
237 2         5 return $lhs->clone->multiply($rhs);
238             }
239              
240             sub _compare_overload {
241 1     1   131 Carp::croak('DateTimeX::Moment::Duration does not overload comparison. See the documentation on the compare() method for details.');
242             }
243              
244             1;
245             __END__