File Coverage

blib/lib/DateTime/Event/Predict/Profile.pm
Criterion Covered Total %
statement 61 111 54.9
branch 6 32 18.7
condition 4 12 33.3
subroutine 11 22 50.0
pod 3 3 100.0
total 85 180 47.2


line stmt bran cond sub pod time code
1              
2             #==================================================================== -*-perl-*-
3             #
4             # DateTime::Event::Predict::Profile
5             #
6             # DESCRIPTION
7             # Provides default profiles and mechanisms for creating custom profiles
8             #
9             # AUTHORS
10             # Brian Hann
11             #
12             #===============================================================================
13              
14             package DateTime::Event::Predict::Profile;
15              
16 1     1   50342 use Carp qw( croak confess );
  1         3  
  1         77  
17 1     1   936 use Params::Validate qw(:all);
  1         22168  
  1         259  
18 1     1   4127 use List::MoreUtils qw(uniq);
  1         2957  
  1         156  
19              
20 1     1   12 use Exporter;
  1         5  
  1         3286  
21             our @ISA = qw(Exporter);
22             our @EXPORT_OK = qw(%DISTINCT_BUCKETS %INTERVAL_BUCKETS);
23             our %EXPORT_TAGS = (buckets => [qw(%DISTINCT_BUCKETS %INTERVAL_BUCKETS)]);
24              
25              
26             our %PROFILES = (
27             default => {
28             distinct_buckets => [
29             'day_of_week',
30             'day_of_month',
31             'day_of_year',
32             ],
33             },
34             holiday => {
35             distinct_buckets => [
36             'day_of_year',
37             'day_of_week',
38             ],
39             },
40             daily => {
41             distinct_buckets => [
42             'day_of_year'
43             ],
44             },
45             );
46              
47             our %DISTINCT_BUCKETS = (
48             nanosecond => DateTime::Event::Predict::Profile::Bucket->new(
49             name => 'nanosecond',
50             type => 'distinct',
51             accessor => 'nanosecond',
52             duration => 'nanoseconds',
53             trimmable => 1,
54             order => 1,
55             ),
56             #microsecond => DateTime::Event::Predict::Profile::Bucket->new(
57             # name => 'microsecond',
58             # accessor => 'microsecond',
59             # duration => 'microseconds',
60             # order => 2,
61             #),
62             #millisecond => DateTime::Event::Predict::Profile::Bucket->new(
63             # name => 'millisecond',
64             # accessor => 'millisecond',
65             # duration => 'milliseconds',
66             # order => 3,
67             #),
68             second => DateTime::Event::Predict::Profile::Bucket->new(
69             name => 'second',
70             type => 'distinct',
71             accessor => 'second',
72             duration => 'seconds',
73             trimmable => 1,
74             order => 4,
75             ),
76             #fractional_second => DateTime::Event::Predict::Profile::Bucket->new(
77             # accessor => 'fractional_second',
78             # order => 5,
79             #),
80             minute => DateTime::Event::Predict::Profile::Bucket->new(
81             name => 'minute',
82             type => 'distinct',
83             accessor => 'minute',
84             duration => 'minutes',
85             trimmable => 1,
86             order => 6,
87             ),
88             hour => DateTime::Event::Predict::Profile::Bucket->new(
89             name => 'hour',
90             type => 'distinct',
91             accessor => 'hour',
92             duration => 'hours',
93             trimmable => 1,
94             order => 7,
95             ),
96             day_of_week => DateTime::Event::Predict::Profile::Bucket->new(
97             name => 'day_of_week',
98             type => 'distinct',
99             accessor => 'day_of_week',
100             duration => 'days',
101             trimmable => 0,
102             order => 8,
103             ),
104             day_of_month => DateTime::Event::Predict::Profile::Bucket->new(
105             name => 'day_of_month',
106             type => 'distinct',
107             accessor => 'day',
108             duration => 'days',
109             trimmable => 1,
110             order => 9,
111             ),
112             day_of_quarter => DateTime::Event::Predict::Profile::Bucket->new(
113             name => 'day_of_quarter',
114             type => 'distinct',
115             accessor => 'day_of_quarter',
116             duration => 'days',
117             trimmable => 0,
118             order => 10,
119             ),
120             weekday_of_month => DateTime::Event::Predict::Profile::Bucket->new(
121             name => 'weekday',
122             type => 'distinct',
123             accessor => 'weekday', #Returns a number from 1..5 indicating which week day of the month this is. For example, June 9, 2003 is the second Monday of the month, and so this method returns 2 for that day.
124             duration => 'days',
125             trimmable => 0,
126             order => 11,
127             ),
128             week_of_month => DateTime::Event::Predict::Profile::Bucket->new(
129             name => 'week_of_month',
130             type => 'distinct',
131             accessor => 'week_of_month',
132             duration => 'weeks',
133             trimmable => 0,
134             order => 12,
135             ),
136             day_of_year => DateTime::Event::Predict::Profile::Bucket->new(
137             name => 'day_of_year',
138             type => 'distinct',
139             accessor => 'day_of_year',
140             duration => 'days',
141             trimmable => 0,
142             order => 13,
143             ),
144             week_number => DateTime::Event::Predict::Profile::Bucket->new(
145             name => 'week_number',
146             type => 'distinct',
147             accessor => 'week_number',
148             duration => 'weeks',
149             trimmable => 0,
150             order => 14,
151             ),
152             month_of_year => DateTime::Event::Predict::Profile::Bucket->new(
153             name => 'month_of_year',
154             type => 'distinct',
155             accessor => 'month',
156             duration => 'months',
157             trimmable => 1,
158             order => 15,
159             ),
160             quarter_of_year => DateTime::Event::Predict::Profile::Bucket->new(
161             name => 'quarter_of_year',
162             type => 'distinct',
163             accessor => 'quarter',
164             duration => 'quarters', #I don't think this duration exists
165             trimmable => 0,
166             order => 16,
167             ),
168             year => DateTime::Event::Predict::Profile::Bucket->new(
169             name => 'year',
170             type => 'distinct',
171             accessor => 'year',
172             duration => 'years', #I don't think this duration exists
173             trimmable => 0,
174             order => 17,
175             ),
176             );
177              
178             #Aliases
179             $DISTINCT_BUCKETS{'second_of_minute'} = $DISTINCT_BUCKETS{'second'};
180             $DISTINCT_BUCKETS{'minute_of_hour'} = $DISTINCT_BUCKETS{'minute'};
181             $DISTINCT_BUCKETS{'hour_of_day'} = $DISTINCT_BUCKETS{'hour'};
182             $DISTINCT_BUCKETS{'day'} = $DISTINCT_BUCKETS{'day_of_month'};
183             $DISTINCT_BUCKETS{'week_of_year'} = $DISTINCT_BUCKETS{'week_number'};
184              
185             #***We'll need an order of precedence here, so that when we find a difference in months we don't increment any of the differences smaller
186             # than that (weeks, days). *OR do we want to increment the difference but leave the weight so small that it has a smaller effect? I can't see why that
187             # would be useful
188              
189             # Interval buckets
190             our %INTERVAL_BUCKETS = (
191             nanoseconds => DateTime::Event::Predict::Profile::Bucket->new(
192             name => 'nanoseconds',
193             type => 'interval',
194             accessor => 'nanoseconds', # Accessor in the DateTime::Duration object that we use to get the difference
195             order => 0, # Order of precedence of this bucket (larger means it takes precedence)
196             ),
197             seconds => DateTime::Event::Predict::Profile::Bucket->new(
198             name => 'seconds',
199             type => 'interval',
200             accessor => 'seconds',
201             order => 1,
202             ),
203             minutes => DateTime::Event::Predict::Profile::Bucket->new(
204             name => 'minutes',
205             type => 'interval',
206             accessor => 'minutes',
207             order => 2,
208             ),
209             hours => DateTime::Event::Predict::Profile::Bucket->new(
210             name => 'hours',
211             type => 'interval',
212             accessor => 'hours',
213             order => 3,
214             ),
215             days => DateTime::Event::Predict::Profile::Bucket->new(
216             name => 'days',
217             type => 'interval',
218             accessor => 'days',
219             order => 4,
220             ),
221             weeks => DateTime::Event::Predict::Profile::Bucket->new(
222             name => 'weeks',
223             type => 'interval',
224             accessor => 'weeks',
225             order => 5,
226             ),
227             months => DateTime::Event::Predict::Profile::Bucket->new(
228             name => 'months',
229             type => 'interval',
230             accessor => 'months',
231             order => 6,
232             ),
233             years => DateTime::Event::Predict::Profile::Bucket->new(
234             name => 'years',
235             type => 'interval',
236             accessor => 'years',
237             order => 7,
238             ),
239             );
240              
241             # Make a list of all the accessors so we can check for them
242             our @distinct_bucket_accessors = map { $_->{accessor} } values %DISTINCT_BUCKETS;
243             our @interval_bucket_accessors = map { $_->{accessor} } values %INTERVAL_BUCKETS;
244              
245             # Condense the accessors down to the unique values
246             our @all_accessors = uniq (@distinct_bucket_accessors, @interval_bucket_accessors);
247              
248             #===============================================================================#
249              
250             sub new {
251 4     4 1 3414 my $proto = shift;
252 4         15 my %opts = @_;
253            
254 4         112 validate(@_, {
255             profile => { type => SCALAR, optional => 1 }, # Preset profile alias
256             distinct_buckets => { type => ARRAYREF, optional => 1 }, # Custom distinct bucket definitions
257             interval_buckets => { type => ARRAYREF, optional => 1 }, # Custom interval bucket definitions
258             });
259            
260 4   33     43 my $class = ref( $proto ) || $proto;
261            
262 4         9 my $self = {};
263            
264 4         11 $self->{buckets} = {};
265 4         9 $self->{interval_buckets} = {};
266 4         7 $self->{distinct_buckets} = {};
267            
268             # Make sure we either have a preset profile alias, or one of the bucket options set
269 4 100 66     30 if ( $opts{'profile'} ) {
    50          
270 1 50       6 if ( exists $PROFILES{ $opts{'profile'} } ) {
271 1         5 $opts{'distinct_buckets'} = $PROFILES{ $opts{'profile'} }->{distinct_buckets};
272 1         4 $opts{'interval_buckets'} = $PROFILES{ $opts{'profile'} }->{interval_buckets};
273             }
274             else {
275 0         0 confess("Undefined profile: '" . $opts{profile} . "' provided");
276             }
277             }
278             elsif ( ! $opts{'distinct_buckets'} && ! $opts{'interval_buckets'}) {
279 0         0 confess("Must specify either a profile or a custom set of buckets");
280             }
281            
282             # Insert a bucket object into the bucket lists for the specified distinct buckets
283 4         6 foreach my $bucket_name (@{ $opts{'distinct_buckets'} }) {
  4         11  
284 7         56 my $bucket = $DISTINCT_BUCKETS{ $bucket_name }->clone;
285            
286             # Put this bucket in the full bucket list and the distinct bucket list
287 7         21 $self->{buckets}->{ $bucket_name } = $bucket;
288 7         19 $self->{distinct_buckets}->{ $bucket_name } = $bucket;
289             }
290            
291             # Insert a bucket object into the bucket lists for the specified interval buckets
292 4         7 foreach my $bucket_name (@{ $opts{'interval_buckets'} }) {
  4         10  
293 2         8 my $bucket = $INTERVAL_BUCKETS{ $bucket_name }->clone;
294            
295             # Put this bucket in the full bucket list and the interval bucket list
296 2         6 $self->{buckets}->{ $bucket_name } = $bucket;
297 2         8 $self->{interval_buckets}->{ $bucket_name } = $bucket;
298             }
299            
300 4         11 bless($self, $class);
301            
302 4         18 return $self;
303             }
304              
305             # Return a bucket by its name
306             sub bucket {
307 0     0 1 0 my $self = shift;
308 0         0 my $bucket = shift;
309            
310 0         0 validate_pos(@_, { type => SCALAR, optional => 1 });
311            
312 0 0 0     0 if (! defined $self->{buckets}->{ $bucket } || ! $self->{buckets}->{ $bucket }) {
313 0         0 return;
314             }
315            
316 0         0 return $self->{buckets}->{ $bucket };
317             }
318              
319             # Return either the full bucket list or a slice of the buckets according to a list of names
320             # sent in
321             sub buckets {
322 4     4 1 1435 my $self = shift;
323 4         9 my @buckets = @_;
324            
325 4         7 my @to_return = ();
326 4 50       10 if (@buckets) {
327 0         0 @to_return = @{ $self->{buckets} }{ @buckets };
  0         0  
328             }
329             else {
330 4         5 @to_return = values %{$self->{buckets}};
  4         20  
331             }
332            
333 4 50       36 return wantarray ? @to_return : \@to_return;
334             }
335              
336             # Return either the full list of the distinct buckets or a slice of the buckets according to a list of names
337             # sent in
338             sub _distinct_buckets {
339 0     0   0 my $self = shift;
340 0         0 my @buckets = @_;
341            
342 0         0 my @to_return = ();
343 0 0       0 if (@buckets) {
344 0         0 @to_return = @{ $self->{distinct_buckets} }{ @buckets };
  0         0  
345             }
346             else {
347 0         0 @to_return = values %{$self->{distinct_buckets}};
  0         0  
348             }
349            
350 0 0       0 return wantarray ? @to_return : \@to_return;
351             }
352              
353             # Return either the full list of the interval buckets or a slice of the buckets according to a list of names
354             # sent in
355             sub _interval_buckets {
356 0     0   0 my $self = shift;
357 0         0 my @buckets = @_;
358            
359 0         0 my @to_return = ();
360 0 0       0 if (@buckets) {
361 0         0 @to_return = @{ $self->{interval_buckets} }{ @buckets };
  0         0  
362             }
363             else {
364 0         0 @to_return = values %{$self->{interval_buckets}};
  0         0  
365             }
366            
367 0 0       0 return wantarray ? @to_return : \@to_return;
368             }
369              
370             1;
371              
372             package DateTime::Event::Predict::Profile::Bucket;
373              
374 1     1   26 use Params::Validate qw(:all);
  1         3  
  1         362  
375 1     1   12 use Carp qw( croak confess );
  1         3  
  1         1546  
376              
377             sub new {
378 22     22   37 my $proto = shift;
379 22         94 my %opts = @_;
380            
381 22         1235 %opts = validate(@_, {
382             name => { type => SCALAR },
383             type => { type => SCALAR },
384             order => { type => SCALAR },
385             accessor => { type => SCALAR },
386             duration => { type => SCALAR, optional => 1 }, # Interval buckets don't have durations
387             trimmable => { type => SCALAR, optional => 1 },
388             on => { type => SCALAR, default => 1 },
389             });
390            
391 22   33     402 my $class = ref( $proto ) || $proto;
392            
393             #unless (exists $BUCKETS{ $opts{'name'} }) {
394             # confess("Undefined bucket: '" . $opts{'name'} . "' provided");
395             #}
396            
397 22         54 my $self = \%opts;
398            
399             #$self->{bucket} = $BUCKETS{ $opts{'name'} };
400 22         64 $self->{weight} = ""; #Not used yet
401            
402 22         243 bless($self, $class);
403            
404 22         161 return $self;
405             }
406              
407             sub name {
408 9     9   1265 my $self = shift;
409            
410 9         29 return $self->{name};
411             }
412              
413             sub type {
414 0     0   0 my $self = shift;
415            
416 0         0 return $self->{type};
417             }
418              
419             sub accessor {
420 0     0   0 my $self = shift;
421            
422 0         0 return $self->{accessor};
423             }
424              
425             sub order {
426 0     0   0 my $self = shift;
427            
428 0         0 return $self->{order};
429             }
430              
431             sub duration {
432 0     0   0 my $self = shift;
433            
434 0         0 return $self->{duration};
435             }
436              
437             sub trimmable {
438 0     0   0 my $self = shift;
439            
440 0         0 return $self->{trimmable};
441             }
442              
443             sub weight {
444 0     0   0 my $self = shift;
445            
446 0         0 return $self->{weight};
447             }
448              
449             #Get or set whether this bucket is on or not
450             sub on {
451 0     0   0 my $self = shift;
452 0         0 my ($on) = @_;
453            
454 0 0       0 if (defined $on) {
455 0 0       0 $self->{on} = ($on) ? 1 : 0;
456             }
457             else {
458 0 0       0 return ($self->{on}) ? 1 : 0;
459             }
460             }
461              
462             #Reverse of on()
463             sub off {
464 0     0   0 my $self = shift;
465 0         0 my ($off) = @_;
466            
467 0 0       0 if (defined $off) {
468 0 0       0 $self->{on} = ($off) ? 0 : 1;
469             }
470             else {
471 0 0       0 return ($self->{on}) ? 0 : 1;
472             }
473             }
474              
475 9     9   13 sub clone { bless { %{ $_[0] } }, ref $_[0] }
  9         108  
476              
477             1;
478              
479             __END__