File Coverage

blib/lib/PDL/DateTime.pm
Criterion Covered Total %
statement 338 461 73.3
branch 146 286 51.0
condition 11 63 17.4
subroutine 59 78 75.6
pod 40 44 90.9
total 594 932 63.7


line stmt bran cond sub pod time code
1             package PDL::DateTime;
2              
3 5     5   665029 use strict;
  5         6  
  5         110  
4 5     5   16 use warnings;
  5         6  
  5         105  
5 5     5   421 use parent 'PDL';
  5         231  
  5         18  
6              
7             our $VERSION = '0.003';
8              
9 5     5   829 use Scalar::Util 'looks_like_number';
  5         6  
  5         209  
10 5     5   1975 use POSIX ();
  5         20872  
  5         101  
11 5     5   416 use PDL::Types;
  5         1707  
  5         492  
12 5     5   714 use PDL::Primitive;
  5         87422  
  5         25  
13 5     5   1567 use PDL::Basic qw(sequence);
  5         2047  
  5         22  
14 5     5   369 use PDL::Math qw(floor);
  5         5  
  5         30  
15 5     5   328 use PDL::Core qw(longlong long double byte short indx);
  5         6  
  5         22  
16 5     5   2146 use Time::Moment;
  5         4779  
  5         126  
17 5     5   22 use Carp;
  5         4  
  5         384  
18              
19 5         50 use overload '>' => \&_num_compare_gt,
20             '<' => \&_num_compare_lt,
21             '>=' => \&_num_compare_ge,
22             '<=' => \&_num_compare_le,
23             '==' => \&_num_compare_eq,
24             '!=' => \&_num_compare_ne,
25 5     5   17 '""' => \&_stringify;
  5         5  
26              
27             my %INC_SECONDS = (
28             week => 60 * 60 * 24 * 7,
29             day => 60 * 60 * 24,
30             hour => 60 * 60,
31             minute => 60,
32             second => 1,
33             );
34              
35             sub initialize {
36 1653     1653 0 63617 my ($class, %args) = @_;
37 1653 100       1799 $class = ref $class ? ref $class : $class;
38 1653         2989 return bless { %args, PDL => PDL->null }, $class;
39             }
40              
41             sub new {
42 10     10 1 4906 my ($class, $data, %opts) = @_;
43              
44             # for 'PDL::DateTime' just make a copy
45 10 100       30 return $data->copy(%opts) if ref $data eq 'PDL::DateTime';
46              
47 9         21 my $self = $class->initialize(%opts);
48             # $data is expected to contain epoch timestamps in microseconds
49 9 100       135 if (ref $data eq 'ARRAY') {
    50          
50 1         4 $self->{PDL} = longlong($data);
51             }
52             elsif (ref $data eq 'PDL') {
53 8 50       21 if ($data->type == longlong) {
    0          
54 8         178 $self->{PDL} = $data->copy;
55             # NOTE:
56             # $x = sequence(longlong, 6) # type LL
57             # $u = long($x) # == clone/copy of $x (type L)
58             # $u = longlong($x) # == same data, same type as $x
59             # $w = PDL->new($x) # == clone/copy of $x (type LL)
60             }
61             elsif ($data->type == double) {
62 0         0 $self->{PDL} = longlong(floor($data + 0.5));
63 0         0 $self->{PDL} -= $self->{PDL} % 1000; #truncate to milliseconds
64             }
65             else {
66 0         0 $self->{PDL} = longlong($data);
67             }
68             }
69             else {
70 0 0       0 if (looks_like_number $data) {
    0          
71 0         0 $self->{PDL} = longlong($data);
72             }
73             elsif ($data) {
74 0         0 $self->{PDL} = longlong(_datetime_to_jumboepoch($data));
75             }
76             else {
77 0         0 croak "PDL::DateTime->new: invalid data";
78             }
79             }
80              
81 9         363 return $self;
82             }
83              
84             # Derived objects need to supply its own copy!
85             sub copy {
86 102     102 0 2244 my ($self, %opts) = @_;
87 102         189 my $new = $self->initialize(%opts);
88             # copy the PDL
89 102         873 $new->{PDL} = $self->{PDL}->SUPER::copy;
90             # copy the other stuff
91             #$new->{someThingElse} = $self->{someThingElse};
92 102         2235 return $new;
93             }
94              
95             sub new_from_epoch {
96 3     3 1 36 my ($class, $ep, %opts) = @_;
97 3         7 my $self = $class->initialize(%opts);
98 3 100       27 $ep = double($ep) if ref $ep eq 'ARRAY';
99             # convert epoch timestamp in seconds to microseconds
100 3         35 $self->{PDL} = longlong(floor(double($ep) * 1_000 + 0.5)) * 1000;
101 3         385 return $self;
102             }
103              
104             sub new_from_ratadie {
105 3     3 1 42 my ($class, $rd, %opts) = @_;
106 3         7 my $self = $class->initialize(%opts);
107 3 100       26 $rd = double($rd) if ref $rd eq 'ARRAY';
108             # EPOCH = (RD - 719_163) * 86_400
109             # only milisecond precision => strip microseconds
110 3         38 $self->{PDL} = longlong(floor((double($rd) - 719_163) * 86_400_000 + 0.5)) * 1000;
111 3         322 return $self;
112             }
113              
114             sub new_from_serialdate {
115 3     3 1 42 my ($class, $sd, %opts) = @_;
116 3         6 my $self = $class->initialize(%opts);
117 3 100       27 $sd = double($sd) if ref $sd eq 'ARRAY';
118             # EPOCH = (SD - 719_163 - 366) * 86_400
119             # only milisecond precision => strip microseconds
120 3         36 $self->{PDL} = longlong(floor((double($sd) - 719_529) * 86_400_000 + 0.5)) * 1000;
121 3         273 return $self;
122             }
123              
124             sub new_from_juliandate {
125 3     3 1 66 my ($class, $jd, %opts) = @_;
126 3         7 my $self = $class->initialize(%opts);
127 3 100       28 $jd = double($jd) if ref $jd eq 'ARRAY';
128             # EPOCH = (JD - 2_440_587.5) * 86_400
129             # only milisecond precision => strip microseconds
130 3         35 $self->{PDL} = longlong(floor((double($jd) - 2_440_587.5) * 86_400_000 + 0.5)) * 1000;
131 3         321 return $self;
132             }
133              
134             sub new_from_datetime {
135 32     32 1 1965 my ($class, $array, %opts) = @_;
136 32         70 my $self = $class->initialize(%opts);
137 32         329 $self->{PDL} = longlong _datetime_to_jumboepoch($array);
138 32         1315 return $self;
139             }
140              
141             sub new_from_parts {
142 3     3 1 12 my ($class, $y, $m, $d, $H, $M, $S, $U, %opts) = @_;
143 3 50 33     19 croak "new_from_parts: args - y, m, d - are mandatory" unless defined $y && defined $m && defined $d;
      33        
144 3         8 my $self = $class->initialize(%opts);
145 3 100       30 $y = long($y) if ref $y eq 'ARRAY';
146 3 100       39 $d = long($d) if ref $d eq 'ARRAY';
147 3 100       38 $m = long($m) if ref $m eq 'ARRAY';
148 3 100       37 $H = long($H) if ref $H eq 'ARRAY';
149 3 100       35 $M = long($M) if ref $M eq 'ARRAY';
150 3 100       34 $S = long($S) if ref $S eq 'ARRAY';
151 3 100       33 $U = long($U) if ref $U eq 'ARRAY';
152 3         36 my $rdate = _ymd2ratadie($y->copy, $m->copy, $d->copy);
153 3         74 my $epoch = (floor($rdate) - 719163) * 86400;
154 3 100       38 $epoch += floor($H) * 3600 if defined $H;
155 3 100       45 $epoch += floor($M) * 60 if defined $M;
156 3 100       32 $epoch += floor($S) if defined $S;
157 3         22 $epoch = longlong($epoch) * 1_000_000;
158 3 100       166 $epoch += longlong(floor($U)) if defined $U;
159 3         85 $self->{PDL} = longlong($epoch);
160 3         85 return $self;
161             }
162              
163             sub new_from_ymd {
164 1     1 1 22 my ($class, $ymd) = @_;
165 1         24 my $y = floor(($ymd/10000) % 10000);
166 1         22 my $m = floor(($ymd/100) % 100);
167 1         16 my $d = floor($ymd % 100);
168 1         4 return $class->new_from_parts($y, $m, $d);
169             }
170              
171             sub new_sequence {
172 44     44 1 14145 my ($class, $start, $count, $unit, $step, %opts) = @_;
173 44 50 33     171 croak "new_sequence: args - count, unit - are mandatory" unless defined $count && defined $unit;
174 44 100       57 $step = 1 unless defined $step;
175 44         85 my $self = $class->initialize(%opts);
176 44 50       472 my $tm_start = $start eq 'now' ? Time::Moment->now_utc : _dt2tm($start);
177 44         86 my $microseconds = $tm_start->microsecond;
178 44 100       65 if ($unit eq 'year') {
179             # slow :(
180 5         12 my @epoch = ($tm_start->epoch);
181 5         804 push @epoch, $tm_start->plus_years($_*$step)->epoch for (1..$count-1);
182 5         12 $self->{PDL} = longlong(\@epoch) * 1_000_000 + $microseconds;
183             }
184 44 50       356 if ($unit eq 'quarter') {
185             # slow :(
186 0         0 my @epoch = ($tm_start->epoch);
187 0         0 push @epoch, $tm_start->plus_months(3*$_*$step)->epoch for (1..$count-1);
188 0         0 $self->{PDL} = longlong(\@epoch) * 1_000_000 + $microseconds;
189             }
190 44 100       82 if ($unit eq 'month') {
    100          
191             # slow :(
192 12         26 my @epoch = ($tm_start->epoch);
193 12         1403 push @epoch, $tm_start->plus_months($_*$step)->epoch for (1..$count-1);
194 12         24 $self->{PDL} = longlong(\@epoch) * 1_000_000 + $microseconds;
195             }
196             elsif (my $inc = $INC_SECONDS{$unit}) { # week day hour minute second
197 27         41 my $epoch = $tm_start->epoch;
198 27         65 $self->{PDL} = (longlong(floor(sequence($count) * $step * $inc + 0.5)) + $epoch) * 1_000_000 + $microseconds;
199             }
200 44         5223 return $self;
201             }
202              
203             sub double_epoch {
204 7     7 1 3776 my $self = shift;
205             # EP = JUMBOEPOCH / 1_000_000;
206 7         60 my $epoch_milisec = ($self - ($self % 1000)) / 1000; # BEWARE: precision only in milliseconds!
207 7         148 return double($epoch_milisec) / 1_000;
208             }
209              
210             sub longlong_epoch {
211 2     2 1 2292 my $self = shift;
212             # EP = JUMBOEPOCH / 1_000_000;
213             # BEWARE: precision only in seconds!
214 2         14 my $epoch_sec = ($self - ($self % 1_000_000)) / 1_000_000;
215 2         47 return longlong($epoch_sec->{PDL});
216             }
217              
218             sub double_ratadie {
219 42     42 1 4462 my $self = shift;
220             # RD = EPOCH / 86_400 + 719_163;
221 42         176 my $epoch_milisec = ($self - ($self % 1000)) / 1000; # BEWARE: precision only in milliseconds!
222 42         730 return double($epoch_milisec) / 86_400_000 + 719_163;
223             }
224              
225             sub double_serialdate {
226 7     7 1 4156 my $self = shift;
227             # SD = EPOCH / 86_400 + 719_163 + 366;
228 7         44 my $epoch_milisec = ($self - ($self % 1000)) / 1000; # BEWARE: precision only in milliseconds!
229 7         141 return double($epoch_milisec) / 86_400_000 + 719_529;
230             }
231              
232             sub double_juliandate {
233 6     6 1 3755 my $self = shift;
234             # JD = EPOCH / 86_400 + 2_440_587.5;
235 6         34 my $epoch_milisec = ($self - ($self % 1000)) / 1000; # BEWARE: precision only in milliseconds!
236 6         109 return double($epoch_milisec) / 86_400_000 + 2_440_587.5;
237             }
238              
239             sub dt_ymd {
240 1     1 1 9 my $self = shift;
241 1         4 my ($y, $m, $d) = _ratadie2ymd($self->double_ratadie);
242 1         8 return (short($y), byte($m), byte($d));
243             }
244              
245             sub dt_year {
246 1     1 1 1229 my $self = shift;
247 1         3 my ($y, undef, undef) = _ratadie2ymd($self->double_ratadie);
248 1         9 return short($y);
249             }
250              
251             sub dt_quarter {
252 1     1 1 2 my $self = shift;
253 1         4 my (undef, $m, undef) = _ratadie2ymd($self->double_ratadie);
254 1         8 return ((byte($m)-1) / 3) + 1;
255             }
256              
257             sub dt_month {
258 1     1 1 355 my $self = shift;
259 1         3 my (undef, $m, undef) = _ratadie2ymd($self->double_ratadie);
260 1         8 return byte($m);
261             }
262              
263             sub dt_day {
264 1     1 1 350 my $self = shift;
265 1         4 my (undef, undef, $d) = _ratadie2ymd($self->double_ratadie);
266 1         9 return byte($d);
267             }
268              
269             sub dt_hour {
270 1     1 1 388 my $self = shift;
271 1         8 return PDL->new(byte((($self - ($self % 3_600_000_000)) / 3_600_000_000) % 24));
272             }
273              
274             sub dt_minute {
275 1     1 1 350 my $self = shift;
276 1         7 return PDL->new(byte((($self - ($self % 60_000_000)) / 60_000_000) % 60));
277             }
278              
279             sub dt_second {
280 1     1 1 344 my $self = shift;
281 1         8 return PDL->new(byte((($self - ($self % 1_000_000)) / 1_000_000) % 60));
282             }
283              
284             sub dt_microsecond {
285 1     1 1 383 my $self = shift;
286 1         7 return PDL->new(long($self % 1_000_000));
287             }
288              
289             sub dt_day_of_week {
290 1     1 1 354 my $self = shift;
291 1         7 my $days = ($self - ($self % 86_400_000_000)) / 86_400_000_000;
292 1         22 return PDL->new(byte(($days + 3) % 7) + 1); # 1..Mon, 7..Sun
293             }
294              
295             sub dt_day_of_year {
296 1     1 1 362 my $self = shift;
297 1         2 my $rd1 = long(floor($self->double_ratadie));
298 1         67 my $rd2 = long(floor($self->dt_align('year')->double_ratadie));
299 1         73 return PDL->new(short, ($rd1 - $rd2 + 1));
300             }
301              
302             sub dt_add {
303 48     48 1 231 my $self = shift;
304 48 100       127 if ($self->is_inplace) {
305 16         26 $self->set_inplace(0);
306 16         26 while (@_) {
307 16         19 my ($unit, $num) = (shift, shift);
308 16 100       52 if ($unit eq 'month') {
    50          
    100          
    100          
    100          
    50          
309 3         4 $self += $self->_plus_delta_m($num);
310             }
311             elsif ($unit eq 'quarter') {
312 0         0 $self += $self->_plus_delta_m($num * 3);
313             }
314             elsif ($unit eq 'year') {
315 4         26 $self += $self->_plus_delta_m($num * 12);
316             }
317             elsif ($unit eq 'millisecond') {
318 1         3 $self += $num * 1000;
319             }
320             elsif ($unit eq 'microsecond') {
321 1         2 $self += $num;
322             }
323             elsif (my $inc = $INC_SECONDS{$unit}) { # week day hour minute second
324 7         63 my $add = longlong(floor($num * $inc * 1_000_000 + 0.5));
325 7         255 $self->inplace->plus($add, 0);
326             }
327             }
328 16         200 return $self;
329             }
330             else {
331 32         65 my $rv = $self->copy;
332 32         64 while (@_) {
333 32         41 my ($unit, $num) = (shift, shift);
334 32 100       103 if ($unit eq 'month') {
    50          
    100          
    100          
    100          
    50          
335 6         8 $rv += $rv->_plus_delta_m($num);
336             }
337             elsif ($unit eq 'quarter') {
338 0         0 $rv += $rv->_plus_delta_m($num * 3);
339             }
340             elsif ($unit eq 'year') {
341 8         15 $rv += $rv->_plus_delta_m($num * 12);
342             }
343             elsif ($unit eq 'millisecond') {
344 2         5 $rv += $num * 1000;
345             }
346             elsif ($unit eq 'microsecond') {
347 2         4 $rv += $num;
348             }
349             elsif(my $inc = $INC_SECONDS{$unit}) { # week day hour minute second
350 14         124 $rv += longlong(floor($num * $inc * 1_000_000 + 0.5));
351             }
352             }
353 32         858 return $rv;
354             }
355             }
356              
357             sub dt_align {
358 24     24 1 2521 my ($self, $unit, $up) = @_;
359 24 100       66 if ($self->is_inplace) {
360 8         12 $self->set_inplace(0);
361 8 50       15 return $self unless defined $unit;
362 8 100       31 if ($unit eq 'year') {
    50          
    100          
    100          
    50          
363 1         2 $self->{PDL} = $self->_allign_myq(0, 1, 0, $up)->{PDL};
364             }
365             elsif ($unit eq 'quarter') {
366 0         0 $self->{PDL} = $self->_allign_myq(0, 0, 1, $up)->{PDL};
367             }
368             elsif ($unit eq 'month') {
369 1         2 $self->{PDL} = $self->_allign_myq(1, 0, 0, $up)->{PDL};
370             }
371             elsif ($unit eq 'millisecond') {
372 1         5 my $sub = $self % 1_000;
373 1         17 $self->inplace->minus($sub, 0);
374             }
375             elsif (my $inc = $INC_SECONDS{$unit}) { # week day hour minute second
376 5 100       29 my $sub = $unit eq 'week' ? ($self + 3 * 60 * 60 * 24 * 1_000_000) % ($inc * 1_000_000) : $self % ($inc * 1_000_000);
377 5 50 33     81 $sub -= 6 * 60 * 60 * 24 * 1_000_000 if $up && $unit eq 'week';
378 5         12 $self->inplace->minus($sub, 0);
379             }
380 8         92 return $self;
381             }
382             else {
383 16 50       28 return unless defined $unit;
384 16 100       70 if ($unit eq 'year') {
    50          
    100          
    100          
    50          
385 3         9 return $self->_allign_myq(0, 1, 0, $up);
386             }
387             elsif ($unit eq 'quarter') {
388 0         0 return $self->_allign_myq(0, 0, 1, $up);
389             }
390             elsif ($unit eq 'month') {
391 2         7 return $self->_allign_myq(1, 0, 0, $up);
392             }
393             elsif ($unit eq 'millisecond') {
394 1         6 return $self - $self % 1_000;
395             }
396             elsif (my $inc = $INC_SECONDS{$unit}) { # week day hour minute second
397 10 100       66 my $sub = $unit eq 'week' ? ($self + 3 * 60 * 60 * 24 * 1_000_000) % ($inc * 1_000_000) : $self % ($inc * 1_000_000);
398 10 50 33     199 $sub -= 6 * 60 * 60 * 24 * 1_000_000 if $up && $unit eq 'week';
399 10         31 return $self - $sub;
400             }
401             }
402             }
403              
404             sub dt_at {
405 5     5 1 6 my $self = shift;
406 5 50       12 my $fmt = looks_like_number($_[-1]) ? 'auto' : pop;
407 5         15 my $v = PDL::Core::at_c($self, [@_]);
408 5 50 33     27 $fmt = $self->_autodetect_strftime_format if !$fmt || $fmt eq 'auto';
409 5         103 return _jumboepoch_to_datetime($v, $fmt);
410             }
411              
412             sub dt_set {
413 4     4 1 5 my $self = shift;
414 4         5 my $datetime = pop;
415 4         9 PDL::Core::set_c($self, [@_], _datetime_to_jumboepoch($datetime));
416             }
417              
418             sub dt_unpdl {
419 113     113 1 4157 my ($self, $fmt) = @_;
420 113 100 66     413 $fmt = $self->_autodetect_strftime_format if !$fmt || $fmt eq 'auto';
421 113 100       1427 if ($fmt eq 'epoch') {
    100          
422 1         4 return (double($self) / 1_000_000)->unpdl;
423             }
424             elsif ($fmt eq 'epoch_int') {
425 1         11 return longlong(($self - ($self % 1_000_000)) / 1_000_000)->unpdl;
426             }
427             else {
428 111         210 my $array = $self->unpdl;
429 111         1826 _jumboepoch_to_datetime($array, $fmt, 1); # change $array inplace!
430 111         455 return $array;
431             }
432             }
433              
434             sub dt_diff {
435 0     0 1 0 my ($self, $unit) = @_;
436 0 0       0 return PDL->new('BAD')->reshape(1) if $self->nelem == 1;
437 0         0 my $rv = PDL->new(longlong, 'BAD')->glue(0, $self->slice("1:-1") - $self->slice("0:-2"));
438 0 0       0 return $rv unless $unit;
439 0 0       0 return double($rv) / 604_800_000_000 if $unit eq 'week';
440 0 0       0 return double($rv) / 86_400_000_000 if $unit eq 'day';
441 0 0       0 return double($rv) / 3_600_000_000 if $unit eq 'hour';
442 0 0       0 return double($rv) / 60_000_000 if $unit eq 'minute';
443 0 0       0 return double($rv) / 1_000_000 if $unit eq 'second';
444 0 0       0 return double($rv) / 1_000 if $unit eq 'millisecond';
445 0         0 croak "dt_diff: invalid unit '$unit'";
446             }
447              
448             sub dt_periodicity {
449 0     0 1 0 my $self = shift;
450 0 0 0     0 return '' if !$self->is_increasing && !$self->is_decreasing;
451 0         0 my $freq = $self->qsort->dt_diff->median;
452 0 0 0     0 return '' if $freq eq 'BAD' || $freq < 0;
453 0 0 0     0 if ($freq < 1_000 ) {
    0 0        
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
    0          
454             # $freq < 1 millisecond
455 0         0 return "microsecond";
456             }
457             elsif ($freq < 1_000_000 ) {
458             # 1 millisecond <= $freq < 1 second
459 0         0 return "millisecond";
460             }
461             elsif ($freq < 60_000_000 ) {
462             # 1 second <= $freq < 1 minute
463 0         0 return "second";
464             }
465             elsif ($freq < 3_600_000_000) {
466             # 1 minute <= $freq < 1 hour
467 0         0 return "minute";
468             }
469             elsif ($freq < 86_400_000_000) {
470             # 1 hour <= $freq < 24 hours
471 0         0 return "hour";
472             }
473             elsif ($freq == 86_400_000_000) {
474             # 24 hours
475 0         0 return "day";
476             }
477             elsif ($freq == 604_800_000_000) {
478             # 7 days
479 0         0 return "week";
480             }
481             elsif ($freq >= 2_419_200_000_000 && $freq <= 2_678_400_000_000 ) {
482             # 28days <= $freq <= 31days
483 0         0 return "month";
484             }
485             elsif ($freq >= 7_776_000_000_000 && $freq <= 7_948_800_000_000 ) {
486             # 90days <= $freq <= 92days
487 0         0 return "quarter";
488             }
489             elsif ($freq >= 31_536_000_000_000 && $freq <= 31_622_400_000_000 ) {
490             # 365days <= $freq <= 366days
491 0         0 return "year";
492             }
493 0         0 return ''; # unknown
494             }
495              
496             sub dt_startpoints {
497 0     0 1 0 my ($self, $unit) = @_;
498 0 0       0 croak "dt_startpoints: undefined unit" unless $unit;
499 0 0       0 croak "dt_startpoints: 1D piddle required" unless $self->ndims == 1;
500 0 0       0 croak "dt_startpoints: input not increasing" unless $self->is_increasing;
501 0         0 return indx(0)->append($self->dt_endpoints($unit)->slice("0:-2") + 1);
502             }
503              
504             sub dt_endpoints {
505 0     0 1 0 my ($self, $unit) = @_;
506 0 0       0 croak "dt_endpoints: undefined unit" unless $unit;
507 0 0       0 croak "dt_endpoints: 1D piddle required" unless $self->ndims == 1;
508 0 0       0 croak "dt_endpoints: input not increasing" unless $self->is_increasing;
509 0         0 my $diff = $self->dt_align($unit)->dt_diff;
510 0         0 my $end = which($diff != 0) - 1;
511 0 0       0 if ($end->nelem == 0) {
512 0         0 $end = indx([$self->nelem-1]);
513             }
514             else {
515 0 0       0 $end = $end->append($self->nelem-1) unless $end->at($end->nelem-1) == $end->nelem-1;
516             }
517 0         0 return indx($end);
518             }
519              
520             sub dt_slices {
521 0     0 1 0 my ($self, $unit) = @_;
522 0 0       0 croak "dt_slices: undefined unit" unless $unit;
523 0 0       0 croak "dt_slices: 1D piddle required" unless $self->ndims == 1;
524 0 0       0 croak "dt_slices: input not increasing" unless $self->is_increasing;
525 0         0 my $end = $self->dt_endpoints($unit);
526 0         0 my $start = indx([0]);
527 0 0       0 $start = $start->append($end->slice("0:-2") + 1) if $end->nelem > 1;
528 0         0 return $start->cat($end)->transpose;
529             }
530              
531             sub dt_nperiods {
532 0     0 1 0 my ($self, $unit) = @_;
533 0 0       0 croak "dt_nperiods: undefined unit" unless $unit;
534 0         0 return $self->dt_endpoints($unit)->nelem;
535             }
536              
537             sub is_increasing {
538 0     0 1 0 my ($self, $strictly) = @_;
539 0 0       0 return !(which($self->dt_diff <= 0)->nelem > 0) if $strictly;
540 0         0 return !(which($self->dt_diff < 0)->nelem > 0);
541             }
542              
543             sub is_decreasing {
544 0     0 1 0 my ($self, $strictly) = @_;
545 0 0       0 return !(which($self->dt_diff >= 0)->nelem > 0) if $strictly;
546 0         0 return !(which($self->dt_diff > 0)->nelem > 0);
547             }
548              
549             sub is_uniq {
550 0     0 1 0 my $self = shift;
551 0         0 my $diff = $self->qsort->dt_diff;
552 0         0 return !(which($diff == 0)->nelem > 0);
553             }
554              
555             sub is_regular {
556 0     0 1 0 my $self = shift;
557 0         0 my $dt = $self->dt_diff;
558 0         0 my $diff = $self->dt_diff->qsort;
559 0         0 my $min = $diff->min;
560 0         0 my $max = $diff->max;
561 0   0     0 return ($min ne "BAD") && ($max ne "BAD") && ($min == $max) && ($max > 0);
562             }
563              
564             ### private methods
565              
566             sub _stringify {
567 1     1   198 my $self = shift;
568 1 50       6 my $data = $self->ndims > 0 ? $self->dt_unpdl : $self->dt_unpdl->[0];
569 1         4 my $rv = _print_array($data, 0);
570 1         5 $rv =~ s/\n$//;
571 1         8 return $rv;
572             }
573              
574             sub _num_compare_gt {
575 0     0   0 my ($self, $other, $swap) = @_;
576 0 0 0     0 $other = PDL::DateTime->new_from_datetime($other) if !ref $other && !looks_like_number($other);
577 0         0 PDL::gt($self, $other, $swap);
578             }
579              
580             sub _num_compare_lt {
581 0     0   0 my ($self, $other, $swap) = @_;
582 0 0 0     0 $other = PDL::DateTime->new_from_datetime($other) if !ref $other && !looks_like_number($other);
583 0         0 PDL::lt($self, $other, $swap);
584             }
585              
586             sub _num_compare_ge {
587 0     0   0 my ($self, $other, $swap) = @_;
588 0 0 0     0 $other = PDL::DateTime->new_from_datetime($other) if !ref $other && !looks_like_number($other);
589 0         0 PDL::ge($self, $other, $swap);
590             }
591              
592             sub _num_compare_le {
593 0     0   0 my ($self, $other, $swap) = @_;
594 0 0 0     0 $other = PDL::DateTime->new_from_datetime($other) if !ref $other && !looks_like_number($other);
595 0         0 PDL::le($self, $other, $swap);
596             }
597              
598             sub _num_compare_eq {
599 27     27   6679 my ($self, $other, $swap) = @_;
600 27 0 33     59 $other = PDL::DateTime->new_from_datetime($other) if !ref $other && !looks_like_number($other);
601 27         118 PDL::eq($self, $other, $swap);
602             }
603              
604             sub _num_compare_ne {
605 266     266   2841 my ($self, $other, $swap) = @_;
606 266 50 33     737 $other = PDL::DateTime->new_from_datetime($other) if !ref $other && !looks_like_number($other);
607 266         820 PDL::ne($self, $other, $swap);
608             }
609              
610             sub _autodetect_strftime_format {
611 75     75   58 my $self = shift;
612 75 100       319 if (which(($self % (24*60*60*1_000_000)) != 0)->nelem == 0) {
    100          
    100          
    100          
613 8         112 return "%Y-%m-%d";
614             }
615             elsif (which(($self % (60*1_000_000)) != 0)->nelem == 0) {
616 4         48 return "%Y-%m-%dT%H:%M";
617             }
618             elsif (which(($self % 1_000_000) != 0)->nelem == 0) {
619 2         25 return "%Y-%m-%dT%H:%M:%S";
620             }
621             elsif (which(($self % 1_000) != 0)->nelem == 0) {
622 2         25 return "%Y-%m-%dT%H:%M:%S.%3N";
623             }
624             else {
625 59         747 return "%Y-%m-%dT%H:%M:%S.%6N";
626             }
627             }
628              
629             sub _plus_delta_m {
630 21     21   22 my ($self, $delta_m) = @_;
631 21         97 my $day_fraction = $self % 86_400_000_000;
632 21         377 my $rdate_bf = ($self - $day_fraction)->double_ratadie;
633 21         495 my ($y, $m, $d) = _ratadie2ymd($rdate_bf);
634 21         36 my $rdate_af = _ymd2ratadie($y, $m, $d, $delta_m);
635 21         107 my $rv = longlong($rdate_af - $rdate_bf) * 86_400_000_000;
636 21         1005 return $rv;
637             }
638              
639             sub _allign_myq {
640 7     7   9 my ($self, $mflag, $yflag, $qflag, $up) = @_;
641 7         13 my $rdate = $self->double_ratadie;
642 7         151 my ($y, $m, $d) = _ratadie2ymd($rdate);
643 7 50       31 $m .= $up ? 12 : 1 if $yflag;
    100          
644 7 0       89 $m = $up ? $m+((3-$m)%3) : $m-(($m-1)%3) if $qflag;
    50          
645 7 50       18 $d .= $up ? _days_in_month($y, $m) : 1;
646 7         107 $rdate = _ymd2ratadie($y, $m, $d);
647 7         79 return PDL::DateTime->new(longlong(floor($rdate) - 719163) * 86_400_000_000);
648             }
649              
650             ### public functions (used e.g. by PDL::IO::CSV)
651              
652             sub dt2ll {
653 0     0 0 0 eval {
654 0         0 my $tm = Time::Moment->from_string(_fix_datetime_value(shift), lenient=>1);
655 0         0 $tm->epoch * 1_000_000 + $tm->microsecond;
656             };
657             }
658              
659             sub ll2dt {
660 0     0 0 0 my $v = shift;
661 0         0 my $us = int($v % 1_000_000);
662 0         0 my $ts = int(($v - $us) / 1_000_000);
663 0 0       0 my $rv = eval { Time::Moment->from_epoch($ts, $us * 1000)->to_string(reduced=>1) } or return;
  0         0  
664 0         0 $rv =~ s/(T00:00)?Z$//;
665 0         0 return $rv;
666             }
667              
668             ### private functions
669              
670             sub _dt2tm {
671 648     648   440 eval { Time::Moment->from_string(_fix_datetime_value(shift), lenient=>1) };
  648         609  
672             }
673              
674             sub _ll2tm {
675 768     768   492 my $v = shift;
676 768         578 my $us = int($v % 1_000_000);
677 768         609 my $ts = int(($v - $us) / 1_000_000);
678 768         491 eval { Time::Moment->from_epoch($ts, $us * 1000) };
  768         1516  
679             }
680              
681             sub _print_array {
682 1     1   1 my ($val, $level) = @_;
683 1         3 my $prefix = " " x $level;
684 1 50 33     6 if (ref $val eq 'ARRAY' && !ref $val->[0]) {
    0          
685 1         5 return $prefix . join(" ", '[', @$val, ']') . "\n";
686             }
687             elsif (ref $val eq 'ARRAY') {
688 0         0 my $out = $prefix."[\n";
689 0         0 $out .= _print_array($_, $level + 1) for (@$val);
690 0         0 $out .= $prefix."]\n";
691             }
692             else {
693 0         0 return $prefix . $val . "\n";
694             }
695             }
696              
697             sub _fix_datetime_value {
698 648     648   396 my $v = shift;
699             # '2015-12-29' > '2015-12-29T00Z'
700 648 100       1376 return $v."T00Z" if $v =~ /^\d\d\d\d-\d\d-\d\d$/;
701             # '2015-12-29 11:59' > '2015-12-29 11:59Z'
702 647 50       973 return $v."Z" if $v =~ /^\d\d\d\d-\d\d-\d\d[ T]\d\d:\d\d$/;
703             # '2015-12-29 11:59:11' > '2015-12-29 11:59:11Z' or '2015-12-29 11:59:11.123' > '2015-12-29 11:59:11.123Z'
704 647 100       2950 return $v."Z" if $v =~ /^\d\d\d\d-\d\d-\d\d[ T]\d\d:\d\d:\d\d(\.\d+)?$/;
705 58         245 return $v;
706             }
707              
708             sub _datetime_to_jumboepoch {
709 636     636   516 my ($dt, $inplace) = @_;
710 636         394 my $tm;
711 636 100       652 if (ref $dt eq 'ARRAY') {
712 32         23 my @new;
713 32         48 for (@$dt) {
714 600         580 my $s = _datetime_to_jumboepoch($_, $inplace);
715 600 50       578 if ($inplace) {
716 0 0       0 $_ = (ref $_ ? undef : $s) if ref $_ ne 'ARRAY';
    0          
717             }
718             else {
719 600         623 push @new, $s;
720             }
721             }
722 32 50       116 return \@new if !$inplace;
723             }
724             else {
725 604 50 0     1091 if (looks_like_number $dt) {
    50          
    0          
    0          
726 0         0 return int POSIX::floor($dt * 1_000_000 + 0.5);
727             }
728             elsif (!ref $dt) {
729 604 50       731 $tm = ($dt eq 'now') ? Time::Moment->now_utc : _dt2tm($dt);
730             }
731             elsif (ref $dt eq 'DateTime' || ref $dt eq 'Time::Piece') {
732 0         0 $tm = eval { Time::Moment->from_object($dt) };
  0         0  
733             }
734             elsif (ref $dt eq 'Time::Moment') {
735 0         0 $tm = $dt;
736             }
737 604 50       3290 return undef unless $tm;
738 604         1504 return int($tm->epoch * 1_000_000 + $tm->microsecond);
739             }
740             }
741              
742             sub _jumboepoch_to_datetime {
743 879     879   713 my ($v, $fmt, $inplace) = @_;
744 879 50       979 return 'BAD' unless defined $v;
745 879 100       1436 if (ref $v eq 'ARRAY') {
    50          
746 111         81 my @new;
747 111         153 for (@$v) {
748 763         687 my $s = _jumboepoch_to_datetime($_, $fmt, $inplace);
749 763 50       725 if ($inplace) {
750 763 50       1296 $_ = $s if ref $_ ne 'ARRAY';
751             }
752             else {
753 0         0 push @new, $s;
754             }
755             }
756 111 50       581 return \@new if !$inplace;
757             }
758             elsif (!ref $v) {
759 768         656 my $tm = _ll2tm($v);
760 768 50       939 return 'BAD' unless defined $tm;
761 768 50       721 if ($fmt eq 'Time::Moment') {
762 0         0 return $tm;
763             }
764             else {
765 768         2162 return $tm->strftime($fmt);
766             }
767             }
768             }
769              
770             my $DAYS_PER_400_YEARS = 146_097;
771             my $DAYS_PER_100_YEARS = 36_524;
772             my $DAYS_PER_4_YEARS = 1_461;
773             my $MAR_1_TO_DEC_31 = 306;
774              
775             sub _ymd2ratadie {
776 31     31   161 my ($y, $m, $d, $delta_m) = @_;
777             # based on Rata Die calculation from https://metacpan.org/source/DROLSKY/DateTime-1.10/lib/DateTime.xs#L151
778             # RD: 1 => 0001-01-01
779             # RD: 2 => 0001-01-02
780             # RD: 719163 => 1970-01-01
781             # RD: 730120 => 2000-01-01
782             # RD: 2434498 => 6666-06-06
783             # RD: 3652059 => 9999-12-31
784              
785 31 100       55 if (defined $delta_m) {
786             # handle months + years
787 21         41 $m->inplace->plus($delta_m - 1, 0);
788 21         497 my $extra_y = floor($m / 12);
789 21         80 $m->inplace->modulo(12, 0);
790 21         325 $m->inplace->plus(1, 0);
791 21         284 $y->inplace->plus($extra_y, 0);
792             # fix days
793 21         865 my $dec_by_one = ($d==31) * (($m==4) + ($m==6) + ($m==9) + ($m==11));
794             # 1800, 1900, 2100, 2200, 2300 - common; 2000, 2400 - leap
795 21         824 my $is_nonleap_yr = (($y % 4)!=0) + (($y % 100)==0) - (($y % 400)==0);
796 21         607 my $dec_nonleap_feb = ($m==2) * ($d>28) * $is_nonleap_yr * ($d-28);
797 21         587 my $dec_leap_feb = ($m==2) * ($d>29) * (1 - $is_nonleap_yr) * ($d-29);
798 21         191 $d->inplace->minus($dec_by_one + $dec_leap_feb + $dec_nonleap_feb, 0);
799             }
800              
801 31         523 my $rdate = double($d); # may contain day fractions
802 31         1676 $rdate->setbadif(($y < 1) + ($y > 9999));
803 31         650 $rdate->setbadif(($m < 1) + ($m > 12));
804 31         680 $rdate->setbadif(($d < 1) + ($d >= 32)); # not 100% correct (max. can be 31.9999999)
805              
806 31         368 my $m2 = ($m <= 2);
807 31         104 $y -= $m2;
808 31         371 $m += $m2 * 12;
809              
810 31         802 $rdate += floor(($m * 367 - 1094) / 12);
811 31         871 $rdate += floor($y % 100 * $DAYS_PER_4_YEARS / 4);
812 31         985 $rdate += floor($y / 100) * $DAYS_PER_100_YEARS + floor($y / 400);
813 31         373 $rdate -= $MAR_1_TO_DEC_31;
814 31         299 return $rdate;
815             }
816              
817             sub _ratadie2ymd {
818             # based on Rata Die calculation from https://metacpan.org/source/DROLSKY/DateTime-1.10/lib/DateTime.xs#L82
819 33     33   151 my $rdate = shift;
820              
821 33         215 my $d = floor($rdate);
822 33         81 $d += $MAR_1_TO_DEC_31;
823              
824 33         920 my $c = floor((($d * 4) - 1) / $DAYS_PER_400_YEARS); # century
825 33         583 $d -= floor($c * $DAYS_PER_400_YEARS / 4);
826 33         810 my $y = floor((($d * 4) - 1) / $DAYS_PER_4_YEARS);
827 33         556 $d -= floor($y * $DAYS_PER_4_YEARS / 4);
828 33         789 my $m = floor((($d * 12) + 1093) / 367);
829 33         692 $d -= floor((($m * 367) - 1094) / 12);
830 33         485 $y += ($c * 100);
831              
832 33         410 my $m12 = ($m > 12);
833 33         120 $y += $m12;
834 33         299 $m -= $m12 * 12;
835              
836 33         279 return ($y, $m, $d);
837             }
838              
839             sub _is_non_leap_year {
840 0     0     my $y = shift;
841 0           return (($y % 4)!=0) + (($y % 100)==0) - (($y % 400)==0);
842             }
843              
844             sub _days_in_year {
845 0     0     my $y = shift;
846 0           return 366 - _is_non_leap_year($y);
847             }
848              
849             sub _days_in_month {
850 0     0     my ($y, $m) = @_;
851 0           my $dec_simple = (2*($m==2) + ($m==4) + ($m==6) + ($m==9) + ($m==11));
852 0           my $dec_nonleap_feb = ($m==2) * _is_non_leap_year($y);
853 0           return 31 - $dec_simple - $dec_nonleap_feb;
854             }
855              
856             1;