Cron-Toolkit
view release on metacpan or search on metacpan
lib/Cron/Toolkit.pm view on Meta::CPAN
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);
# shortcut for HMS
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);
if ($curval >= $highval) {
$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 odometer if no match
$tm = $self->_set_date( $tm, $node->field_type, $lowval );
$tm = $self->_plus_one( $tm, $self->{nodes}[ $i + 1 ]->field_type );
}
# set year
my $year_node = $self->{nodes}[6];
my $year_lowval = $year_node->lowest($tm);
my $tm_year_low = $self->_set_date( $tm, $year_node->field_type, $year_lowval );
$tm_year_low = $self->_minus_one( $tm_year_low, $year_node->field_type );
$tm = $tm_year_low if $tm->is_before($tm_year_low);
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);
# 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
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);
NODE: foreach my $i ( 0 .. 2 ) {
my $node = $self->{nodes}[$i];
my $lowval = $node->lowest($tm);
my $highval = $node->highest($tm);
my $curval = $self->_field_value( $tm, $node->field_type );
if ($curval <= $lowval) {
$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 odometer if no match
$tm = $self->_set_date( $tm, $node->field_type, $highval );
$tm = $self->_minus_one( $tm, $self->{nodes}[ $i + 1 ]->field_type );
}
# set year
my $year_node = $self->{nodes}[6];
my $year_highval = $year_node->highest($tm);
my $tm_year_high = $self->_set_date( $tm, $year_node->field_type, $year_highval );
$tm_year_high = $self->_plus_one( $tm_year_high, $year_node->field_type );
$tm = $tm_year_high if $tm->is_after($tm_year_high);
# calculate maximum iterations
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);
for my $day ( 0 .. $min_iter ) {
return $tm->epoch if $self->_is_match($tm);
$tm = $tm->minus_days(1);
}
return;
}
sub _field_value {
my ( $self, $tm, $field_type ) = @_;
return $tm->second if $field_type eq 'second';
return $tm->minute if $field_type eq 'minute';
return $tm->hour if $field_type eq 'hour';
return $tm->day_of_month if $field_type eq 'dom';
return $tm->month if $field_type eq 'month';
return $tm->day_of_week if $field_type eq 'dow';
return $tm->year if $field_type eq 'year';
}
sub _set_date {
my ( $self, $tm, $field_type, $value ) = @_;
return $tm->with_second($value) if $field_type eq 'second';
return $tm->with_minute($value) if $field_type eq 'minute';
return $tm->with_hour($value) if $field_type eq 'hour';
return $tm->with_day_of_month($value) if $field_type eq 'dom';
return $tm->with_month($value) if $field_type eq 'month';
if ( $field_type eq 'dow' ) {
$value = 7 if $value == 0;
return $tm->with_day_of_week($value);
}
return $tm->with_year($value) if $field_type eq 'year';
}
sub _plus_one {
my ( $self, $tm, $field_type ) = @_;
return $tm->plus_seconds(1) if $field_type eq 'second';
return $tm->plus_minutes(1) if $field_type eq 'minute';
return $tm->plus_hours(1) if $field_type eq 'hour';
return $tm->plus_days(1) if $field_type eq 'dom';
return $tm->plus_months(1) if $field_type eq 'month';
return $tm->plus_weeks(1) if $field_type eq 'dow';
return $tm->plus_years(1) if $field_type eq 'year';
}
sub _minus_one {
my ( $self, $tm, $field_type ) = @_;
return $tm->minus_seconds(1) if $field_type eq 'second';
return $tm->minus_minutes(1) if $field_type eq 'minute';
return $tm->minus_hours(1) if $field_type eq 'hour';
return $tm->minus_days(1) if $field_type eq 'dom';
( run in 0.941 second using v1.01-cache-2.11-cpan-96521ef73a4 )