Bug 42310: (QA follow-up) Add closed_dates_in_range to Koha::Calendar

Introduces a public Koha::Calendar->closed_dates_in_range($from_dt,
$to_dt) method that returns an arrayref of ISO date strings representing
every closed day in the inclusive range, honouring weekly, annual
repeating, single and exception (open-override) closures.

This replaces callers that were reaching into Koha::Calendar private
state (_holidays, weekly_closed_days, day_month_closed_days) to
reconstruct the same logic. A follow-up under Bug 42206 converts the
REST controller; further callers can be migrated incrementally.

Test plan:
1. prove t/db_dependent/Koha/Calendar.t
2. Verify the new 'closed_dates_in_range' subtest passes

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This commit is contained in:
Martin Renvoize 2026-04-17 07:36:04 +01:00 committed by Pedro Amorim
parent 5875ce4c75
commit 7a8a220842
No known key found for this signature in database
GPG Key ID: 06B75BA7F3894D55
2 changed files with 87 additions and 1 deletions

View File

@ -484,6 +484,38 @@ sub delete_exception {
return $self; return $self;
} }
sub closed_dates_in_range {
my ( $self, $from_dt, $to_dt ) = @_;
croak 'Missing from_dt in closed_dates_in_range' unless $from_dt;
croak 'Missing to_dt in closed_dates_in_range' unless $to_dt;
my $holidays = $self->_holidays;
my @closed;
my $current = $from_dt->clone;
while ( $current <= $to_dt ) {
my $ymd = $current->ymd('');
# Exception (open override) wins over any other closure rule.
if ( defined $holidays->{$ymd} && $holidays->{$ymd} == 0 ) {
$current->add( days => 1 );
next;
}
if ( ( defined $holidays->{$ymd} && $holidays->{$ymd} == 1 )
|| $self->{weekly_closed_days}->[ $current->day_of_week % 7 ]
|| $self->{day_month_closed_days}->{ $current->month }->{ $current->day } )
{
push @closed, $current->ymd;
}
$current->add( days => 1 );
}
return \@closed;
}
sub copy_to { sub copy_to {
my ( $self, $target_branchcode ) = @_; my ( $self, $target_branchcode ) = @_;
croak "No target_branchcode" unless $target_branchcode; croak "No target_branchcode" unless $target_branchcode;
@ -771,6 +803,17 @@ list of specified dates
$calendar->delete_exception({ date => '2026-12-25' }); $calendar->delete_exception({ date => '2026-12-25' });
=head3 closed_dates_in_range
my $dates = $calendar->closed_dates_in_range($from_dt, $to_dt);
Returns an arrayref of ISO date strings (YYYY-MM-DD) representing every
closed day between C<$from_dt> and C<$to_dt> (inclusive), taking
weekly, annual-repeating, single and exception (open override)
closures into account. Exceptions win: a date listed as an exception
is never returned even if a weekly or repeating rule would otherwise
close it.
=head3 copy_to =head3 copy_to
$calendar->copy_to($target_branchcode); $calendar->copy_to($target_branchcode);

View File

@ -19,7 +19,7 @@
use Modern::Perl; use Modern::Perl;
use Test::More tests => 5; use Test::More tests => 6;
use Test::Exception; use Test::Exception;
use Test::NoWarnings; use Test::NoWarnings;
@ -229,6 +229,49 @@ subtest 'CRUD methods' => sub {
$schema->storage->txn_rollback; $schema->storage->txn_rollback;
}; };
subtest 'closed_dates_in_range' => sub {
plan tests => 5;
$schema->storage->txn_begin;
my $library = $builder->build_object( { class => 'Koha::Libraries' } );
my $branchcode = $library->branchcode;
# Sunday closed every week, 25 December every year, 2026-06-15 single,
# 2026-12-25 open exception (overrides the repeating rule).
$builder->build_object(
{ class => 'Koha::Calendar::WeeklyClosures', value => { library_id => $branchcode, weekday => 0 } } );
$builder->build_object(
{
class => 'Koha::Calendar::RepeatingClosures',
value => { library_id => $branchcode, day => 25, month => 12 }
}
);
$builder->build_object(
{ class => 'Koha::Calendar::SingleClosures', value => { library_id => $branchcode, date => '2026-06-15' } } );
$builder->build_object(
{ class => 'Koha::Calendar::Exceptions', value => { library_id => $branchcode, date => '2026-12-25' } } );
my $calendar = Koha::Calendar->new( branchcode => $branchcode );
my $closed = $calendar->closed_dates_in_range( dt_from_string('2026-06-14'), dt_from_string('2026-06-16') );
is_deeply( $closed, [ '2026-06-14', '2026-06-15' ], 'Sunday and single closure returned' );
$closed = $calendar->closed_dates_in_range( dt_from_string('2026-12-24'), dt_from_string('2026-12-26') );
is_deeply( $closed, [], 'Exception suppresses repeating closure on 2026-12-25' );
$closed = $calendar->closed_dates_in_range( dt_from_string('2027-06-15'), dt_from_string('2027-06-15') );
is_deeply( $closed, [], 'Open Tuesday not returned' );
$closed = $calendar->closed_dates_in_range( dt_from_string('2027-12-25'), dt_from_string('2027-12-25') );
is_deeply( $closed, ['2027-12-25'], 'Repeating closure returned in a year with no exception' );
throws_ok { $calendar->closed_dates_in_range() } qr/Missing from_dt/, 'croaks without from_dt';
$schema->storage->txn_rollback;
};
subtest 'delete_*_closure clears _holidays cache' => sub { subtest 'delete_*_closure clears _holidays cache' => sub {
plan tests => 4; plan tests => 4;