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   234 use strict;
  35         65  
  35         850  
3 35     35   147 use warnings;
  35         59  
  35         693  
4              
5 35     35   137 use Carp;
  35         62  
  35         1667  
6 35     35   164 use List::Util qw/first/;
  35         61  
  35         1667  
7 35     35   166 use Scalar::Util qw/blessed/;
  35         62  
  35         1392  
8              
9 35     35   163 use constant ALL_UNITS => qw/months days minutes seconds nanoseconds/;
  35         57  
  35         3109  
10              
11             use overload (
12 35         245 fallback => 1,
13             '+' => '_add_overload',
14             '-' => '_subtract_overload',
15             '*' => '_multiply_overload',
16             '<=>' => '_compare_overload',
17             'cmp' => '_compare_overload',
18 35     35   210 );
  35         59  
19              
20             sub isa {
21 62     62 0 125 my ($invocant, $a) = @_;
22 62 100       288 return !!1 if $a eq 'DateTime::Duration';
23 7         66 return $invocant->SUPER::isa($a);
24             }
25              
26             sub new {
27 96     96 0 8181 my $class = shift;
28 96 50 33     414 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
29              
30 96         145 my %params;
31 96         170 for my $key (qw/years months weeks days hours minutes seconds nanoseconds/) {
32 768 100       1460 $params{$key} = exists $args{$key} ? delete $args{$key} : 0;
33             }
34 96 50       185 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         429 } => $class;
46 96         196 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   161 my $self = shift;
53 114 100       580 return $self unless $self->{nanoseconds};
54              
55 31         85 my $seconds = $self->{seconds} + $self->{nanoseconds} / 1_000_000_000;
56 31         49 $self->{seconds} = int($seconds);
57 31         54 $self->{nanoseconds} = $self->{nanoseconds} % 1_000_000_000;
58 31 100       60 $self->{nanoseconds} -= 1_000_000_000 if $seconds < 0;
59              
60 31         102 return $self;
61             }
62              
63 47     47 0 59 sub clone { bless {%{$_[0]}} => ref $_[0] }
  47         209  
64              
65 4     4 0 559 sub years { abs(shift->in_units(qw/years/)) }
66 4     4 0 669 sub months { abs(shift->in_units(qw/months years/)) }
67 4     4 0 540 sub weeks { abs(shift->in_units(qw/weeks/)) }
68 4     4 0 548 sub days { abs(shift->in_units(qw/days weeks/)) }
69 10     10 0 581 sub hours { abs(shift->in_units(qw/hours/)) }
70 7     7 0 25 sub minutes { abs(shift->in_units(qw/minutes hours/) ) }
71 7     7 0 602 sub seconds { abs(shift->in_units(qw/seconds/)) }
72 4     4 0 607 sub nanoseconds { abs(shift->in_units(qw/nanoseconds seconds/)) }
73              
74 7 100   7 0 4247 sub is_positive { $_[0]->_has_positive && !$_[0]->_has_negative }
75 9 100   9 0 25 sub is_negative { !$_[0]->_has_positive && $_[0]->_has_negative }
76 66 100   66   152 sub _has_positive { (first { $_ > 0 } values %{$_[0]}) ? 1 : 0 }
  16     16   70  
  16         68  
77 38 100   38   113 sub _has_negative { (first { $_ < 0 } values %{$_[0]}) ? 1 : 0 }
  11     11   42  
  11         28  
78              
79             sub is_zero {
80 56     56 0 91 my $self = shift;
81 56 100   125   297 return 0 if first { $_ != 0 } values %$self;
  125         266  
82 3         30 return 1;
83             }
84              
85 72     72 0 151 sub deltas { %{$_[0]} }
  72         270  
86              
87 32     32 0 700 sub delta_months { shift->{months} }
88 36     36 0 164 sub delta_days { shift->{days} }
89 24     24 0 102 sub delta_minutes { shift->{minutes} }
90 34     34 0 158 sub delta_seconds { shift->{seconds} }
91 34     34 0 140 sub delta_nanoseconds { shift->{nanoseconds} }
92              
93             sub in_units {
94 64     64 0 111 my $self = shift;
95 64         119 my @units = @_;
96              
97 64         108 my %units = map { $_ => 1 } @units;
  95         234  
98              
99 64         94 my %ret;
100              
101 64         142 my ($months, $days, $minutes, $seconds) = @$self{qw/months days minutes seconds/};
102 64 100       135 if ($units{years}) {
103 12         34 $ret{years} = int($months / 12);
104 12         21 $months -= $ret{years} * 12;
105             }
106              
107 64 100       112 if ($units{months}) {
108 8         11 $ret{months} = $months;
109             }
110              
111 64 100       115 if ($units{weeks}) {
112 11         27 $ret{weeks} = int($days / 7);
113 11         19 $days -= $ret{weeks} * 7;
114             }
115              
116 64 100       113 if ($units{days}) {
117 8         14 $ret{days} = $days;
118             }
119              
120 64 100       101 if ($units{hours}) {
121 21         59 $ret{hours} = int($minutes / 60);
122 21         34 $minutes -= $ret{hours} * 60;
123             }
124              
125 64 100       105 if ($units{minutes}) {
126 11         47 $ret{minutes} = $minutes;
127             }
128              
129 64 100       107 if ($units{seconds}) {
130 16         30 $ret{seconds} = $seconds;
131 16         22 $seconds = 0;
132             }
133              
134 64 100       118 if ($units{nanoseconds}) {
135 8         17 $ret{nanoseconds} = $seconds * 1_000_000_000 + $self->{nanoseconds};
136             }
137              
138 64 100       327 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 137 sub is_limit_mode { 1 }
144 0     0 0 0 sub is_preserve_mode { 0 }
145 2     2 0 10 sub end_of_month_mode { 'limit' }
146              
147             sub calendar_duration {
148 3     3 0 10 my $self = shift;
149 3         7 my $clone = $self->clone;
150 3         12 $clone->{$_} = 0 for qw/minutes seconds nanoseconds/;
151 3         9 return $clone;
152             }
153              
154             sub clock_duration {
155 3     3 0 7 my $self = shift;
156 3         8 my $clone = $self->clone;
157 3         11 $clone->{$_} = 0 for qw/months days/;
158 3         10 return $clone;
159             }
160              
161             sub inverse {
162 30     30 0 48 my $self = shift;
163 30         67 my $clone = $self->clone;
164 30         122 $clone->{$_} *= -1 for keys %$clone;
165 30         84 return $clone;
166             }
167              
168             sub add_duration {
169 15     15 0 37 my ($lhs, $rhs) = @_;
170 15         80 $lhs->{$_} += $rhs->{$_} for ALL_UNITS;
171 15         28 return $lhs->_normalize_nanoseconds();
172             }
173              
174             sub add {
175 4     4 0 8 my $self = shift;
176 4         9 my $class = ref $self;
177              
178 4         6 my $lhs = $self;
179 4         9 my $rhs = $class->new(@_);
180 4         10 return $lhs->add_duration($rhs);
181             }
182              
183 10     10 0 22 sub subtract_duration { $_[0]->add_duration($_[1]->inverse) }
184              
185             sub subtract {
186 4     4 0 9 my $self = shift;
187 4         7 my $class = ref $self;
188              
189 4         7 my $lhs = $self;
190 4         10 my $rhs = $class->new(@_);
191 4         10 return $lhs->subtract_duration($rhs);
192             }
193              
194             sub multiply {
195 3     3 0 6 my ($lhs, $rhs) = @_;
196 3         16 $lhs->{$_} *= $rhs for ALL_UNITS;
197 3         7 return $lhs->_normalize_nanoseconds();
198             }
199              
200             sub compare {
201 5     5 0 699 my ($class, $lhs, $rhs, $base) = @_;
202 5   66     22 $base ||= DateTimeX::Moment->now;
203 5         15 return DateTimeX::Moment->compare(
204             $base->clone->add_duration($lhs),
205             $base->clone->add_duration($rhs)
206             );
207             }
208              
209 7 50   7   45 sub _isa_datetime { blessed $_[0] && $_[0]->isa('DateTime') }
210              
211             sub _add_overload {
212 1     1   6 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         4 return $lhs->clone->add_duration($rhs);
222             }
223              
224             sub _subtract_overload {
225 6     6   18 my ($lhs, $rhs, $flip) = @_;
226 6 50       13 ($lhs, $rhs) = ($rhs, $lhs) if $flip;
227              
228 6 50       12 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         15 return $lhs->clone->subtract_duration($rhs);
233             }
234              
235             sub _multiply_overload {
236 2     2   9 my ($lhs, $rhs) = @_;
237 2         5 return $lhs->clone->multiply($rhs);
238             }
239              
240             sub _compare_overload {
241 1     1   159 Carp::croak('DateTimeX::Moment::Duration does not overload comparison. See the documentation on the compare() method for details.');
242             }
243              
244             1;
245             __END__
246              
247             =pod
248              
249             =encoding utf-8
250              
251             =head1 NAME
252              
253             DateTimeX::Moment::Duration - TODO
254              
255             =head1 SYNOPSIS
256              
257             use DateTimeX::Moment::Duration;
258              
259             =head1 DESCRIPTION
260              
261             TODO
262              
263             =head1 SEE ALSO
264              
265             L<DateTime::Duration>
266              
267             =head1 LICENSE
268              
269             Copyright (C) karupanerura.
270              
271             This library is free software; you can redistribute it and/or modify
272             it under the same terms as Perl itself.
273              
274             =head1 AUTHOR
275              
276             karupanerura E<lt>karupa@cpan.orgE<gt>
277              
278             =cut