File Coverage

blib/lib/DateTime/Event/Cron/Quartz.pm
Criterion Covered Total %
statement 502 641 78.3
branch 246 362 67.9
condition 144 243 59.2
subroutine 26 32 81.2
pod 3 23 13.0
total 921 1301 70.7


line stmt bran cond sub pod time code
1             package DateTime::Event::Cron::Quartz;
2              
3 1     1   1058 use strict;
  1         2  
  1         34  
4 1     1   6 use warnings;
  1         2  
  1         35  
5              
6 1     1   5 use vars qw($VERSION);
  1         11  
  1         56  
7              
8             $VERSION = '0.05';
9              
10 1     1   7 use base qw/Class::Accessor/;
  1         1  
  1         944  
11              
12 1     1   2020 use DateTime;
  1         2  
  1         22  
13 1     1   5 use Readonly;
  1         2  
  1         76  
14              
15             use Exception::Class (
16 1         13 'UnknownException',
17             'ParseException' => { fields => ['line'] },
18             'IllegalArgumentException' => {},
19             'UnsupportedOperationException' => {}
20 1     1   892 );
  1         9808  
21              
22 1     1   2001 use DateTime::Event::Cron::Quartz::TreeSet;
  1         3  
  1         39  
23 1     1   797 use DateTime::Event::Cron::Quartz::ValueSet;
  1         2  
  1         8  
24              
25             Readonly my $MONDAY => 0;
26             Readonly my $TUESDAY => 1;
27             Readonly my $WEDNESDAY => 2;
28             Readonly my $THURSDAY => 3;
29             Readonly my $FRIDAY => 4;
30             Readonly my $SATURDAY => 5;
31             Readonly my $SUNDAY => 6;
32              
33             Readonly::Scalar my $SECOND => 0;
34             Readonly::Scalar my $MINUTE => 1;
35             Readonly::Scalar my $HOUR => 2;
36             Readonly::Scalar my $DAY_OF_MONTH => 3;
37             Readonly::Scalar my $MONTH => 4;
38             Readonly::Scalar my $DAY_OF_WEEK => 5;
39             Readonly::Scalar my $YEAR => 6;
40             Readonly::Scalar my $ALL_SPEC_INT => 99;
41             Readonly::Scalar my $NO_SPEC_INT => 98;
42             Readonly::Scalar my $ALL_SPEC => $ALL_SPEC_INT;
43             Readonly::Scalar my $NO_SPEC => $NO_SPEC_INT;
44              
45             Readonly::Scalar my $MONTH_MAP => {
46             JAN => 1,
47             FEB => 2,
48             MAR => 3,
49             APR => 4,
50             MAY => 5,
51             JUN => 6,
52             JUL => 7,
53             AUG => 8,
54             SEP => 9,
55             OCT => 10,
56             NOV => 11,
57             DEC => 12
58             };
59              
60             Readonly::Scalar my $DAY_MAP => {
61             MON => 1,
62             TUE => 2,
63             WED => 3,
64             THU => 4,
65             FRI => 5,
66             SAT => 6,
67             SUN => 7
68             };
69              
70             __PACKAGE__->mk_accessors(
71             qw/
72             cron_expression
73             time_zone
74             seconds
75             minutes
76             hours
77             days_of_month
78             months
79             days_of_week
80             years
81              
82             lastday_of_week
83             nthday_of_week
84             lastday_of_month
85             nearest_weekday
86             expression_parsed
87             /
88             );
89              
90             sub new {
91 50     50 1 43928 my ( $class, $cron_expression ) = @_;
92              
93 50 50       183 if ( !defined $cron_expression ) {
94 0         0 IllegalArgumentException->throw(
95             error => 'cron expression cannot be undef' );
96             }
97              
98 50         201 my $this = bless {}, $class;
99              
100             # initialize fields values
101 50         683 $this->lastday_of_week(0);
102 50         818 $this->nthday_of_week(0);
103 50         552 $this->lastday_of_month(0);
104 50         494 $this->nearest_weekday(0);
105 50         579 $this->expression_parsed(0);
106              
107 50         547 $this->cron_expression( uc $cron_expression );
108              
109 50         482 $this->build_expression( $this->cron_expression );
110              
111 38         180 return $this;
112             }
113              
114             sub is_satisfied_by {
115 0     0 0 0 my ( $this, $date ) = @_;
116              
117 0         0 my $time_after = $this->get_time_after($date);
118              
119 0   0     0 return ( ( defined $time_after )
120             && ( DateTime->compare( $time_after, $date ) == 0 ) );
121             }
122              
123             sub get_next_valid_time_after {
124 97     97 1 117531 my ( $this, $date ) = @_;
125              
126 97         530 return $this->get_time_after($date);
127             }
128              
129             sub get_next_invalid_time_after {
130 0     0 0 0 my ( $this, $last_date ) = @_;
131              
132 0         0 my $difference = 1;
133              
134 0         0 my $new_date = undef;
135              
136             # keep getting the next included time until it's farther than one second
137             # apart. At that point, lastDate is the last valid fire time. We return
138             # the second immediately following it.
139 0         0 while ( $difference == 1 ) {
140 0         0 $new_date = $this->get_time_after($last_date);
141              
142 0         0 $difference = $new_date->subtract_datetime_absolute($last_date);
143              
144 0 0       0 if ( $difference == 1 ) {
145 0         0 $last_date = $new_date;
146             }
147             }
148              
149 0         0 return $last_date->clone()->add( second => 1 );
150             }
151              
152             sub is_valid_expression {
153 12     12 1 7554 my ( $this, $cron_expression ) = @_;
154              
155 12         25 my $res = eval { $this->new($cron_expression); };
  12         274  
156              
157 12 50       350 if ( my $e = Exception::Class->caught('ParseException') ) {
158 12         196 return 0;
159             }
160              
161 0         0 return 1;
162             }
163              
164             #//////////////////////////////////////////////////////////////////////////
165             #
166             # Expression Parsing Functions
167             #
168             #//////////////////////////////////////////////////////////////////////////
169              
170             sub build_expression {
171 50     50 0 551 my ( $this, $expression ) = @_;
172              
173 50         77 my $expression_parsed = 1;
174              
175 50         106 my $ret = eval {
176              
177 50 50       207 if ( !defined $this->seconds ) {
178 50         652 $this->seconds( DateTime::Event::Cron::Quartz::TreeSet->new );
179             }
180 50 50       551 if ( !defined $this->minutes ) {
181 50         572 $this->minutes( DateTime::Event::Cron::Quartz::TreeSet->new );
182             }
183 50 50       659 if ( !defined $this->hours ) {
184 50         565 $this->hours( DateTime::Event::Cron::Quartz::TreeSet->new );
185             }
186 50 50       518 if ( !defined $this->days_of_month ) {
187 50         548 $this->days_of_month( DateTime::Event::Cron::Quartz::TreeSet->new );
188             }
189 50 50       528 if ( !defined $this->months ) {
190 50         563 $this->months( DateTime::Event::Cron::Quartz::TreeSet->new );
191             }
192 50 50       505 if ( !defined $this->days_of_week ) {
193 50         572 $this->days_of_week( DateTime::Event::Cron::Quartz::TreeSet->new );
194             }
195 50 50       668 if ( !defined $this->years ) {
196 50         557 $this->years( DateTime::Event::Cron::Quartz::TreeSet->new );
197             }
198              
199 50         533 my $expr_on = $SECOND;
200              
201 50         280 my @exprs_tok = split /\s+/sxm, $expression;
202              
203 50         314 foreach my $expr (@exprs_tok) {
204              
205             # not interested in after expression text
206 264 50       796 last if $expr_on > $YEAR;
207              
208             # throw an exception if L is used with other days of the month
209 264 50 100     905 if ( $expr_on == $DAY_OF_MONTH
      100        
      66        
210             && index( $expr, 'L' ) != -1
211             && length($expr) > 1
212             && index( $expr, q/,/ ) >= 0 )
213             {
214              
215 0         0 ParseException->throw(
216             error =>
217             q/Support for specifying 'L' and 'LW' with other days of the month is not implemented/,
218             line => -1
219             );
220             }
221              
222             # throw an exception if L is used with other days of the week
223 264 50 100     1128 if ( $expr_on == $DAY_OF_WEEK
      66        
      66        
224             && index( $expr, 'L' ) != -1
225             && length($expr) > 1
226             && index( $expr, q/,/ ) >= 0 )
227             {
228              
229 0         0 ParseException->throw(
230             error =>
231             q/Support for specifying 'L' with other days of the week is not implemented/,
232             line => -1
233             );
234             }
235              
236 264         886 my @v_tok = split /,/sxm, $expr;
237 264         415 foreach my $v (@v_tok) {
238 291         720 $this->store_expression_vals( 0, $v, $expr_on );
239             }
240              
241 254         810 $expr_on++;
242             }
243              
244 40 100       121 if ( $expr_on <= $DAY_OF_WEEK ) {
245 2         9 ParseException->throw(
246             error => q/Unexpected end of expression/,
247             line => ( length $expression )
248             );
249             }
250              
251 38 100       94 if ( $expr_on <= $YEAR ) {
252 37         86 $this->store_expression_vals( 0, q/*/, $YEAR );
253             }
254              
255             #TreeSet
256 38         165 my $dow = $this->get_set($DAY_OF_WEEK);
257              
258             #TreeSet
259 38         595 my $dom = $this->get_set($DAY_OF_MONTH);
260              
261             # Copying the logic from the UnsupportedOperationException below
262 38         425 my $day_of_m_spec = !$dom->contains($NO_SPEC);
263 38         139 my $day_of_w_spec = !$dow->contains($NO_SPEC);
264              
265 38 100 66     458 if ( $day_of_m_spec && !$day_of_w_spec ) {
    50 33        
266              
267             # skip
268             }
269             elsif ( $day_of_w_spec && !$day_of_m_spec ) {
270              
271             # skip
272             }
273             else {
274 0         0 ParseException->throw(
275             error => q/Support for specifying both a day-of-week /
276             . q/AND a day-of-month parameter is not implemented./,
277             line => 0
278             );
279             }
280             };
281              
282 50 100       7393 if ( my $pe = Exception::Class->caught('ParseException') ) {
    50          
283 12         208 $pe->rethrow;
284             }
285             elsif ( my $e = Exception::Class->caught() ) {
286 0         0 ParseException->throw(
287             error => q/Illegal cron expression format (/ . $e->error . q/)/,
288             line => 0
289             );
290             }
291              
292 38         769 return;
293             }
294              
295             sub store_expression_vals {
296 328     328 0 533 my ( $this, $pos, $s, $type ) = @_;
297              
298 328         382 my $incr = 0;
299 328         701 my $i = $this->skip_white_space( $pos, $s );
300 328 50       771 if ( $i >= ( length $s ) ) {
301 0         0 return $i;
302             }
303              
304 328         846 my $c = ( substr $s, $i, 1 );
305 328 100 66     1455 if ( ( ( ord $c ) >= ( ord 'A' ) )
      100        
      100        
306             && ( ( ord $c ) <= ( ord 'Z' ) )
307             && ( !( $s eq 'L' ) )
308             && ( !( $s eq 'LW' ) ) )
309             {
310 7         152 my $sub = ( substr $s, $i, $i + 3 );
311 7         11 my $sval = -1;
312 7         8 my $eval = -1;
313 7 100       24 if ( $type == $MONTH ) {
    100          
314 2         6 $sval = $this->get_month_number($sub);
315 2 50       6 if ( $sval <= 0 ) {
316 0         0 ParseException->throw(
317             error => q/Invalid Month value: '/ . $sub . q/'/,
318             line => $i
319             );
320             }
321 2 50       6 if ( ( length $s ) > $i + 3 ) {
322 0         0 $c = ( substr $s, $i + 3, 1 );
323 0 0       0 if ( $c eq q/-/ ) {
324 0         0 $i += ( 3 + 1 );
325 0         0 $sub = ( substr $s, $i, $i + 3 );
326 0         0 $eval = $this->get_month_number($sub);
327 0 0       0 if ( $eval <= 0 ) {
328 0         0 ParseException->throw(
329             error => q/Invalid Month value: '/ . $sub . q/'/,
330             line => $i
331             );
332             }
333             }
334             }
335             }
336             elsif ( $type == $DAY_OF_WEEK ) {
337 3         11 $sval = $this->get_day_of_week_number($sub);
338 3 50       9 if ( $sval < 0 ) {
339 0         0 ParseException->throw(
340             error => q/Invalid Day-of-Week value: '/ . $sub . q/'/,
341             line => $i
342             );
343             }
344 3 50       10 if ( length $s > $i + 3 ) {
345 0         0 $c = substr $s, $i + 3, 1;
346 0 0       0 if ( $c eq q/-/ ) {
    0          
    0          
347 0         0 $i += ( 3 + 1 );
348 0         0 $sub = ( substr $s, $i, $i + 3 );
349 0         0 $eval = $this->get_day_of_week_number($sub);
350 0 0       0 if ( $eval < 0 ) {
351 0         0 ParseException->throw(
352             error => q/Invalid Day-of-Week value: '/
353             . $sub . q/'/,
354             line => $i
355             );
356             }
357             }
358             elsif ( $c eq q/#/ ) {
359 0         0 my $ret = eval {
360 0         0 $i += ( 3 + 1 );
361 0         0 $this->nthday_of_week( int substr $s, $i );
362 0 0 0     0 if ( $this->nthday_of_week < 1
363             || $this->nthday_of_week > 5 )
364             {
365 0         0 Exception::Class->throw();
366             }
367             };
368              
369 0 0       0 if ( my $e = Exception::Class->caught() ) {
370 0         0 ParseException->throw(
371             error =>
372             q/A numeric value between 1 and 5 must follow the '#' option/,
373             line => $i
374             );
375             }
376             }
377             elsif ( $c == 'L' ) {
378 0         0 $this->lastday_of_week(1);
379 0         0 $i++;
380             }
381             }
382              
383             }
384             else {
385 2         14 ParseException->throw(
386             error => q/Illegal characters for this position: '/
387             . $sub . q/'/,
388             line => $i
389             );
390             }
391 5 50       12 if ( $eval != -1 ) {
392 0         0 $incr = 1;
393             }
394 5         14 $this->add_to_set( $sval, $eval, $incr, $type );
395 5         13 return ( $i + 3 );
396             }
397              
398 321 100       691 if ( $c eq '?' ) {
399 39         59 $i++;
400 39 0 0     144 if ( ( $i + 1 ) < length($s)
      33        
401             && ( substr( $s, $i, 1 ) ne ' ' && substr( $s, $i + 1, 1 ) ne "\t" )
402             )
403             {
404 0         0 ParseException->throw(
405             error => q/Illegal character after '?': / . substr( $s, $i, 1 ),
406             line => $i
407             );
408             }
409 39 50 66     165 if ( $type != $DAY_OF_WEEK && $type != $DAY_OF_MONTH ) {
410 0         0 ParseException->throw(
411             error =>
412             q/'?' can only be specfied for Day-of-Month or Day-of-Week./,
413             line => $i
414             );
415             }
416 39 100 100     160 if ( $type == $DAY_OF_WEEK && !$this->lastday_of_month ) {
417 19         253 my $val = int( $this->days_of_month->last_item() );
418 19 50       63 if ( $val == $NO_SPEC_INT ) {
419 0         0 ParseException->throw(
420             error =>
421             q/'?' can only be specfied for Day-of-Month -OR- Day-of-Week./,
422             line => $i
423             );
424             }
425             }
426              
427 39         137 $this->add_to_set( $NO_SPEC_INT, -1, 0, $type );
428 39         121 return $i;
429             }
430              
431 282 100 66     1838 if ( $c eq '*' || $c eq '/' ) {
    100 66        
    100          
432 111 100 66     488 if ( $c eq '*' && ( $i + 1 ) >= length($s) ) {
    50 0        
    50 33        
433 110         619 $this->add_to_set( $ALL_SPEC_INT, -1, $incr, $type );
434 110         362 return $i + 1;
435             }
436             elsif (
437             $c eq '/'
438             && ( ( $i + 1 ) >= length($s)
439             || substr( $s, $i + 1, 1 ) eq ' '
440             || substr( $s, $i + 1, 1 ) eq '\t' )
441             )
442             {
443 0         0 ParseException->throw(
444             error => q/'\/' must be followed by an integer./,
445             line => $i
446             );
447             }
448             elsif ( $c eq '*' ) {
449 1         3 $i++;
450             }
451 1         3 $c = substr( $s, $i, 1 );
452 1 50       5 if ( $c eq '/' ) { # is an increment specified?
453 1         2 $i++;
454 1 50       4 if ( $i >= length($s) ) {
455 0         0 ParseException->throw(
456             error => q/Unexpected end of string./,
457             line => $i
458             );
459             }
460              
461 1         6 $incr = $this->get_numeric_value( $s, $i );
462              
463 1         3 $i++;
464 1 50       4 if ( $incr > 10 ) {
465 1         2 $i++;
466             }
467 1 50 0     50 if ( $incr > 59 && ( $type == $SECOND || $type == $MINUTE ) ) {
    50 33        
    50 33        
    50 33        
    50 33        
      33        
468 0         0 ParseException->throw(
469             error => 'Increment > 60 : ' . $incr,
470             line => $i
471             );
472             }
473             elsif ( $incr > 23 && ( $type == $HOUR ) ) {
474 0         0 ParseException->throw(
475             error => 'Increment > 24 : ' . $incr,
476             line => $i
477             );
478             }
479             elsif ( $incr > 31 && ( $type == $DAY_OF_MONTH ) ) {
480 0         0 ParseException->throw(
481             error => 'Increment > 31 : ' . $incr,
482             line => $i
483             );
484             }
485             elsif ( $incr > 7 && ( $type == $DAY_OF_WEEK ) ) {
486 0         0 ParseException->throw(
487             error => 'Increment > 7 : ' . $incr,
488             line => $i
489             );
490             }
491             elsif ( $incr > 12 && ( $type == $MONTH ) ) {
492 0         0 ParseException->throw(
493             error => 'Increment > 12 : ' . $incr,
494             line => $i
495             );
496             }
497             }
498             else {
499 0         0 $incr = 1;
500             }
501              
502 1         6 $this->add_to_set( $ALL_SPEC_INT, -1, $incr, $type );
503 1         4 return $i;
504             }
505             elsif ( $c eq 'L' ) {
506 2         4 $i++;
507 2 50       6 if ( $type == $DAY_OF_MONTH ) {
508 2         7 $this->lastday_of_month(1);
509             }
510 2 50       21 if ( $type == $DAY_OF_WEEK ) {
511 0         0 $this->add_to_set( 7, 7, 0, $type );
512             }
513 2 100 66     11 if ( $type == $DAY_OF_MONTH && length($s) > $i ) {
514 1         2 $c = substr( $s, $i, 1 );
515 1 50       3 if ( $c eq 'W' ) {
516 1         4 $this->nearest_weekday(1);
517 1         7 $i++;
518             }
519             }
520 2         6 return $i;
521             }
522             elsif ( ord($c) >= ord('0') && ord($c) <= ord('9') ) {
523 168         286 my $val = int($c);
524 168         182 $i++;
525 168 100       316 if ( $i >= length($s) ) {
526 99         329 $this->add_to_set( $val, -1, -1, $type );
527             }
528             else {
529 69         9266 $c = substr( $s, $i, 1 );
530 69 100 100     474 if ( ord($c) >= ord('0') && ord($c) <= ord('9') ) {
531              
532             # ValueSet ??
533 64         211 my $vs = $this->get_value( $val, $s, $i );
534 64         165 $val = $vs->value;
535 64         629 $i = $vs->pos;
536             }
537 69         811 $i = $this->check_next( $i, $s, $val, $type );
538 64         192 return $i;
539             }
540             }
541             else {
542 1         20 ParseException->throw(
543             error => "Unexpected character: " . $c,
544             line => $i
545             );
546             }
547              
548 97         251 return $i;
549             }
550              
551             sub check_next {
552 69     69 0 104 my $this = shift;
553              
554 69         141 my ( $pos, $s, $val, $type ) = @_;
555              
556 69         102 my $end = -1;
557 69         97 my $i = $pos;
558              
559 69 100       167 if ( $i >= length($s) ) {
560 62         147 $this->add_to_set( $val, $end, -1, $type );
561 57         132 return $i;
562             }
563              
564 7         19 my $c = substr( $s, $pos, 1 );
565              
566 7 100       23 if ( $c eq 'L' ) {
567 2 50       9 if ( $type == $DAY_OF_WEEK ) {
568 2         7 $this->lastday_of_week(1);
569             }
570             else {
571 0         0 ParseException->throw(
572             "'L' option is not valid here. (pos=" . $i . ")", $i );
573             }
574              
575             # TreeSet
576 2         23 my $set = $this->get_set($type);
577 2         24 $set->add( int($val) );
578 2         99 $i++;
579 2         6 return $i;
580             }
581              
582 5 100       22 if ( $c eq 'W' ) {
583 1 50       5 if ( $type == $DAY_OF_MONTH ) {
584 1         15 $this->nearest_weekday(1);
585             }
586             else {
587 0         0 ParseException->throw(
588             "'W' option is not valid here. (pos=" . $i . ")", $i );
589             }
590              
591             # TreeSet
592 1         13 my $set = $this->get_set($type);
593 1         13 $set->add( int($val) );
594 1         3 $i++;
595 1         3 return $i;
596             }
597              
598 4 100       13 if ( $c eq '#' ) {
599 1 50       5 if ( $type != $DAY_OF_WEEK ) {
600 0         0 ParseException->throw(
601             error => "'#' option is not valid here. (pos=" . $i . ")",
602             line => $i
603             );
604             }
605 1         1 $i++;
606 1         4 eval {
607 1         5 $this->nthday_of_week( int( substr( $s, $i ) ) );
608 1 50 33     12 if ( $this->nthday_of_week < 1 || $this->nthday_of_week > 5 ) {
609 0         0 Exception::Class->throw();
610             }
611             };
612              
613 1 50       29 if ( my $e = Exception::Class->caught() ) {
614 0         0 ParseException->throw(
615             error =>
616             "A numeric value between 1 and 5 must follow the '#' option",
617             line => $i
618             );
619             }
620              
621             # TreeSet
622 1         10 my $set = $this->get_set($type);
623 1         12 $set->add( int($val) );
624 1         1 $i++;
625 1         4 return $i;
626             }
627              
628 3 50       11 if ( $c eq '-' ) {
629 3         4 $i++;
630 3         7 $c = substr( $s, $i, 1 );
631 3         6 my $v = int($c);
632 3         4 $end = $v;
633 3         5 $i++;
634 3 100       27 if ( $i >= length($s) ) {
635 2         7 $this->add_to_set( $val, $end, 1, $type );
636 2         6 return $i;
637             }
638 1         3 $c = substr( $s, $i, 1 );
639 1 50 33     8 if ( $c >= '0' && $c <= '9' ) {
640              
641             # ValueSet
642 1         3 my $vs = $this->get_value( $v, $s, $i );
643 1         4 my $v1 = $vs->value;
644 1         9 $end = $v1;
645 1         4 $i = $vs->pos;
646             }
647 1 50 33     13 if ( $i < length($s) && ( ( $c = substr( $s, $i, 1 ) ) eq '/' ) ) {
648 0         0 $i++;
649 0         0 $c = substr( $s, $i, 1 );
650 0         0 my $v2 = int($c);
651 0         0 $i++;
652 0 0       0 if ( $i >= length($s) ) {
653 0         0 $this->add_to_set( $val, $end, $v2, $type );
654 0         0 return $i;
655             }
656 0         0 $c = substr( $s, $i, 1 );
657 0 0 0     0 if ( $c >= '0' && $c <= '9' ) {
658              
659             # ValueSet
660 0         0 my $vs = $this->get_value( $v2, $s, $i );
661 0         0 my $v3 = $vs->value;
662 0         0 $this->add_to_set( $val, $end, $v3, $type );
663 0         0 $i = $vs->pos;
664 0         0 return $i;
665             }
666             else {
667 0         0 $this->add_to_set( $val, $end, $v2, $type );
668 0         0 return $i;
669             }
670             }
671             else {
672 1         4 $this->add_to_set( $val, $end, 1, $type );
673 1         2 return $i;
674             }
675             }
676              
677 0 0       0 if ( $c eq '/' ) {
678 0         0 $i++;
679 0         0 $c = substr( $s, $i, 1 );
680 0         0 my $v2 = int($c);
681 0         0 $i++;
682 0 0       0 if ( $i >= length($s) ) {
683 0         0 $this->add_to_set( $val, $end, $v2, $type );
684 0         0 return $i;
685             }
686 0         0 $c = substr( $s, $i, 1 );
687 0 0 0     0 if ( $c >= '0' && $c <= '9' ) {
688              
689             # ValueSet
690 0         0 my $vs = $this->get_value( $v2, $s, $i );
691 0         0 my $v3 = $vs->value;
692 0         0 $this->add_to_set( $val, $end, $v3, $type );
693 0         0 $i = $vs->pos;
694 0         0 return $i;
695             }
696             else {
697 0         0 ParseException->throw(
698             error => "Unexpected character '" . $c . "' after '/'", line => $i );
699             }
700             }
701              
702 0         0 $this->add_to_set( $val, $end, 0, $type );
703 0         0 $i++;
704 0         0 return $i;
705             }
706              
707             sub get_cron_expression {
708 0     0 0 0 my $this = shift;
709              
710 0         0 return $this->cron_expression;
711             }
712              
713             sub skip_white_space {
714 328     328 0 411 my $this = shift;
715              
716 328         442 my ( $i, $s ) = @_;
717              
718 328   33     2764 for (
      33        
719             ;
720             $i < length($s)
721             && ( substr( $s, $i, 1 ) eq ' ' || substr( $s, $i, 1 ) eq '\t' ) ;
722             $i++
723             )
724             {
725             ;
726             }
727              
728 328         623 return $i;
729             }
730              
731             sub find_next_white_space {
732 1     1 0 2 my $this = shift;
733              
734 1         3 my ( $i, $s ) = @_;
735              
736 1   33     14 for (
      66        
737             ;
738             $i < length($s)
739             && ( substr( $s, $i, 1 ) ne ' ' || substr( $s, $i, 1 ) ne '\t' ) ;
740             $i++
741             )
742             {
743             ;
744             }
745              
746 1         2 return $i;
747             }
748              
749             sub add_to_set {
750 319     319 0 431 my $this = shift;
751              
752 319         425 my ( $val, $end, $incr, $type ) = @_;
753              
754             #TreeSet
755 319         674 my $set = $this->get_set($type);
756              
757 319 100 100     6053 if ( $type == $SECOND || $type == $MINUTE ) {
    100          
    100          
    100          
    100          
758 93 100 66     700 if ( ( $val < 0 || $val > 59 || $end > 59 )
      100        
759             && ( $val != $ALL_SPEC_INT ) )
760             {
761 1         5 ParseException->throw(
762             error => "Minute and Second values must be between 0 and 59",
763             line => -1
764             );
765             }
766             }
767             elsif ( $type == $HOUR ) {
768 53 100 66     441 if ( ( $val < 0 || $val > 23 || $end > 23 )
      100        
769             && ( $val != $ALL_SPEC_INT ) )
770             {
771 1         7 ParseException->throw(
772             error => "Hour values must be between 0 and 23",
773             line => -1
774             );
775             }
776             }
777             elsif ( $type == $DAY_OF_MONTH ) {
778 44 100 66     484 if ( ( $val < 1 || $val > 31 || $end > 31 )
      100        
      100        
779             && ( $val != $ALL_SPEC_INT )
780             && ( $val != $NO_SPEC_INT ) )
781             {
782 2         10 ParseException->throw(
783             error => "Day of month values must be between 1 and 31",
784             line => -1
785             );
786             }
787             }
788             elsif ( $type == $MONTH ) {
789 51 100 66     570 if ( ( $val < 1 || $val > 12 || $end > 12 )
      100        
790             && ( $val != $ALL_SPEC_INT ) )
791             {
792 2         14 ParseException->throw(
793             error => "Month values must be between 1 and 12",
794             line => -1
795             );
796             }
797             }
798             elsif ( $type == $DAY_OF_WEEK ) {
799 40 100 66     417 if ( ( $val == 0 || $val > 7 || $end > 7 )
      100        
      100        
800             && ( $val != $ALL_SPEC_INT )
801             && ( $val != $NO_SPEC_INT ) )
802             {
803 1         7 ParseException->throw(
804             error => "Day-of-Week values must be between 1 and 7",
805             line => -1
806             );
807             }
808             }
809              
810 312 100 100     12803 if ( ( $incr == 0 || $incr == -1 ) && $val != $ALL_SPEC_INT ) {
      100        
811             {
812 198 50       206 if ( $val != -1 ) {
  198         336  
813 198         602 $set->add($val);
814             }
815             else {
816 0         0 $set->add($NO_SPEC);
817             }
818             }
819              
820 198         362 return;
821             }
822              
823 114         187 my $start_at = $val;
824 114         143 my $stop_at = $end;
825              
826 114 100 100     495 if ( $val == $ALL_SPEC_INT && $incr <= 0 ) {
827 110         171 $incr = 1;
828 110         321 $set->add($ALL_SPEC); # put in a marker, but also fill values
829             }
830              
831 114 100 100     762 if ( $type == $SECOND || $type == $MINUTE ) {
    100          
    100          
    100          
    100          
    50          
832 18 100       52 if ( $stop_at == -1 ) {
833 17         24 $stop_at = 59;
834             }
835 18 100 66     112 if ( $start_at == -1 || $start_at == $ALL_SPEC_INT ) {
836 17         24 $start_at = 0;
837             }
838             }
839             elsif ( $type == $HOUR ) {
840 10 50       34 if ( $stop_at == -1 ) {
841 10         13 $stop_at = 23;
842             }
843 10 50 33     66 if ( $start_at == -1 || $start_at == $ALL_SPEC_INT ) {
844 10         17 $start_at = 0;
845             }
846             }
847             elsif ( $type == $DAY_OF_MONTH ) {
848 9 50       38 if ( $stop_at == -1 ) {
849 9         16 $stop_at = 31;
850             }
851 9 50 33     68 if ( $start_at == -1 || $start_at == $ALL_SPEC_INT ) {
852 9         18 $start_at = 1;
853             }
854             }
855             elsif ( $type == $MONTH ) {
856 32 50       91 if ( $stop_at == -1 ) {
857 32         56 $stop_at = 12;
858             }
859 32 50 33     303 if ( $start_at == -1 || $start_at == $ALL_SPEC_INT ) {
860 32         54 $start_at = 1;
861             }
862             }
863             elsif ( $type == $DAY_OF_WEEK ) {
864 7 100       24 if ( $stop_at == -1 ) {
865 6         11 $stop_at = 7;
866             }
867 7 100 66     60 if ( $start_at == -1 || $start_at == $ALL_SPEC_INT ) {
868 6         18 $start_at = 1;
869             }
870             }
871             elsif ( $type == $YEAR ) {
872 38 100       93 if ( $stop_at == -1 ) {
873 37         40 $stop_at = 2099;
874             }
875 38 100 66     262 if ( $start_at == -1 || $start_at == $ALL_SPEC_INT ) {
876 37         54 $start_at = 1970;
877             }
878             }
879              
880             # if the end of the range is before the start, then we need to overflow into
881             # the next day, month etc. This is done by adding the maximum amount for that
882             # type, and using modulus max to determine the value being added.
883 114         146 my $max = -1;
884 114 50       392 if ( $stop_at < $start_at ) {
885 0 0       0 if ( $type == $SECOND ) {
    0          
    0          
    0          
    0          
    0          
    0          
886 0         0 $max = 60;
887             }
888             elsif ( $type == $MINUTE ) {
889 0         0 $max = 60;
890             }
891             elsif ( $type == $HOUR ) {
892 0         0 $max = 24;
893             }
894             elsif ( $type == $MONTH ) {
895 0         0 $max = 12;
896             }
897             elsif ( $type == $DAY_OF_WEEK ) {
898 0         0 $max = 7;
899             }
900             elsif ( $type == $DAY_OF_MONTH ) {
901 0         0 $max = 31;
902             }
903             elsif ( $type == $YEAR ) {
904 0         0 IllegalArgumentException->throw(
905             error => "Start year must be less than stop year" );
906             }
907             else {
908 0         0 IllegalArgumentException->throw(
909             error => "Unexpected type encountered" );
910             }
911              
912 0         0 $stop_at += $max;
913             }
914              
915 114         296 for ( my $i = $start_at ; $i <= $stop_at ; $i += $incr ) {
916 6724 50       13683 if ( $max == -1 ) {
917              
918             # ie: there's no max to overflow over
919 6724         22445 $set->add( int($i) );
920             }
921             else {
922              
923             # take the modulus to get the real value
924 0         0 my $i2 = $i % $max;
925              
926             # 1-indexed ranges should not include 0, and should include their max
927 0 0 0     0 if (
      0        
928             $i2 == 0
929             && ( $type == $MONTH
930             || $type == $DAY_OF_WEEK
931             || $type == $DAY_OF_MONTH )
932             )
933             {
934 0         0 $i2 = $max;
935             }
936              
937 0         0 $set->add( int($i2) );
938             }
939             }
940             }
941              
942             sub get_set {
943 399     399 0 439 my $this = shift;
944              
945 399         696 my $type = shift;
946              
947 399 100       1623 if ( $type == $SECOND ) {
    100          
    100          
    100          
    100          
    100          
    50          
948 46         162 return $this->seconds;
949             }
950             elsif ( $type == $MINUTE ) {
951 47         145 return $this->minutes;
952             }
953             elsif ( $type == $HOUR ) {
954 53         181 return $this->hours;
955             }
956             elsif ( $type == $MONTH ) {
957 51         155 return $this->months;
958             }
959             elsif ( $type == $DAY_OF_MONTH ) {
960 83         289 return $this->days_of_month;
961             }
962             elsif ( $type == $DAY_OF_WEEK ) {
963 81         323 return $this->days_of_week;
964             }
965             elsif ( $type == $YEAR ) {
966 38         115 return $this->years;
967             }
968             else {
969 0         0 return undef;
970             }
971             }
972              
973             sub get_value {
974 65     65 0 86 my $this = shift;
975              
976 65         207 my ( $v, $s, $i ) = @_;
977              
978 65         103 my $c = substr( $s, $i, 1 );
979 65         93 my $s1 = "$v";
980              
981 65   100     300 while ( ord($c) >= ord('0') && ord($c) <= ord('9') ) {
982 69         83 $s1 .= $c;
983 69         79 $i++;
984 69 100       207 if ( $i >= length($s) ) {
985 63         83 last;
986             }
987 6         26 $c = substr( $s, $i, 1 );
988             }
989              
990             # ValueSet
991 65         343 my $val = DateTime::Event::Cron::Quartz::ValueSet->new;
992              
993 65 100       361 $val->pos( ( $i < length($s) ) ? $i : $i + 1 );
994 65         838 $val->value( int($s1) );
995 65         708 return $val;
996             }
997              
998             sub get_numeric_value {
999 1     1 0 2 my $this = shift;
1000              
1001 1         4 my ( $s, $i ) = @_;
1002              
1003 1         4 my $end_of_val = $this->find_next_white_space( $i, $s );
1004 1         4 my $val = substr( $s, $i, $end_of_val );
1005              
1006 1 50       28 if ( !( $val =~ /^\d+$/ ) ) {
1007 0         0 ParseException->throw(
1008             error => "value is not numeric: " . $val,
1009             line => $i
1010             );
1011             }
1012              
1013 1         5 return int($val);
1014             }
1015              
1016             sub get_month_number {
1017 2     2 0 4 my $this = shift;
1018              
1019 2         2 my $s = shift;
1020              
1021 2         15 my $integer = $MONTH_MAP->{$s};
1022              
1023 2 50       25 if ( !defined $integer ) {
1024 0         0 return -1;
1025             }
1026              
1027 2         6 return $integer;
1028             }
1029              
1030             sub get_day_of_week_number {
1031 3     3 0 5 my $this = shift;
1032              
1033 3         4 my $s = shift;
1034              
1035 3         21 my $integer = $DAY_MAP->{$s};
1036              
1037 3 50       24 if ( !defined $integer ) {
1038 0         0 return -1;
1039             }
1040              
1041 3         8 return $integer;
1042             }
1043              
1044             #//////////////////////////////////////////////////////////////////////////
1045             #
1046             # Computation Functions
1047             #
1048             #//////////////////////////////////////////////////////////////////////////
1049              
1050             sub get_time_after {
1051 97     97 0 150 my $this = shift;
1052              
1053 97         528 my $after_time = shift->clone;
1054              
1055             # move ahead one second, since we're computing the time *after* the
1056             # given time
1057 97         1564 $after_time->add( seconds => 1 );
1058              
1059             # operable calendar
1060 97         80552 my $cl = $after_time->clone;
1061              
1062 97         1158 my $got_one = 0;
1063              
1064             # loop until we've computed the next time, or we've past the endTime
1065 97         337 ITER: while ( !$got_one ) {
1066              
1067             #if (endTime != null && cl.getTime().after(endTime)) return null;
1068 555 50       3289 if ( ( $cl->year ) > 2999 ) { # prevent endless loop...
1069 0         0 return undef;
1070             }
1071              
1072              
1073             # get second.................................................
1074             {
1075             # sorted set
1076             # SortedSet
1077 555         3862 my $st = undef;
  555         821  
1078 555         669 my $t = 0;
1079            
1080 555         1562 my $sec = $cl->second;
1081 555         3741 my $min = $cl->minute;
1082            
1083 555         4585 $st = $this->seconds->tail_set($sec);
1084            
1085 555 100 66     2495 if ( defined $st && $st->size() != 0 ) {
1086 466         2399 $sec = int( $st->first_item() );
1087             }
1088             else {
1089 89         274 $sec = int( $this->seconds->first_item() );
1090 89         431 $cl->add(minutes => 1);
1091             }
1092 555         84240 $cl->set( second => $sec );
1093             }
1094              
1095             # get minute.................................................
1096             {
1097 555         207807 my $min = $cl->minute;
  555         1791  
1098 555         4080 my $hr = $cl->hour;
1099 555         2640 my $t = -1;
1100              
1101 555         1990 my $st = $this->minutes->tail_set($min);
1102 555 100 66     3668 if ( defined $st && $st->size() != 0 ) {
1103 487         691 $t = $min;
1104 487         1208 $min = int( $st->first_item );
1105             }
1106             else {
1107             # next hour
1108 68         323 $min = int( $this->minutes->first_item() );
1109 68         158 $hr++;
1110             }
1111              
1112 555 100       2062 if ( $min != $t ) {
1113 209         745 $cl->set( second => 0, minute => $min );
1114 209         74811 $this->set_calendar_hour( $cl, $hr );
1115 209         1665 next ITER;
1116             }
1117              
1118 346         1191 $cl->set( minute => $min );
1119             }
1120              
1121             # get hour...................................................
1122             {
1123 346         125742 my $hr = $cl->hour;
  346         1143  
1124 346         2575 my $day = $cl->day;
1125 346         2702 my $t = -1;
1126            
1127 346         1298 my $st = $this->hours->tail_set( int($hr) );
1128 346 100 66     2118 if ( defined $st && $st->size() != 0 ) {
1129 287         665 $t = $hr;
1130 287         907 $hr = int( $st->first_item() );
1131             }
1132             else {
1133 59         180 $hr = int( $this->hours->first_item() );
1134 59         127 $day++;
1135             }
1136              
1137 346 100       1793 if ( $hr != $t ) {
1138              
1139 156         507 $cl->add( days => $day - $cl->day );
1140 156         70369 $cl->set( second => 0, minute => 0 );
1141            
1142 156         88158 $this->set_calendar_hour( $cl, $hr );
1143 156         891 next ITER;
1144             }
1145            
1146 190         711 $cl->set( hour => $hr );
1147             }
1148              
1149             # get day...................................................
1150             {
1151 190         73661 my $day = $cl->day;
  190         1312  
1152 190         1292 my $mon = $cl->month;
1153 190         1000 my $t = -1;
1154 190         308 my $tmon = $mon;
1155              
1156 190         846 my $day_of_m_spec = !$this->days_of_month->contains($NO_SPEC);
1157 190         759 my $day_of_w_spec = !$this->days_of_week->contains($NO_SPEC);
1158              
1159 190         909 my $min = $cl->min;
1160 190         1721 my $sec = $cl->sec;
1161 190         1142 my $hr = $cl->hour;
1162              
1163 190 100 66     3412 if ( $day_of_m_spec && !$day_of_w_spec )
    50 33        
1164             {
1165             # get day by day of month rule
1166 112         332 my $st = $this->days_of_month->tail_set( int($day) );
1167 112 100 66     471 if ( $this->lastday_of_month ) {
    100          
    100          
1168 10 100       122 if ( !$this->nearest_weekday ) {
1169 4         38 $t = $day;
1170 4         17 $day = $this->getlastday_of_month( $mon, $cl->year );
1171             }
1172             else {
1173 6         84 $t = $day;
1174 6         26 $day = $this->getlastday_of_month( $mon, $cl->year );
1175            
1176 6         1846 my $tcal = DateTime->new(
1177             second => 0,
1178             minute => 0,
1179             hour => 0,
1180             day => $day,
1181             month => $mon,
1182             year => $cl->year
1183             );
1184            
1185 6         1629 my $ldom = $this->getlastday_of_month( $mon, $cl->year );
1186 6         1199 my $dow = $tcal->day_of_week_0;
1187            
1188 6 50 66     77 if ( $dow == $SATURDAY && $day == 1 ) {
    100 33        
    50          
    50          
1189 0         0 $day += 2;
1190             }
1191             elsif ( $dow == $SATURDAY ) {
1192 2         35 $day -= 1;
1193             }
1194             elsif ( $dow == $SUNDAY && $day == $ldom ) {
1195 0         0 $day -= 2;
1196             }
1197             elsif ( $dow == $SUNDAY ) {
1198 0         0 $day += 1;
1199             }
1200            
1201             $tcal->set(
1202 6         131 second => $sec,
1203             minute => $min,
1204             hour => $hr,
1205             day => $day,
1206             month => $mon
1207             );
1208            
1209             # tcal before afterTime
1210 6 50       6678 if ( DateTime->compare( $tcal, $after_time ) < 0 ) {
1211 0         0 $day = 1;
1212 0         0 $mon++;
1213             }
1214             }
1215             }
1216             elsif ( $this->nearest_weekday ) {
1217 5         110 $t = $day;
1218 5         21 $day = int( $this->days_of_month->first_item() );
1219            
1220 5         32 my $tcal = DateTime->new(
1221             second => 0,
1222             minute => 0,
1223             hour => 0,
1224             day => $day,
1225             month => $mon,
1226             year => $cl->year
1227             );
1228              
1229 5         3740 my $ldom = $this->getlastday_of_month( $mon, $cl->year );
1230 5         1429 my $dow = $tcal->day_of_week_0;
1231              
1232 5 50 66     68 if ( $dow == $SATURDAY && $day == 1 ) {
    100 33        
    50          
    50          
1233 0         0 $day += 2;
1234             }
1235             elsif ( $dow == $SATURDAY ) {
1236 2         40 $day -= 1;
1237             }
1238             elsif ( $dow == $SUNDAY && $day == $ldom ) {
1239 0         0 $day -= 2;
1240             }
1241             elsif ( $dow == $SUNDAY ) {
1242 0         0 $day += 1;
1243             }
1244              
1245             $tcal->set(
1246 5         104 second => $sec,
1247             minute => $min,
1248             hour => $hr,
1249             day => $day,
1250             month => $mon
1251             );
1252            
1253             # tcal before afterTime
1254 5 100       8197 if ( DateTime->compare( $tcal, $after_time ) < 0 ) {
1255 2         208 $day = int( $this->days_of_month->first_item() );
1256 2         12 $mon++;
1257             }
1258             }
1259             elsif ( defined $st && $st->size() != 0 ) {
1260 84         231 $t = $day;
1261 84         485 $day = int( $st->first_item );
1262              
1263             # make sure we don't over-run a short month, such as february
1264 84         323 my $last_day = $this->getlastday_of_month( $mon, $cl->year );
1265 84 100       27115 if ( $day > $last_day ) {
1266 11         45 $day = int( $this->days_of_month->first_item() );
1267 11         33 $mon++;
1268             }
1269             }
1270             else {
1271 13         318 $day = int( $this->days_of_month->first_item() );
1272 13         34 $mon++;
1273             }
1274              
1275 112 100 100     2671 if ( $day != $t || $mon != $tmon ) {
1276 43         187 $cl->set(
1277             second => 0,
1278             minute => 0,
1279             hour => 0
1280             );
1281              
1282 43 100       15156 if ($mon > 12) {
1283 1         4 $cl->set(month => 12, day => 1);
1284 1         273 $cl->add(months => $mon - 12);
1285             } else {
1286 42         166 $cl->set(month => $mon, day => $day);
1287             }
1288              
1289 43         16637 next ITER;
1290             }
1291             }
1292             elsif ( $day_of_w_spec && !$day_of_m_spec )
1293             {
1294             # get day by day of week rule
1295              
1296 78 100       328 if ( $this->lastday_of_week )
    100          
1297             { # are we looking for the last XXX day of
1298             # the month?
1299              
1300 16         168 my $dow = int( $this->days_of_week->first_item() ); # desired d-o-w
1301 16         68 my $c_dow = $cl->day_of_week(); # current d-o-w
1302 16         70 my $days_to_add = 0;
1303 16 100       44 if ( $c_dow < $dow ) {
1304 4         7 $days_to_add = $dow - $c_dow;
1305             }
1306 16 100       43 if ( $c_dow > $dow ) {
1307 6         12 $days_to_add = $dow + ( 7 - $c_dow );
1308             }
1309            
1310 16         57 my $l_day = $this->getlastday_of_month( $mon, $cl->year );
1311            
1312 16 100       3290 if ( $day + $days_to_add > $l_day ) { # did we already miss the
1313             # last one?
1314 4         20 $cl->set(
1315             second => 0,
1316             minute => 0,
1317             hour => 0,
1318             day => 1,
1319             month => $mon + 1
1320             );
1321 4         1267 next ITER;
1322             }
1323            
1324             # find date of last occurance of this day in this month...
1325 12         43 while ( ( $day + $days_to_add + 7 ) <= $l_day ) {
1326 17         44 $days_to_add += 7;
1327             }
1328            
1329 12         15 $day += $days_to_add;
1330            
1331 12 100       33 if ( $days_to_add > 0 ) {
1332 6         26 $cl->set(
1333             second => 0,
1334             minute => 0,
1335             hour => 0,
1336             day => $day,
1337             month => $mon
1338             );
1339 6         1797 next ITER;
1340             }
1341            
1342             }
1343             elsif ( $this->nthday_of_week != 0 ) {
1344            
1345             # are we looking for the Nth XXX day in the month?
1346 8         180 my $dow = int( $this->days_of_week->first_item() ); # desired
1347             # d-o-w
1348 8         41 my $c_dow = $cl->day_of_week(); # current d-o-w
1349 8         37 my $days_to_add = 0;
1350 8 100       41 if ( $c_dow < $dow ) {
    100          
1351 1         2 $days_to_add = $dow - $c_dow;
1352             }
1353             elsif ( $c_dow > $dow ) {
1354 4         9 $days_to_add = $dow + ( 7 - $c_dow );
1355             }
1356            
1357 8         22 my $day_shifted = 0;
1358 8 100       31 if ( $days_to_add > 0 ) {
1359 5         8 $day_shifted = 1;
1360             }
1361            
1362 8         15 $day += $days_to_add;
1363 8         23 my $week_of_month = int( $day / 7 );
1364 8 50       28 if ( $day % 7 > 0 ) {
1365 8         21 $week_of_month++;
1366             }
1367            
1368 8         33 $days_to_add = ( $this->nthday_of_week - $week_of_month ) * 7;
1369 8         72 $day += $days_to_add;
1370 8 100 66     56 if ( $days_to_add < 0
    100 66        
1371             || $day > $this->getlastday_of_month( $mon, $cl->year ) )
1372             {
1373 2         9 $cl->set(
1374             second => 0,
1375             minute => 0,
1376             hour => 0,
1377             day => 1,
1378             month => $mon
1379             );
1380              
1381 2         604 $cl->add(months => 1);
1382 2         1363 next ITER;
1383             }
1384             elsif ( $days_to_add > 0 || $day_shifted ) {
1385 3         752 $cl->set(
1386             second => 0,
1387             minute => 0,
1388             hour => 0,
1389             day => $day,
1390             month => $mon
1391             );
1392 3         1083 next ITER;
1393             }
1394             }
1395             else {
1396 54         1506 my $c_dow = $cl->day_of_week; # current d-o-w
1397 54         502 my $dow = int( $this->days_of_week->first_item() ); # desired
1398             # d-o-w
1399 54         226 my $st = $this->days_of_week->tail_set( int($c_dow) );
1400 54 100 66     348 if ( defined $st && $st->size() > 0 ) {
1401 46         140 $dow = int( $st->first_item() );
1402             }
1403            
1404 54         269 my $days_to_add = 0;
1405 54 100       415 if ( $c_dow < $dow ) {
1406 10         20 $days_to_add = $dow - $c_dow;
1407             }
1408 54 100       199 if ( $c_dow > $dow ) {
1409 8         15 $days_to_add = $dow + ( 7 - $c_dow );
1410             }
1411            
1412 54         208 my $l_day = $this->getlastday_of_month( $mon, $cl->year );
1413            
1414 54 100       14862 if ( $day + $days_to_add > $l_day ) { # will we pass the end of
    100          
1415             # the month?
1416             # switch to the next month
1417 1         7 $cl->set(
1418             second => 0,
1419             minute => 0,
1420             hour => 0,
1421             day => 1
1422             );
1423            
1424 1         471 $cl->add(months => 1);
1425            
1426 1         581 next ITER;
1427             }
1428             elsif ( $days_to_add > 0 ) { # are we swithing days?
1429             # just add some more days
1430 17         95 $cl->set(
1431             second => 0,
1432             minute => 0,
1433             hour => 0,
1434             month => $mon
1435             );
1436            
1437 17         6828 $cl->add(days => $days_to_add);
1438            
1439 17         11765 next ITER;
1440             }
1441             }
1442             }
1443             else { # dayOfWSpec && !dayOfMSpec
1444 0         0 UnsupportedOperationException->throw(
1445             error => q/Support for specifying both /
1446             . q/a day-of-week AND a day-of-month parameter is not implemented./
1447             );
1448            
1449             # TODO:
1450             }
1451              
1452 114         1478 $cl->set( day => $day );
1453             }
1454              
1455             # get month...................................................
1456             {
1457 114         42562 my $mon = $cl->month;
  114         394  
1458 114         899 my $year = $cl->year;
1459 114         556 my $t = -1;
1460            
1461             # test for expressions that never generate a valid fire date,
1462             # but keep looping...
1463 114 50       325 if ( $year > 2099 ) {
1464 0         0 return undef;
1465             }
1466            
1467 114         471 my $st = $this->months->tail_set( int($mon) );
1468 114 100 66     713 if ( defined $st && $st->size() != 0 ) {
1469 102         157 $t = $mon;
1470 102         352 $mon = ( int $st->first_item() );
1471             }
1472             else {
1473 12         48 $mon = ( int $this->months->first_item() );
1474 12         37 $year++;
1475             }
1476 114 100       557 if ( $mon != $t ) {
1477 17         79 $cl->set(
1478             second => 0,
1479             minute => 0,
1480             hour => 0,
1481             day => 1,
1482             month => $mon
1483             );
1484            
1485 17         5529 $cl->set( year => $year );
1486 17         5803 next ITER;
1487             }
1488 97         423 $cl->set( month => $mon );
1489             }
1490              
1491             # get year...................................................
1492             {
1493 97         33379 my $year = $cl->year;
  97         340  
1494 97         829 my $t = -1;
1495              
1496 97         416 my $st = $this->years->tail_set( int($year) );
1497 97 50 33     760 if ( defined $st && $st->size() != 0 ) {
1498 97         307 $t = $year;
1499 97         267 $year = int( $st->first_item() );
1500             }
1501             else {
1502 0         0 return undef; # ran out of years...
1503             }
1504            
1505 97 50       1064 if ( $year != $t ) {
1506 0         0 $cl->set(
1507             second => 0,
1508             minute => 0,
1509             hour => 0,
1510             day => 1,
1511             month => 1
1512             );
1513 0         0 $cl->set( year => $year );
1514 0         0 next ITER;
1515             }
1516            
1517 97         350 $cl->set( year => $year );
1518             }
1519              
1520 97         44613 $got_one = 1;
1521             } # while( !done )
1522              
1523 97         853 return $cl;
1524             }
1525              
1526             #* Advance the calendar to the particular hour paying particular attention
1527             #* to daylight saving problems.
1528             #*
1529             #* @param cal
1530             #* @param hour
1531              
1532             sub set_calendar_hour {
1533 365     365 0 631 my $this = shift;
1534              
1535 365         626 my ( $cal, $hour ) = @_;
1536              
1537 365         499 my $delta = 0;
1538              
1539 365 100       1108 if ( $hour == 24 ) {
1540 1         2 $delta = 1;
1541 1         3 $hour--;
1542             }
1543              
1544 365         1025 $cal->set( hour => $hour );
1545              
1546 365 100       120529 if ( $delta > 0 ) {
1547 1         5 $cal->add( hours => $delta );
1548             }
1549             }
1550              
1551             #=pod
1552             # * NOT YET IMPLEMENTED: Returns the time before the given time
1553             # * that the CronExpression matches.
1554             #=cut
1555              
1556             sub get_time_before {
1557 0     0 0 0 my $this = shift;
1558              
1559 0         0 my $end_time = shift;
1560              
1561             # TODO: implement QUARTZ-423
1562 0         0 return;
1563             }
1564              
1565             #=pod
1566             # * NOT YET IMPLEMENTED: Returns the final time that the
1567             # * CronExpression will match.
1568             #=cut
1569              
1570             sub get_final_fire_time {
1571              
1572             # TODO: implement QUARTZ-423
1573 0     0 0 0 return;
1574             }
1575              
1576             sub is_leap_year {
1577 0     0 0 0 my $this = shift;
1578              
1579 0         0 return DateTime->new( year => shift )->is_leap_year;
1580             }
1581              
1582             sub getlastday_of_month {
1583 181     181 0 1818 my $this = shift;
1584              
1585 181         515 my ( $month_num, $year ) = @_;
1586              
1587 181         1411 return DateTime->last_day_of_month( month => $month_num, year => $year )
1588             ->day;
1589             }
1590              
1591             1;
1592              
1593             __END__