use DateTime;
use DateTime::Set;
+use DateTime::Duration;
use C4::Context;
use Carp;
use Readonly;
sub new {
my ( $classname, %options ) = @_;
my $self = {};
+ bless $self, $classname;
for my $o_name ( keys %options ) {
my $o = lc $o_name;
$self->{$o} = $options{$o_name};
}
+ if ( exists $options{TEST_MODE} ) {
+ $self->_mockinit();
+ return $self;
+ }
if ( !defined $self->{branchcode} ) {
croak 'No branchcode argument passed to Koha::Calendar->new';
}
- bless $self, $classname;
$self->_init();
return $self;
}
'SELECT * from repeatable_holidays WHERE branchcode = ? AND ISNULL(weekday) = ?'
);
$repeat_sth->execute( $branch, 0 );
- $self->{weekly_closed_days} = [];
+ $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];
Readonly::Scalar my $sunday => 7;
while ( my $tuple = $repeat_sth->fetchrow_hashref ) {
- my $day = $tuple->{weekday} == 0 ? $sunday : $tuple->{weekday};
- push @{ $self->{weekly_closed_days} }, $day;
+ $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1;
}
$repeat_sth->execute( $branch, 1 );
- $self->{day_month_closed_days} = [];
+ $self->{day_month_closed_days} = {};
while ( my $tuple = $repeat_sth->fetchrow_hashref ) {
- push @{ $self->{day_month_closed_days} },
- { day => $tuple->{day}, month => $tuple->{month}, };
+ $self->{day_month_closed_days}->{ $tuple->{day} }->{ $tuple->{month} } =
+ 1;
}
my $special = $dbh->prepare(
'SELECT day, month, year, title, description FROM special_holidays WHERE ( branchcode = ? ) AND (isexception = ?)'
)->truncate( to => 'day' );
}
$self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates );
+ $self->{days_mode} = C4::Context->preference('useDaysMode');
return;
}
sub addDate {
- my ( $self, $base_date, $add_duration, $unit ) = @_;
- my $days_mode = C4::Context->preference('useDaysMode');
+ my ( $self, $startdate, $add_duration, $unit ) = @_;
+ my $base_date = $startdate->clone();
+ if ( ref $add_duration ne 'DateTime::Duration' ) {
+ $add_duration = DateTime::Duration->new( days => $add_duration );
+ }
+ $unit ||= q{}; # default days ?
+ my $days_mode = $self->{days_mode};
Readonly::Scalar my $return_by_hour => 10;
- my $day_dur = DateTime::Duration->new( days => 1);
- if ($add_duration->is_negative()) {
+ my $day_dur = DateTime::Duration->new( days => 1 );
+ if ( $add_duration->is_negative() ) {
$day_dur->inverse();
}
if ( $days_mode eq 'Datedue' ) {
while ( $self->is_holiday($dt) ) {
# TODOP if hours set to 10 am
- $dt->add_duration( $day_dur );
+ $dt->add_duration($day_dur);
if ( $unit eq 'hours' ) {
$dt->set_hour($return_by_hour); # Staffs specific
}
}
return $dt;
} elsif ( $days_mode eq 'Calendar' ) {
- my $days = $add_duration->in_units('days');
- while ($days) {
- $base_date->add_duration( $day_dur );
- if ( $self->is_holiday($base_date) ) {
- next;
- } else {
- --$days;
+ if ( $unit eq 'hours' ) {
+ $base_date->add_duration($add_duration);
+ while ( $self->is_holiday($base_date) ) {
+ $base_date->add_duration($day_dur);
+
+ }
+
+ } else {
+ my $days = abs $add_duration->in_units('days');
+ while ($days) {
+ $base_date->add_duration($day_dur);
+ if ( $self->is_holiday($base_date) ) {
+ next;
+ } else {
+ --$days;
+ }
}
}
if ( $unit eq 'hours' ) {
sub is_holiday {
my ( $self, $dt ) = @_;
my $dow = $dt->day_of_week;
- my @matches = grep { $_ == $dow } @{ $self->{weekly_closed_days} };
- if (@matches) {
+ if ( $dow == 7 ) {
+ $dow = 0;
+ }
+ if ( $self->{weekly_closed_days}->[$dow] == 1 ) {
return 1;
}
- $dt->truncate(to => 'days');
+ $dt->truncate( to => 'days' );
my $day = $dt->day;
my $month = $dt->month;
- for my $dm ( @{ $self->{day_month_closed_days} } ) {
- if ( $month == $dm->{month} && $day == $dm->{day} ) {
- return 1;
- }
+ if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) {
+ return 1;
}
if ( $self->{exception_holidays}->contains($dt) ) {
return 1;
return 0;
}
+sub days_between {
+ my $self = shift;
+ my $start_dt = shift;
+ my $end_dt = shift;
+
+ # start and end should not be closed days
+ my $duration = $end_dt->delta_days($start_dt);
+ $start_dt->truncate( to => 'days' );
+ $end_dt->truncate( to => 'days' );
+ while ( DateTime->compare( $start_dt, $end_dt ) == -1 ) {
+ $start_dt->add( days => 1 );
+ if ( $self->is_holiday($start_dt) ) {
+ $duration->subtract( days => 1 );
+ }
+ }
+ return $duration;
+
+}
+
+sub hours_between {
+ my ($self, $start_dt, $end_dt) = @_;
+ my $duration = $end_dt->delta_ms($start_dt);
+ $start_dt->truncate( to => 'days' );
+ $end_dt->truncate( to => 'days' );
+ # NB this is a kludge in that it assumes all days are 24 hours
+ # However for hourly loans the logic should be expanded to
+ # take into account open/close times then it would be a duration
+ # of library open hours
+ while ( DateTime->compare( $start_dt, $end_dt ) == -1 ) {
+ $start_dt->add( days => 1 );
+ if ( $self->is_holiday($start_dt) ) {
+ $duration->subtract( hours => 24 );
+ }
+ }
+ return $duration;
+
+}
+
+sub _mockinit {
+ my $self = shift;
+ $self->{weekly_closed_days} = [ 1, 0, 0, 0, 0, 0, 0 ]; # Sunday only
+ $self->{day_month_closed_days} = { 6 => { 16 => 1, } };
+ my $dates = [];
+ $self->{exception_holidays} =
+ DateTime::Set->from_datetimes( dates => $dates );
+ my $special = DateTime->new(
+ year => 2011,
+ month => 6,
+ day => 1,
+ time_zone => 'Europe/London',
+ );
+ push @{$dates}, $special;
+ $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates );
+ $self->{days_mode} = 'Calendar';
+ return;
+}
+
1;
__END__
passed at DateTime object returns 1 if it is a closed day
0 if not according to the calendar
+=head2 days_between
+
+$duration = $calendar->days_between($start_dt, $end_dt);
+
+Passed two dates returns a DateTime::Duration object measuring the length between them
+ignoring closed days
+
=head1 DIAGNOSTICS
Will croak if not passed a branchcode in new