1 package Koha::Calendar;
8 use DateTime::Duration;
14 my ( $classname, %options ) = @_;
16 bless $self, $classname;
17 for my $o_name ( keys %options ) {
19 $self->{$o} = $options{$o_name};
21 if ( exists $options{TEST_MODE} ) {
25 if ( !defined $self->{branchcode} ) {
26 croak 'No branchcode argument passed to Koha::Calendar->new';
34 my $branch = $self->{branchcode};
35 my $dbh = C4::Context->dbh();
36 my $repeat_sth = $dbh->prepare(
37 'SELECT * from repeatable_holidays WHERE branchcode = ? AND ISNULL(weekday) = ?'
39 $repeat_sth->execute( $branch, 0 );
40 $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];
41 Readonly::Scalar my $sunday => 7;
42 while ( my $tuple = $repeat_sth->fetchrow_hashref ) {
43 $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1;
45 $repeat_sth->execute( $branch, 1 );
46 $self->{day_month_closed_days} = {};
47 while ( my $tuple = $repeat_sth->fetchrow_hashref ) {
48 $self->{day_month_closed_days}->{ $tuple->{month} }->{ $tuple->{day} } =
52 my $special = $dbh->prepare(
53 'SELECT day, month, year FROM special_holidays WHERE branchcode = ? AND isexception = ?'
55 $special->execute( $branch, 1 );
57 while ( my ( $day, $month, $year ) = $special->fetchrow ) {
63 time_zone => C4::Context->tz()
64 )->truncate( to => 'day' );
66 $self->{exception_holidays} =
67 DateTime::Set->from_datetimes( dates => $dates );
69 $special->execute( $branch, 0 );
71 while ( my ( $day, $month, $year ) = $special->fetchrow ) {
77 time_zone => C4::Context->tz()
78 )->truncate( to => 'day' );
80 $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates );
81 $self->{days_mode} = C4::Context->preference('useDaysMode');
87 my ( $self, $startdate, $add_duration, $unit ) = @_;
88 my $base_date = $startdate->clone();
89 if ( ref $add_duration ne 'DateTime::Duration' ) {
90 $add_duration = DateTime::Duration->new( days => $add_duration );
92 $unit ||= q{}; # default days ?
93 my $days_mode = $self->{days_mode};
94 Readonly::Scalar my $return_by_hour => 10;
95 my $day_dur = DateTime::Duration->new( days => 1 );
96 if ( $add_duration->is_negative() ) {
97 $day_dur = DateTime::Duration->new( days => -1 );
99 if ( $days_mode eq 'Datedue' ) {
101 my $dt = $base_date + $add_duration;
102 while ( $self->is_holiday($dt) ) {
104 $dt->add_duration($day_dur);
105 if ( $unit eq 'hours' ) {
106 $dt->set_hour($return_by_hour); # Staffs specific
110 } elsif ( $days_mode eq 'Calendar' ) {
111 if ( $unit eq 'hours' ) {
112 $base_date->add_duration($add_duration);
113 while ( $self->is_holiday($base_date) ) {
114 $base_date->add_duration($day_dur);
119 my $days = abs $add_duration->in_units('days');
121 $base_date->add_duration($day_dur);
122 if ( $self->is_holiday($base_date) ) {
129 if ( $unit eq 'hours' ) {
130 my $dt = $base_date->clone()->subtract( days => 1 );
131 if ( $self->is_holiday($dt) ) {
132 $base_date->set_hour($return_by_hour); # Staffs specific
137 return $base_date + $add_duration;
142 my ( $self, $dt ) = @_;
143 my $localdt = $dt->clone();
144 my $dow = $localdt->day_of_week;
148 if ( $self->{weekly_closed_days}->[$dow] == 1 ) {
151 $localdt->truncate( to => 'day' );
152 my $day = $localdt->day;
153 my $month = $localdt->month;
154 if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) {
157 if ( $self->{exception_holidays}->contains($dt) ) {
160 if ( $self->{single_holidays}->contains($dt) ) {
164 # damn have to go to work after all
170 my $start_dt = shift;
174 # start and end should not be closed days
175 my $days = $start_dt->delta_days($end_dt)->delta_days;
176 for (my $dt = $start_dt->clone();
180 if ($self->is_holiday($dt)) {
184 return DateTime::Duration->new( days => $days );
189 my ($self, $start_date, $end_date) = @_;
190 my $start_dt = $start_date->clone();
191 my $end_dt = $end_date->clone();
192 my $duration = $end_dt->delta_ms($start_dt);
193 $start_dt->truncate( to => 'day' );
194 $end_dt->truncate( to => 'day' );
195 # NB this is a kludge in that it assumes all days are 24 hours
196 # However for hourly loans the logic should be expanded to
197 # take into account open/close times then it would be a duration
198 # of library open hours
199 my $skipped_days = 0;
200 for (my $dt = $start_dt->clone();
204 if ($self->is_holiday($dt)) {
209 $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
218 $self->{weekly_closed_days} = [ 1, 0, 0, 0, 0, 0, 0 ]; # Sunday only
219 $self->{day_month_closed_days} = { 6 => { 16 => 1, } };
221 $self->{exception_holidays} =
222 DateTime::Set->from_datetimes( dates => $dates );
223 my $special = DateTime->new(
227 time_zone => 'Europe/London',
229 push @{$dates}, $special;
230 $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates );
231 $self->{days_mode} = 'Calendar';
237 my ( $self, $mode ) = @_;
239 # if not testing this is a no op
240 if ( $self->{test} ) {
241 $self->{days_mode} = $mode;
247 sub clear_weekly_closed_days {
249 $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ]; # Sunday only
256 my @dt = $self->{exception_holidays}->as_list;
258 $self->{exception_holidays} =
259 DateTime::Set->from_datetimes( dates => \@dt );
269 Koha::Calendar - Object containing a branches calendar
273 This documentation refers to Koha::Calendar version 0.0.1
279 my $c = Koha::Calender->new( branchcode => 'MAIN' );
280 my $dt = DateTime->now();
283 $open = $c->is_holiday($dt);
284 # when will item be due if loan period = $dur (a DateTime::Duration object)
285 $duedate = $c->addDate($dt,$dur,'days');
290 Implements those features of C4::Calendar needed for Staffs Rolling Loans
294 =head2 new : Create a calendar object
296 my $calendar = Koha::Calendar->new( branchcode => 'MAIN' );
298 The option branchcode is required
303 my $dt = $calendar->addDate($date, $dur, $unit)
305 C<$date> is a DateTime object representing the starting date of the interval.
307 C<$offset> is a DateTime::Duration to add to it
309 C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
311 Currently unit is only used to invoke Staffs return Monday at 10 am rule this
312 parameter will be removed when issuingrules properly cope with that
317 $yesno = $calendar->is_holiday($dt);
319 passed at DateTime object returns 1 if it is a closed day
320 0 if not according to the calendar
324 $duration = $calendar->days_between($start_dt, $end_dt);
326 Passed two dates returns a DateTime::Duration object measuring the length between them
327 ignoring closed days. Always returns a positive number irrespective of the
328 relative order of the parameters
332 For testing only allows the calling script to change days mode
334 =head2 clear_weekly_closed_days
336 In test mode changes the testing set of closed days to a new set with
337 no closed days. TODO passing an array of closed days to this would
338 allow testing of more configurations
342 Passed a datetime object this will add it to the calendar's list of
343 closed days. This is for testing so that we can alter the Calenfar object's
344 list of specified dates
348 Will croak if not passed a branchcode in new
350 =head1 BUGS AND LIMITATIONS
352 This only contains a limited subset of the functionality in C4::Calendar
353 Only enough to support Staffs Rolling loans
357 Colin Campbell colin.campbell@ptfs-europe.com
359 =head1 LICENSE AND COPYRIGHT
361 Copyright (c) 2011 PTFS-Europe Ltd All rights reserved
363 This program is free software: you can redistribute it and/or modify
364 it under the terms of the GNU General Public License as published by
365 the Free Software Foundation, either version 2 of the License, or
366 (at your option) any later version.
368 This program is distributed in the hope that it will be useful,
369 but WITHOUT ANY WARRANTY; without even the implied warranty of
370 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
371 GNU General Public License for more details.
373 You should have received a copy of the GNU General Public License
374 along with this program. If not, see <http://www.gnu.org/licenses/>.