Bug 40636: Add `Koha::Calendar->has_business_days_between()` method

This patch adds a new method to Koha::Calendar that returns a boolean indicating
whether there are any business days between two given dates (exclusive).

Implementation details:
* Returns 1 if any business days exist between start and end dates
* Returns 0 for consecutive days, same dates, or only holidays between
* Uses exclusive range (does not include start or end dates)
* Handles date comparison and timezone normalization
* Efficient early return when first business day is found

This is designed specifically for use cases like detecting missed
business days due to server downtime or scheduling issues.

To test:
1. Apply patch
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Calendar.t
=> SUCCESS: All tests pass!
3. Sign off :-D

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
(cherry picked from commit 2bfb86afb5)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
This commit is contained in:
Tomas Cohen Arazi 2025-08-12 14:25:37 -03:00 committed by Paul Derscheid
parent b3a9ed2262
commit 790e92ffb9
No known key found for this signature in database
GPG Key ID: C25F9FD44CED595B
2 changed files with 176 additions and 0 deletions

View File

@ -383,6 +383,28 @@ sub hours_between {
return $duration;
}
sub has_business_days_between {
my ( $self, $start_dt, $end_dt ) = @_;
# Clone dates to avoid modifying originals
my $current_date = $start_dt->clone->set_time_zone('floating');
my $end_date = $end_dt->clone->set_time_zone('floating');
# Ensure start is before end
if ( $current_date->compare($end_date) >= 0 ) {
return 0; # Same date or start after end
}
# Check each day between start and end (exclusive)
$current_date->add( days => 1 );
while ( $current_date->compare($end_date) < 0 ) {
return 1 unless $self->is_holiday($current_date);
$current_date->add( days => 1 );
}
return 0; # No business days found
}
sub set_daysmode {
my ( $self, $mode ) = @_;
@ -506,6 +528,22 @@ relative order of the parameters.
Note: This routine assumes neither the passed start_dt nor end_dt can be a closed day
=head2 has_business_days_between
$boolean = $calendar->has_business_days_between($start_dt, $end_dt);
Passed two DateTime objects, returns 1 if there are any business days (non-holidays)
between the start and end dates (exclusive), 0 otherwise.
This method is useful for determining if business days were missed between two dates,
such as when checking if server downtime caused business days to be skipped.
Examples:
- Monday to Wednesday with Tuesday as business day: returns 1
- Monday to Tuesday (consecutive days): returns 0
- Friday to Sunday with Saturday as holiday: returns 0
- Same date: returns 0
=head2 next_open_days
$datetime = $calendar->next_open_days($duedate_dt, $to_add)

138
t/db_dependent/Koha/Calendar.t Executable file
View File

@ -0,0 +1,138 @@
#!/usr/bin/perl
# Copyright 2025 Koha Development team
#
# This file is part of Koha
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use Test::More tests => 3;
use Test::Exception;
use Test::NoWarnings;
use Koha::Database;
use Koha::DateUtils qw( dt_from_string );
use t::lib::TestBuilder;
BEGIN {
use_ok('Koha::Calendar');
}
my $schema = Koha::Database->schema;
my $builder = t::lib::TestBuilder->new;
subtest 'has_business_days_between' => sub {
plan tests => 6;
$schema->storage->txn_begin;
my $library = $builder->build_object( { class => 'Koha::Libraries' } );
my $branchcode = $library->branchcode;
# Create test dates
my $monday = dt_from_string('2024-01-01'); # Monday
my $tuesday = dt_from_string('2024-01-02'); # Tuesday
my $wednesday = dt_from_string('2024-01-03'); # Wednesday
my $thursday = dt_from_string('2024-01-04'); # Thursday
my $friday = dt_from_string('2024-01-05'); # Friday
my $saturday = dt_from_string('2024-01-06'); # Saturday
my $sunday = dt_from_string('2024-01-07'); # Sunday
# Make Wednesday a holiday
my $wednesday_holiday = $builder->build(
{
source => 'SpecialHoliday',
value => {
branchcode => $branchcode,
day => $wednesday->day,
month => $wednesday->month,
year => $wednesday->year,
title => 'Wednesday Holiday',
isexception => 0
},
}
);
# Make Saturday and Sunday holidays (weekend)
my $saturday_holiday = $builder->build(
{
source => 'SpecialHoliday',
value => {
branchcode => $branchcode,
day => $saturday->day,
month => $saturday->month,
year => $saturday->year,
title => 'Saturday Holiday',
isexception => 0
},
}
);
my $sunday_holiday = $builder->build(
{
source => 'SpecialHoliday',
value => {
branchcode => $branchcode,
day => $sunday->day,
month => $sunday->month,
year => $sunday->year,
title => 'Sunday Holiday',
isexception => 0
},
}
);
my $calendar = Koha::Calendar->new( branchcode => $branchcode );
# Test 1: Business day between two business days
is(
$calendar->has_business_days_between( $monday, $wednesday ), 1,
'Should find business day (Tuesday) between Monday and Wednesday'
);
# Test 2: No business days between consecutive business days
is(
$calendar->has_business_days_between( $monday, $tuesday ), 0,
'Should find no business days between consecutive days'
);
# Test 3: Holiday between two business days
is(
$calendar->has_business_days_between( $tuesday, $thursday ), 0,
'Should find no business days when only holiday (Wednesday) is between'
);
# Test 4: Multiple days with business days
is(
$calendar->has_business_days_between( $monday, $friday ), 1,
'Should find business days between Monday and Friday'
);
# Test 5: Only holidays between dates
is(
$calendar->has_business_days_between( $friday, $sunday ), 0,
'Should find no business days between Friday and Sunday (Saturday is holiday)'
);
# Test 6: Same date
is(
$calendar->has_business_days_between( $monday, $monday ), 0,
'Should find no business days between same date'
);
$schema->storage->txn_rollback;
};