Cron-Toolkit
view release on metacpan or search on metacpan
lib/Cron/Toolkit.pm view on Meta::CPAN
return $self->_is_match($tm);
}
sub _is_match {
my ( $self, $tm ) = @_;
NODE: for my $node ( @{ $self->{nodes} } ) {
my $value = $self->_field_value( $tm, $node->field_type );
if ( $node->type eq 'list' ) {
for my $child ( @{ $node->children } ) {
next NODE if $child->match( $value, $tm );
}
return 0;
}
return 0 unless $node->match( $value, $tm );
}
return 1;
}
sub next {
my ( $self, $epoch_seconds ) = @_;
$epoch_seconds //= time;
my $clamped = max( $epoch_seconds, $self->{begin_epoch} );
return if $clamped > $self->{end_epoch};
my $tm = Time::Moment->from_epoch($clamped)
->with_offset_same_instant( $self->{utc_offset} );
$tm = $tm->plus_seconds(1);
# HMS shortcut / odometer carry
NODE: foreach my $i ( 0 .. 2 ) {
my $node = $self->{nodes}[$i];
my $curval = $self->_field_value( $tm, $node->field_type );
my $lowval = $node->lowest($tm);
my $highval = $node->highest($tm);
# Changed condition + explicit handling for exact highval
if ($curval > $highval || ($curval == $highval && !$node->match($curval, $tm))) {
$tm = $self->_set_date( $tm, $node->field_type, $lowval );
$tm = $self->_plus_one( $tm, $self->{nodes}[ $i + 1 ]->field_type );
next NODE;
}
for my $c ( $curval .. $highval ) {
my $c_tm = $self->_set_date( $tm, $node->field_type, $c );
if ( $self->_is_match($c_tm) ) {
$tm = $c_tm;
last NODE;
}
}
# flip if no match found in this field
$tm = $self->_set_date( $tm, $node->field_type, $lowval );
$tm = $self->_plus_one( $tm, $self->{nodes}[ $i + 1 ]->field_type );
}
# the brute force approach for DMY is correct here because:
# 1) the design is simple and easy to understand and debug
# 2) solves all tricky end-of-month and leap year calculations
# 3) 365 iterations per one-year time window is good enough
my $max_tm = Time::Moment->new( year => 2099, month => 12, day => 31,
hour => 23, minute => 59, second => 59 );
my $max_iter = $tm->delta_days($max_tm) || 1;
for my $day ( 1 .. $max_iter ) {
return $tm->epoch if $self->_is_match($tm);
$tm = $tm->plus_days(1);
}
return;
}
sub previous {
my ( $self, $epoch_seconds ) = @_;
$epoch_seconds //= time;
my $clamped = min( $epoch_seconds, $self->{end_epoch} );
return if $clamped < $self->{begin_epoch};
my $tm = Time::Moment->from_epoch($clamped)
->with_offset_same_instant( $self->{utc_offset} );
$tm = $tm->minus_seconds(1);
# HMS shortcut / odometer carry (backward)
NODE: foreach my $i ( 0 .. 2 ) {
my $node = $self->{nodes}[$i];
my $curval = $self->_field_value( $tm, $node->field_type );
my $lowval = $node->lowest($tm);
my $highval = $node->highest($tm);
# Changed condition + explicit handling for exact lowval
if ($curval < $lowval || ($curval == $lowval && !$node->match($curval, $tm))) {
$tm = $self->_set_date( $tm, $node->field_type, $highval );
$tm = $self->_minus_one( $tm, $self->{nodes}[ $i + 1 ]->field_type );
next NODE;
}
for ( my $c = $curval ; $c >= $lowval ; $c-- ) {
my $c_tm = $self->_set_date( $tm, $node->field_type, $c );
if ( $self->_is_match($c_tm) ) {
$tm = $c_tm;
last NODE;
}
}
# flip if no match found in this field
$tm = $self->_set_date( $tm, $node->field_type, $highval );
$tm = $self->_minus_one( $tm, $self->{nodes}[ $i + 1 ]->field_type );
}
# Brute force for DOM/DOW/year (no year adjustment block)
my $min_tm = Time::Moment->new( year => 1970, month => 1, day => 1,
hour => 0, minute => 0, second => 0 );
my $min_iter = $min_tm->delta_days($tm) || 1;
for my $day ( 0 .. $min_iter ) {
return $tm->epoch if $self->_is_match($tm);
$tm = $tm->minus_days(1);
}
return;
( run in 1.006 second using v1.01-cache-2.11-cpan-4ab04211f4c )