File Coverage

blib/lib/Brick/Dates.pm
Criterion Covered Total %
statement 18 100 18.0
branch 0 32 0.0
condition 0 78 0.0
subroutine 6 24 25.0
pod 0 5 0.0
total 24 239 10.0


line stmt bran cond sub pod time code
1             package Brick::Dates;
2 5     5   32 use base qw(Exporter);
  5         10  
  5         572  
3 5     5   28 use vars qw($VERSION);
  5         10  
  5         306  
4              
5             $VERSION = '0.904';
6              
7             package Brick::Bucket;
8 5     5   24 use strict;
  5         8  
  5         118  
9              
10 5     5   21 use subs qw();
  5         11  
  5         131  
11              
12 5     5   30 use Carp qw(carp croak);
  5         9  
  5         327  
13 5     5   2329 use Time::Moment;
  5         8584  
  5         11354  
14              
15             =encoding utf8
16              
17             =head1 NAME
18              
19             Brick - This is the description
20              
21             =head1 SYNOPSIS
22              
23             use Brick;
24              
25             =head1 DESCRIPTION
26              
27              
28             =over 4
29              
30             =item _is_YYYYMMDD_date_format
31              
32             =cut
33              
34             sub _is_YYYYMMDD_date_format {
35 0     0     my( $bucket, $setup ) = @_;
36              
37 0           my @caller = $bucket->__caller_chain_as_list();
38              
39             $bucket->add_to_bucket( {
40             name => $setup->{name} || $caller[0]{'sub'},
41             code => $bucket->_matches_regex( {
42             description => "The $setup->{field} is in the YYYYMMDD date format",
43             field => $setup->{field},
44 0   0       name => $caller[0]{'sub'},
45             regex => qr/
46             \A
47             \d\d\d\d # year
48             \d\d # month
49             \d\d # day
50             \z
51             /x,
52             } )
53             } );
54             }
55              
56             sub _is_valid_date {
57 0     0     my( $bucket, $setup ) = @_;
58              
59 0           my @caller = $bucket->__caller_chain_as_list();
60              
61             $bucket->add_to_bucket( {
62             name => $setup->{name} || $caller[0]{'sub'},
63             code => sub {
64 0     0     my $eval_error = 'Could not parse YYYYMMMDD date';
65 0 0         if( my( $year, $month, $day ) =
66             $_[0]->{$setup->{field}} =~ m/(\d\d\d\d)(\d\d)(\d\d)/g ) {
67 0           $eval_error = '';
68 0           my $tm = eval {
69 0           Time::Moment->new(
70             year => $year,
71             month => $month,
72             day => $day,
73             ) };
74              
75 0 0         return 1 unless $@;
76 0           $eval_error = $@;
77             }
78              
79 0           my $date_error = do {
80 0 0         if( $eval_error =~ /^Parameter 'month'/ )
    0          
81 0           { 'The month is not right' }
82             elsif( $eval_error =~ /^Parameter 'day'/ )
83 0           { 'The day of the month is not right' }
84             else
85 0           { 'Could not parse YYYYMMMDD date' }
86             };
87              
88             die {
89             message => "The value in $setup->{field} [$_[0]->{$setup->{field}}] was not a valid date: $date_error",
90             failed_field => $setup->{field},
91 0 0         handler => $caller[0]{'sub'},
92             } if $eval_error;
93              
94             # 1;
95             },
96 0   0       } );
97              
98             }
99              
100             =item _is_YYYYMMDD_date_format
101              
102             =cut
103              
104             =pod
105              
106             sub _is_in_the_future
107             {
108             my( $bucket, $setup ) = @_;
109             croak "Not implemented";
110             }
111              
112             sub _is_tomorrow
113             {
114             my( $bucket, $setup ) = @_;
115             croak "Not implemented";
116             }
117              
118             sub _is_today
119             {
120             my( $bucket, $setup ) = @_;
121             croak "Not implemented";
122             }
123              
124             sub _is_yesterday
125             {
126             my( $bucket, $setup ) = @_;
127             croak "Not implemented";
128             }
129              
130             sub _is_in_the_past
131             {
132             my( $bucket, $setup ) = @_;
133             croak "Not implemented";
134             }
135              
136             =cut
137              
138             sub _date_is_after {
139 0     0     my( $bucket, $setup ) = @_;
140              
141 0           my @caller = $bucket->__caller_chain_as_list();
142              
143             $bucket->add_to_bucket( {
144             name => $setup->{name} || $caller[0]{'sub'},
145             description => "Date is after the start date",
146             code => sub {
147 0   0 0     my $start = $setup->{start_date} || $_[0]->{$setup->{start_date_field}};
148 0   0       my $in_date = $setup->{input_date} || $_[0]->{$setup->{input_date_field}};
149              
150             #print STDERR "date after: $start --> $in_date\n";
151             die {
152             handler => $setup->{name} || $caller[0]{'sub'},
153             message => "Date [$in_date] is not after start date [$start]",
154             failed_field => $setup->{field},
155 0 0 0       } if $in_date <= $start;
156 0           1;
157             },
158 0   0       } );
159             }
160              
161             sub _date_is_before {
162 0     0     my( $bucket, $setup ) = @_;
163              
164 0           my @caller = $bucket->__caller_chain_as_list();
165              
166             $bucket->add_to_bucket( {
167             name => $setup->{name} || $caller[0]{'sub'},
168             description => "Date is before the end date",
169             code => sub {
170 0   0 0     my $end = $setup->{end_date} || $_[0]->{$setup->{end_date_field}};
171 0   0       my $in_date = $setup->{input_date} || $_[0]->{$setup->{input_date_field}};
172              
173             #print STDERR "date before: $in_date --> $end\n";
174             die {
175             handler => $setup->{name} || $caller[0]{'sub'},
176             message => "Date [$in_date] is not before end date [$end]",
177             failed_field => $setup->{field},
178              
179 0 0 0       } if $end <= $in_date;
180             },
181 0   0       } );
182             }
183              
184             =item date_within_range
185              
186              
187              
188             =cut
189              
190             sub date_within_range { # inclusive, negative numbers indicate past
191 0     0 0   my( $bucket, $setup ) = @_;
192              
193 0           my $before_sub = $bucket->_date_is_before( $setup );
194 0           my $after_sub = $bucket->_date_is_after( $setup );
195              
196 0           my $composed = $bucket->__compose_satisfy_all( $after_sub, $before_sub );
197              
198 0           $bucket->__make_constraint( $composed, $setup );
199             }
200              
201             =item days_between_dates_within_range( HASHREF )
202              
203             I can specify any of the dates as part of the setup by supplying them
204             as the values for these keys in the setup hash:
205              
206             start_date
207             end_date
208             input_date
209              
210             Instead of fixed values, I can tell the function to get values from
211             input fields. Put the field names in the values for these keys of
212             the setup hash"
213              
214             start_date_field
215             end_date_field
216             input_date_field
217              
218             I can use any combination of these setup fields, although the
219             start_date, end_date, and input_date take precedence.
220              
221             TO DO: Need to validate all the date formats before I use them
222             in the comparisons
223              
224             =cut
225              
226             sub days_between_dates_within_range { # inclusive, negative numbers indicate past
227 0     0 0   my( $bucket, $setup ) = @_;
228              
229 0           my @caller = $bucket->__caller_chain_as_list();
230              
231             $bucket->__make_constraint(
232             $bucket->add_to_bucket( {
233             name => $setup->{name} || $caller[0]{'sub'},
234             description => "",
235             code => sub {
236 0   0 0     my $start = $setup->{start_date} || $_[0]->{$setup->{start_date_field}};
237 0   0       my $end = $setup->{end_date} || $_[0]->{$setup->{end_date_field}};
238 0   0       my $in_date = $setup->{input_date} || $_[0]->{$setup->{input_date_field}};
239              
240             die {
241             message => 'Dates were not within range',
242             handler => '',
243             failed_field => $setup->{field},
244 0 0 0       } unless $start <= $in_date && $in_date <= $end;
245             }
246             } )
247 0   0       );
248             }
249              
250             =item days_between_dates_outside_range( HASHREF )
251              
252             I can specify any of the dates as part of the setup by supplying them
253             as the values for these keys in the setup hash:
254              
255             start_date
256             end_date
257             input_date
258              
259             Instead of fixed values, I can tell the function to get values from
260             input fields. Put the field names in the values for these keys of
261             the setup hash"
262              
263             start_date_field
264             end_date_field
265             input_date_field
266              
267             I can use any combination of these setup fields, although the
268             start_date, end_date, and input_date take precedence.
269              
270             TO DO: Need to validate all the date formats before I use them
271             in the comparisons
272              
273             =cut
274              
275             sub days_between_dates_outside_range {
276 0     0 0   my( $bucket, $setup ) = @_;
277              
278 0           my @caller = $bucket->__caller_chain_as_list();
279              
280             $bucket->__make_constraint(
281             $bucket->add_to_bucket( {
282             name => $setup->{name} || $caller[0]{'sub'},
283             description => "",
284             code => sub {
285 0   0 0     my $start = $setup->{start_date} || $_[0]->{$setup->{start_date_field}};
286 0   0       my $end = $setup->{end_date} || $_[0]->{$setup->{end_date_field}};
287 0   0       my $in_date = $setup->{input_date} || $_[0]->{$setup->{input_date_field}};
288              
289             die {
290             message => 'Dates were not outside range',
291             handler => '',
292             failed_field => $setup->{field},
293 0 0 0       } unless $in_date < $start || $end < $in_date;
294             }
295             } )
296 0   0       );
297             }
298              
299             =item at_least_N_days_between
300              
301             =cut
302              
303             sub at_least_N_days_between {
304 0     0 0   my( $bucket, $setup ) = @_;
305              
306 0           my @caller = $bucket->__caller_chain_as_list();
307              
308             $bucket->__make_constraint(
309             $bucket->add_to_bucket( {
310             name => $setup->{name} || $caller[0]{'sub'},
311             description => "Dates within $setup->{number_of_days} days",
312             code => sub {
313 0   0 0     my $start = $setup->{start_date} || $_[0]->{$setup->{start_date_field}};
314 0   0       my $end = $setup->{end_date} || $_[0]->{$setup->{end_date_field}};
315              
316 0 0         print STDERR "Expected interval: $setup->{number_of_days}\n" if $ENV{DEBUG};
317              
318 0           my $interval = $bucket->_get_days_between( $start, $end );
319 0 0         print STDERR "Interval: $start --> $interval --> $end\n" if $ENV{DEBUG};
320              
321             die {
322             message => 'Dates were not within range',
323             handler => 'at_least_N_days_between',
324             failed_field => $setup->{field},
325              
326 0 0         } unless $interval >= $setup->{number_of_days};
327             }
328             } )
329 0   0       );
330             }
331              
332             =item at_most_N_days_between
333              
334             Like C, but the dates cannot be more than N days
335             apart.
336              
337             At the moment this has the curious result that if the end date in before the
338             start date, the duration between them is negative, so that duration is shorter
339             than any positive number. This isn't a bug but a loack of a design decision
340             if I should require the end date to be after the start date.
341              
342             =cut
343              
344             sub at_most_N_days_between {
345 0     0 0   my( $bucket, $setup ) = @_;
346              
347 0           my @caller = $bucket->__caller_chain_as_list();
348              
349             $bucket->__make_constraint(
350             $bucket->add_to_bucket( {
351             name => $setup->{name} || $caller[0]{'sub'},
352             description => "",
353             code => sub {
354 0   0 0     my $start = $setup->{start_date} || $_[0]->{$setup->{start_date_field}};
355 0   0       my $end = $setup->{end_date} || $_[0]->{$setup->{end_date_field}};
356              
357 0           my $interval = $bucket->_get_days_between( $start, $end );
358 0 0         print STDERR "Interval: $start --> $interval --> $end\n" if $ENV{DEBUG};
359              
360             die {
361             message => 'Dates were outside the range',
362             handler => 'at_most_N_days_between',
363             failed_field => $setup->{field},
364              
365 0 0         } unless $setup->{number_of_days} >= $interval;
366             }
367             } )
368 0   0       );
369             }
370              
371             =pod
372              
373             sub at_most_N_days_after
374             {
375             my( $bucket, $setup ) = @_;
376              
377             croak "Not implemented!";
378             }
379              
380             sub at_most_N_days_before
381             {
382             my( $bucket, $setup ) = @_;
383              
384             croak "Not implemented!";
385             }
386              
387             sub before_fixed_date
388             {
389             my( $bucket, $setup ) = @_;
390              
391             croak "Not implemented!";
392             }
393              
394             sub after_fixed_date
395             {
396             my( $bucket, $setup ) = @_;
397              
398             croak "Not implemented!";
399             }
400              
401             =cut
402              
403             # return negative values if second date is earlier than first date
404              
405             =item __get_ymd_as_hashref( YYYYMMDD );
406              
407             Given two dates in YYYYMMDD format, return the number of days between
408             them, including the last date.
409              
410             For the dates 20070101 and 20070103, return 2 because it includes the
411             last day.
412              
413             For the dates 20070101 and 20060101, return -365 because the last date
414             is in the past.
415              
416             =cut
417              
418             sub _get_days_between {
419 0     0     my( $bucket, $start, $stop ) = @_;
420              
421             my @dates =
422 0           map { Time::Moment->new(%{$bucket->__get_ymd_as_hashref($_)}) }
  0            
  0            
423             $start, $stop;
424              
425 0           my $days = $dates[0]->delta_days( $dates[1] );
426             }
427              
428             =item __get_ymd_as_hashref( YYYYMMDD );
429              
430             Given a date in YYYYMMDD format, return an anonymous hash with the
431             keys:
432              
433             year
434             month
435             day
436              
437             =cut
438              
439             sub __get_ymd_as_hashref {
440 0     0     my( $bucket, $date ) = @_;
441              
442 0           my %hash = eval {
443 0 0         die "Could not parse date!"
444             unless $date =~ m/
445             \A
446             (\d\d\d\d)
447             (\d\d)
448             (\d\d)
449             \z
450             /x;
451              
452 0           my $tm = Time::Moment->new( year => $1, month => $2, day => $3 );
453             (
454 0           year => $tm->year,
455             month => $tm->month,
456             day => $tm->day_of_month,
457             );
458             };
459              
460 0 0         if( $@ ) {
461 0           $@ =~ s/\s+at\s+$0.*//s;
462 0           croak( "$@: I got [$date] but was expecting something in YYYYMMDD format!" );
463             }
464              
465 0           \%hash;
466             }
467              
468              
469             =back
470              
471             =head1 TO DO
472              
473             TBA
474              
475             =head1 SEE ALSO
476              
477             TBA
478              
479             =head1 SOURCE AVAILABILITY
480              
481             This source is in Github:
482              
483             https://github.com/briandfoy/brick
484              
485             =head1 AUTHOR
486              
487             brian d foy, C<< >>
488              
489             =head1 COPYRIGHT
490              
491             Copyright © 2007-2025, brian d foy . All rights reserved.
492              
493             You may redistribute this under the terms of the Artistic License 2.0.
494              
495             =cut
496              
497             1;