X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=Koha%2FCalendar.pm;h=6a1253013be1e9a975ef299a327f301d25502bf6;hb=17b38f15a7d3e5d11c09b7c8fd95101f6180a5cc;hp=ac3655ac674c06d2612d0fb644fc06448683ac02;hpb=ed297795254e9d82417a714e036508105f80f70e;p=koha.git diff --git a/Koha/Calendar.pm b/Koha/Calendar.pm index ac3655ac67..6a1253013b 100644 --- a/Koha/Calendar.pm +++ b/Koha/Calendar.pm @@ -33,28 +33,31 @@ sub _init { my $self = shift; my $branch = $self->{branchcode}; my $dbh = C4::Context->dbh(); - my $repeat_sth = $dbh->prepare( -'SELECT * from repeatable_holidays WHERE branchcode = ? AND ISNULL(weekday) = ?' + my $weekly_closed_days_sth = $dbh->prepare( +'SELECT weekday FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NOT NULL' ); - $repeat_sth->execute( $branch, 0 ); + $weekly_closed_days_sth->execute( $branch ); $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ]; Readonly::Scalar my $sunday => 7; - while ( my $tuple = $repeat_sth->fetchrow_hashref ) { + while ( my $tuple = $weekly_closed_days_sth->fetchrow_hashref ) { $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1; } - $repeat_sth->execute( $branch, 1 ); + my $day_month_closed_days_sth = $dbh->prepare( +'SELECT day, month FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NULL' + ); + $day_month_closed_days_sth->execute( $branch ); $self->{day_month_closed_days} = {}; - while ( my $tuple = $repeat_sth->fetchrow_hashref ) { - $self->{day_month_closed_days}->{ $tuple->{day} }->{ $tuple->{month} } = + while ( my $tuple = $day_month_closed_days_sth->fetchrow_hashref ) { + $self->{day_month_closed_days}->{ $tuple->{month} }->{ $tuple->{day} } = 1; } - my $special = $dbh->prepare( -'SELECT day, month, year, title, description FROM special_holidays WHERE ( branchcode = ? ) AND (isexception = ?)' + + my $exception_holidays_sth = $dbh->prepare( +'SELECT day, month, year FROM special_holidays WHERE branchcode = ? AND isexception = 1' ); - $special->execute( $branch, 1 ); + $exception_holidays_sth->execute( $branch ); my $dates = []; - while ( my ( $day, $month, $year, $title, $description ) = - $special->fetchrow ) { + while ( my ( $day, $month, $year ) = $exception_holidays_sth->fetchrow ) { push @{$dates}, DateTime->new( day => $day, @@ -65,10 +68,13 @@ sub _init { } $self->{exception_holidays} = DateTime::Set->from_datetimes( dates => $dates ); - $special->execute( $branch, 1 ); + + my $single_holidays_sth = $dbh->prepare( +'SELECT day, month, year FROM special_holidays WHERE branchcode = ? AND isexception = 0' + ); + $single_holidays_sth->execute( $branch ); $dates = []; - while ( my ( $day, $month, $year, $title, $description ) = - $special->fetchrow ) { + while ( my ( $day, $month, $year ) = $single_holidays_sth->fetchrow ) { push @{$dates}, DateTime->new( day => $day, @@ -78,85 +84,131 @@ sub _init { )->truncate( to => 'day' ); } $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates ); - $self->{days_mode} = C4::Context->preference('useDaysMode'); + $self->{days_mode} = C4::Context->preference('useDaysMode'); + $self->{test} = 0; return; } sub addDate { my ( $self, $startdate, $add_duration, $unit ) = @_; - my $base_date = $startdate->clone(); + + # Default to days duration (legacy support I guess) 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() ) { - $day_dur = DateTime::Duration->new( days => -1 ); + + $unit ||= 'days'; # default days ? + my $dt; + + if ( $unit eq 'hours' ) { + # Fixed for legacy support. Should be set as a branch parameter + Readonly::Scalar my $return_by_hour => 10; + + $dt = $self->addHours($startdate, $add_duration, $return_by_hour); + } else { + # days + $dt = $self->addDays($startdate, $add_duration); } - if ( $days_mode eq 'Datedue' ) { - my $dt = $base_date + $add_duration; - while ( $self->is_holiday($dt) ) { + return $dt; +} - # TODOP if hours set to 10 am - $dt->add_duration($day_dur); - if ( $unit eq 'hours' ) { - $dt->set_hour($return_by_hour); # Staffs specific - } +sub addHours { + my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_; + my $base_date = $startdate->clone(); + + $base_date->add_duration($hours_duration); + + # If we are using the calendar behave for now as if Datedue + # was the chosen option (current intended behaviour) + + if ( $self->{days_mode} ne 'Days' && + $self->is_holiday($base_date) ) { + + if ( $hours_duration->is_negative() ) { + $base_date = $self->prev_open_day($base_date); + } else { + $base_date = $self->next_open_day($base_date); } - return $dt; - } elsif ( $days_mode eq 'Calendar' ) { - if ( $unit eq 'hours' ) { - $base_date->add_duration($add_duration); - while ( $self->is_holiday($base_date) ) { - $base_date->add_duration($day_dur); - } + $base_date->set_hour($return_by_hour); + + } + + return $base_date; +} + +sub addDays { + my ( $self, $startdate, $days_duration ) = @_; + my $base_date = $startdate->clone(); + + if ( $self->{days_mode} eq 'Calendar' ) { + # use the calendar to skip all days the library is closed + # when adding + my $days = abs $days_duration->in_units('days'); + if ( $days_duration->is_negative() ) { + while ($days) { + $base_date = $self->prev_open_day($base_date); + --$days; + } } 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; - } + $base_date = $self->next_open_day($base_date); + --$days; } } - if ( $unit eq 'hours' ) { - my $dt = $base_date->clone()->subtract( days => 1 ); - if ( $self->is_holiday($dt) ) { - $base_date->set_hour($return_by_hour); # Staffs specific + + } else { # Days or Datedue + # use straight days, then use calendar to push + # the date to the next open day if Datedue + $base_date->add_duration($days_duration); + + if ( $self->{days_mode} eq 'Datedue' ) { + # Datedue, then use the calendar to push + # the date to the next open day if holiday + if ( $self->is_holiday($base_date) ) { + if ( $days_duration->is_negative() ) { + $base_date = $self->prev_open_day($base_date); + } else { + $base_date = $self->next_open_day($base_date); + } } } - return $base_date; - } else { # Days - return $base_date + $add_duration; } + + return $base_date; } sub is_holiday { my ( $self, $dt ) = @_; - my $dow = $dt->day_of_week; + my $localdt = $dt->clone(); + my $day = $localdt->day; + my $month = $localdt->month; + + $localdt->truncate( to => 'day' ); + + if ( $self->{exception_holidays}->contains($localdt) ) { + # exceptions are not holidays + return 0; + } + + my $dow = $localdt->day_of_week; + # Representation fix + # TODO: Shouldn't we shift the rest of the $dow also? if ( $dow == 7 ) { $dow = 0; } + if ( $self->{weekly_closed_days}->[$dow] == 1 ) { return 1; } - $dt->truncate( to => 'days' ); - my $day = $dt->day; - my $month = $dt->month; + if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) { return 1; } - if ( $self->{exception_holidays}->contains($dt) ) { - return 1; - } - if ( $self->{single_holidays}->contains($dt) ) { + + if ( $self->{single_holidays}->contains($localdt) ) { return 1; } @@ -164,43 +216,83 @@ sub is_holiday { return 0; } +sub next_open_day { + my ( $self, $dt ) = @_; + my $base_date = $dt->clone(); + + $base_date->add(days => 1); + + while ($self->is_holiday($base_date)) { + $base_date->add(days => 1); + } + + return $base_date; +} + +sub prev_open_day { + my ( $self, $dt ) = @_; + my $base_date = $dt->clone(); + + $base_date->add(days => -1); + + while ($self->is_holiday($base_date)) { + $base_date->add(days => -1); + } + + return $base_date; +} + sub days_between { my $self = shift; my $start_dt = shift; my $end_dt = shift; - my $datestart_temp = $start_dt->clone(); - my $dateend_temp = $end_dt->clone(); + if ( $start_dt->compare($end_dt) > 0 ) { + # swap dates + my $int_dt = $end_dt; + $end_dt = $start_dt; + $start_dt = $int_dt; + } + # start and end should not be closed days - $datestart_temp->truncate( to => 'day' ); - $dateend_temp->truncate( to => 'day' ); - my $duration = $dateend_temp - $datestart_temp; - while ( DateTime->compare( $datestart_temp, $dateend_temp ) == -1 ) { - $datestart_temp->add( days => 1 ); - if ( $self->is_holiday($datestart_temp) ) { - $duration->subtract( days => 1 ); + my $days = $start_dt->delta_days($end_dt)->delta_days; + for (my $dt = $start_dt->clone(); + $dt <= $end_dt; + $dt->add(days => 1) + ) { + if ($self->is_holiday($dt)) { + $days--; } } - return $duration; + return DateTime::Duration->new( days => $days ); } sub hours_between { - my ($self, $start_dt, $end_dt) = @_; + my ($self, $start_date, $end_date) = @_; + my $start_dt = $start_date->clone(); + my $end_dt = $end_date->clone(); my $duration = $end_dt->delta_ms($start_dt); - $start_dt->truncate( to => 'days' ); - $end_dt->truncate( to => 'days' ); + $start_dt->truncate( to => 'day' ); + $end_dt->truncate( to => 'day' ); # 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 ); + my $skipped_days = 0; + for (my $dt = $start_dt->clone(); + $dt <= $end_dt; + $dt->add(days => 1) + ) { + if ($self->is_holiday($dt)) { + ++$skipped_days; } } + if ($skipped_days) { + $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days)); + } + return $duration; } @@ -220,7 +312,41 @@ sub _mockinit { ); push @{$dates}, $special; $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates ); - $self->{days_mode} = 'Calendar'; + + # if not defined, days_mode defaults to 'Calendar' + if ( !defined($self->{days_mode}) ) { + $self->{days_mode} = 'Calendar'; + } + + $self->{test} = 1; + return; +} + +sub set_daysmode { + my ( $self, $mode ) = @_; + + # if not testing this is a no op + if ( $self->{test} ) { + $self->{days_mode} = $mode; + } + + return; +} + +sub clear_weekly_closed_days { + my $self = shift; + $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ]; # Sunday only + return; +} + +sub add_holiday { + my $self = shift; + my $new_dt = shift; + my @dt = $self->{single_holidays}->as_list; + push @dt, $new_dt; + $self->{single_holidays} = + DateTime::Set->from_datetimes( dates => \@dt ); + return; } @@ -237,9 +363,9 @@ This documentation refers to Koha::Calendar version 0.0.1 =head1 SYNOPSIS - use Koha::Calendat + use Koha::Calendar - my $c = Koha::Calender->new( branchcode => 'MAIN' ); + my $c = Koha::Calendar->new( branchcode => 'MAIN' ); my $dt = DateTime->now(); # are we open @@ -275,11 +401,36 @@ Currently unit is only used to invoke Staffs return Monday at 10 am rule this parameter will be removed when issuingrules properly cope with that +=head2 addHours + + my $dt = $calendar->addHours($date, $dur, $return_by_hour ) + +C<$date> is a DateTime object representing the starting date of the interval. + +C<$offset> is a DateTime::Duration to add to it + +C<$return_by_hour> is an integer value representing the opening hour for the branch + + +=head2 addDays + + my $dt = $calendar->addDays($date, $dur) + +C<$date> is a DateTime object representing the starting date of the interval. + +C<$offset> is a DateTime::Duration to add to it + +C<$unit> is a string value 'days' or 'hours' toflag granularity of duration + +Currently unit is only used to invoke Staffs return Monday at 10 am rule this +parameter will be removed when issuingrules properly cope with that + + =head2 is_holiday $yesno = $calendar->is_holiday($dt); -passed at DateTime object returns 1 if it is a closed day +passed a DateTime object returns 1 if it is a closed day 0 if not according to the calendar =head2 days_between @@ -287,7 +438,40 @@ passed at DateTime object returns 1 if it is a closed day $duration = $calendar->days_between($start_dt, $end_dt); Passed two dates returns a DateTime::Duration object measuring the length between them -ignoring closed days +ignoring closed days. Always returns a positive number irrespective of the +relative order of the parameters + +=head2 next_open_day + +$datetime = $calendar->next_open_day($duedate_dt) + +Passed a Datetime returns another Datetime representing the next open day. It is +intended for use to calculate the due date when useDaysMode syspref is set to either +'Datedue' or 'Calendar'. + +=head2 prev_open_day + +$datetime = $calendar->prev_open_day($duedate_dt) + +Passed a Datetime returns another Datetime representing the previous open day. It is +intended for use to calculate the due date when useDaysMode syspref is set to either +'Datedue' or 'Calendar'. + +=head2 set_daysmode + +For testing only allows the calling script to change days mode + +=head2 clear_weekly_closed_days + +In test mode changes the testing set of closed days to a new set with +no closed days. TODO passing an array of closed days to this would +allow testing of more configurations + +=head2 add_holiday + +Passed a datetime object this will add it to the calendar's list of +closed days. This is for testing so that we can alter the Calenfar object's +list of specified dates =head1 DIAGNOSTICS