Bug 7833 - unique holiday link broken
[koha.git] / Koha / Calendar.pm
blob75c5c9ebb15c6516692a323f483134c30a4f2bf7
1 package Koha::Calendar;
2 use strict;
3 use warnings;
4 use 5.010;
6 use DateTime;
7 use DateTime::Set;
8 use DateTime::Duration;
9 use C4::Context;
10 use Carp;
11 use Readonly;
13 sub new {
14 my ( $classname, %options ) = @_;
15 my $self = {};
16 bless $self, $classname;
17 for my $o_name ( keys %options ) {
18 my $o = lc $o_name;
19 $self->{$o} = $options{$o_name};
21 if ( exists $options{TEST_MODE} ) {
22 $self->_mockinit();
23 return $self;
25 if ( !defined $self->{branchcode} ) {
26 croak 'No branchcode argument passed to Koha::Calendar->new';
28 $self->_init();
29 return $self;
32 sub _init {
33 my $self = shift;
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->{day} }->{ $tuple->{month} } =
51 my $special = $dbh->prepare(
52 'SELECT day, month, year, title, description FROM special_holidays WHERE ( branchcode = ? ) AND (isexception = ?)'
54 $special->execute( $branch, 1 );
55 my $dates = [];
56 while ( my ( $day, $month, $year, $title, $description ) =
57 $special->fetchrow ) {
58 push @{$dates},
59 DateTime->new(
60 day => $day,
61 month => $month,
62 year => $year,
63 time_zone => C4::Context->tz()
64 )->truncate( to => 'day' );
66 $self->{exception_holidays} =
67 DateTime::Set->from_datetimes( dates => $dates );
68 $special->execute( $branch, 1 );
69 $dates = [];
70 while ( my ( $day, $month, $year, $title, $description ) =
71 $special->fetchrow ) {
72 push @{$dates},
73 DateTime->new(
74 day => $day,
75 month => $month,
76 year => $year,
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');
82 return;
85 sub addDate {
86 my ( $self, $startdate, $add_duration, $unit ) = @_;
87 my $base_date = $startdate->clone();
88 if ( ref $add_duration ne 'DateTime::Duration' ) {
89 $add_duration = DateTime::Duration->new( days => $add_duration );
91 $unit ||= q{}; # default days ?
92 my $days_mode = $self->{days_mode};
93 Readonly::Scalar my $return_by_hour => 10;
94 my $day_dur = DateTime::Duration->new( days => 1 );
95 if ( $add_duration->is_negative() ) {
96 $day_dur->inverse();
98 if ( $days_mode eq 'Datedue' ) {
100 my $dt = $base_date + $add_duration;
101 while ( $self->is_holiday($dt) ) {
103 # TODOP if hours set to 10 am
104 $dt->add_duration($day_dur);
105 if ( $unit eq 'hours' ) {
106 $dt->set_hour($return_by_hour); # Staffs specific
109 return $dt;
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);
118 } else {
119 my $days = abs $add_duration->in_units('days');
120 while ($days) {
121 $base_date->add_duration($day_dur);
122 if ( $self->is_holiday($base_date) ) {
123 next;
124 } else {
125 --$days;
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
135 return $base_date;
136 } else { # Days
137 return $base_date + $add_duration;
141 sub is_holiday {
142 my ( $self, $dt ) = @_;
143 my $dow = $dt->day_of_week;
144 if ( $dow == 7 ) {
145 $dow = 0;
147 if ( $self->{weekly_closed_days}->[$dow] == 1 ) {
148 return 1;
150 $dt->truncate( to => 'days' );
151 my $day = $dt->day;
152 my $month = $dt->month;
153 if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) {
154 return 1;
156 if ( $self->{exception_holidays}->contains($dt) ) {
157 return 1;
159 if ( $self->{single_holidays}->contains($dt) ) {
160 return 1;
163 # damn have to go to work after all
164 return 0;
167 sub days_between {
168 my $self = shift;
169 my $start_dt = shift;
170 my $end_dt = shift;
172 # start and end should not be closed days
173 my $duration = $end_dt->delta_days($start_dt);
174 $start_dt->truncate( to => 'days' );
175 $end_dt->truncate( to => 'days' );
176 while ( DateTime->compare( $start_dt, $end_dt ) == -1 ) {
177 $start_dt->add( days => 1 );
178 if ( $self->is_holiday($start_dt) ) {
179 $duration->subtract( days => 1 );
182 return $duration;
186 sub hours_between {
187 my ($self, $start_dt, $end_dt) = @_;
188 my $duration = $end_dt->delta_ms($start_dt);
189 $start_dt->truncate( to => 'days' );
190 $end_dt->truncate( to => 'days' );
191 # NB this is a kludge in that it assumes all days are 24 hours
192 # However for hourly loans the logic should be expanded to
193 # take into account open/close times then it would be a duration
194 # of library open hours
195 while ( DateTime->compare( $start_dt, $end_dt ) == -1 ) {
196 $start_dt->add( days => 1 );
197 if ( $self->is_holiday($start_dt) ) {
198 $duration->subtract( hours => 24 );
201 return $duration;
205 sub _mockinit {
206 my $self = shift;
207 $self->{weekly_closed_days} = [ 1, 0, 0, 0, 0, 0, 0 ]; # Sunday only
208 $self->{day_month_closed_days} = { 6 => { 16 => 1, } };
209 my $dates = [];
210 $self->{exception_holidays} =
211 DateTime::Set->from_datetimes( dates => $dates );
212 my $special = DateTime->new(
213 year => 2011,
214 month => 6,
215 day => 1,
216 time_zone => 'Europe/London',
218 push @{$dates}, $special;
219 $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates );
220 $self->{days_mode} = 'Calendar';
221 return;
225 __END__
227 =head1 NAME
229 Koha::Calendar - Object containing a branches calendar
231 =head1 VERSION
233 This documentation refers to Koha::Calendar version 0.0.1
235 =head1 SYNOPSIS
237 use Koha::Calendat
239 my $c = Koha::Calender->new( branchcode => 'MAIN' );
240 my $dt = DateTime->now();
242 # are we open
243 $open = $c->is_holiday($dt);
244 # when will item be due if loan period = $dur (a DateTime::Duration object)
245 $duedate = $c->addDate($dt,$dur,'days');
248 =head1 DESCRIPTION
250 Implements those features of C4::Calendar needed for Staffs Rolling Loans
252 =head1 METHODS
254 =head2 new : Create a calendar object
256 my $calendar = Koha::Calendar->new( branchcode => 'MAIN' );
258 The option branchcode is required
261 =head2 addDate
263 my $dt = $calendar->addDate($date, $dur, $unit)
265 C<$date> is a DateTime object representing the starting date of the interval.
267 C<$offset> is a DateTime::Duration to add to it
269 C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
271 Currently unit is only used to invoke Staffs return Monday at 10 am rule this
272 parameter will be removed when issuingrules properly cope with that
275 =head2 is_holiday
277 $yesno = $calendar->is_holiday($dt);
279 passed at DateTime object returns 1 if it is a closed day
280 0 if not according to the calendar
282 =head2 days_between
284 $duration = $calendar->days_between($start_dt, $end_dt);
286 Passed two dates returns a DateTime::Duration object measuring the length between them
287 ignoring closed days
289 =head1 DIAGNOSTICS
291 Will croak if not passed a branchcode in new
293 =head1 BUGS AND LIMITATIONS
295 This only contains a limited subset of the functionality in C4::Calendar
296 Only enough to support Staffs Rolling loans
298 =head1 AUTHOR
300 Colin Campbell colin.campbell@ptfs-europe.com
302 =head1 LICENSE AND COPYRIGHT
304 Copyright (c) 2011 PTFS-Europe Ltd All rights reserved
306 This program is free software: you can redistribute it and/or modify
307 it under the terms of the GNU General Public License as published by
308 the Free Software Foundation, either version 2 of the License, or
309 (at your option) any later version.
311 This program is distributed in the hope that it will be useful,
312 but WITHOUT ANY WARRANTY; without even the implied warranty of
313 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
314 GNU General Public License for more details.
316 You should have received a copy of the GNU General Public License
317 along with this program. If not, see <http://www.gnu.org/licenses/>.