| Filename | /mnt/catalyst/koha/Koha/Calendar.pm | 
| Statements | Executed 20 statements in 3.29ms | 
| Calls | P | F | Exclusive Time  | 
        Inclusive Time  | 
        Subroutine | 
|---|---|---|---|---|---|
| 1 | 1 | 1 | 4.07ms | 27.6ms | Koha::Calendar::BEGIN@7 | 
| 1 | 1 | 1 | 3.19ms | 3.44ms | Koha::Calendar::BEGIN@11 | 
| 1 | 1 | 1 | 470µs | 482µs | Koha::Calendar::BEGIN@2 | 
| 1 | 1 | 1 | 9µs | 9µs | Koha::Calendar::BEGIN@4 | 
| 1 | 1 | 1 | 8µs | 11µs | Koha::Calendar::BEGIN@9 | 
| 1 | 1 | 1 | 7µs | 11µs | Koha::Calendar::BEGIN@3 | 
| 1 | 1 | 1 | 7µs | 43µs | Koha::Calendar::BEGIN@10 | 
| 1 | 1 | 1 | 7µs | 7µs | Koha::Calendar::BEGIN@8 | 
| 1 | 1 | 1 | 6µs | 6µs | Koha::Calendar::BEGIN@6 | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::_init | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::addDate | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::addDays | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::addHours | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::add_holiday | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::clear_weekly_closed_days | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::days_between | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::exception_holidays | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::hours_between | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::is_holiday | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::new | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::next_open_day | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::prev_open_day | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::set_daysmode | 
| 0 | 0 | 0 | 0s | 0s | Koha::Calendar::single_holidays | 
| Line | State ments  | 
      Time on line  | 
      Calls | Time in subs  | 
      Code | 
|---|---|---|---|---|---|
| 1 | package Koha::Calendar; | ||||
| 2 | 2 | 27µs | 2 | 495µs | # spent 482µs (470+12) within Koha::Calendar::BEGIN@2 which was called:
#    once (470µs+12µs) by C4::Circulation::BEGIN@49 at line 2 # spent   482µs making 1 call to Koha::Calendar::BEGIN@2
# spent    12µs making 1 call to strict::import  | 
| 3 | 2 | 19µs | 2 | 16µs | # spent 11µs (7+4) within Koha::Calendar::BEGIN@3 which was called:
#    once (7µs+4µs) by C4::Circulation::BEGIN@49 at line 3 # spent    11µs making 1 call to Koha::Calendar::BEGIN@3
# spent     4µs making 1 call to warnings::import  | 
| 4 | 2 | 34µs | 1 | 9µs | # spent 9µs within Koha::Calendar::BEGIN@4 which was called:
#    once (9µs+0s) by C4::Circulation::BEGIN@49 at line 4 # spent     9µs making 1 call to Koha::Calendar::BEGIN@4  | 
| 5 | |||||
| 6 | 2 | 21µs | 1 | 6µs | # spent 6µs within Koha::Calendar::BEGIN@6 which was called:
#    once (6µs+0s) by C4::Circulation::BEGIN@49 at line 6 # spent     6µs making 1 call to Koha::Calendar::BEGIN@6  | 
| 7 | 2 | 709µs | 1 | 27.6ms | # spent 27.6ms (4.07+23.5) within Koha::Calendar::BEGIN@7 which was called:
#    once (4.07ms+23.5ms) by C4::Circulation::BEGIN@49 at line 7 # spent  27.6ms making 1 call to Koha::Calendar::BEGIN@7  | 
| 8 | 2 | 21µs | 1 | 7µs | # spent 7µs within Koha::Calendar::BEGIN@8 which was called:
#    once (7µs+0s) by C4::Circulation::BEGIN@49 at line 8 # spent     7µs making 1 call to Koha::Calendar::BEGIN@8  | 
| 9 | 2 | 21µs | 2 | 14µs | # spent 11µs (8+3) within Koha::Calendar::BEGIN@9 which was called:
#    once (8µs+3µs) by C4::Circulation::BEGIN@49 at line 9 # spent    11µs making 1 call to Koha::Calendar::BEGIN@9
# spent     3µs making 1 call to C4::Context::import  | 
| 10 | 2 | 21µs | 2 | 79µs | # spent 43µs (7+36) within Koha::Calendar::BEGIN@10 which was called:
#    once (7µs+36µs) by C4::Circulation::BEGIN@49 at line 10 # spent    43µs making 1 call to Koha::Calendar::BEGIN@10
# spent    36µs making 1 call to Exporter::import  | 
| 11 | 2 | 2.41ms | 2 | 3.46ms | # spent 3.44ms (3.19+242µs) within Koha::Calendar::BEGIN@11 which was called:
#    once (3.19ms+242µs) by C4::Circulation::BEGIN@49 at line 11 # spent  3.44ms making 1 call to Koha::Calendar::BEGIN@11
# spent    24µs making 1 call to Exporter::import  | 
| 12 | |||||
| 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}; | ||||
| 20 | } | ||||
| 21 | if ( !defined $self->{branchcode} ) { | ||||
| 22 | croak 'No branchcode argument passed to Koha::Calendar->new'; | ||||
| 23 | } | ||||
| 24 | $self->_init(); | ||||
| 25 | return $self; | ||||
| 26 | } | ||||
| 27 | |||||
| 28 | sub _init { | ||||
| 29 | my $self = shift; | ||||
| 30 | my $branch = $self->{branchcode}; | ||||
| 31 | my $dbh = C4::Context->dbh(); | ||||
| 32 | my $weekly_closed_days_sth = $dbh->prepare( | ||||
| 33 | 'SELECT weekday FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NOT NULL' | ||||
| 34 | ); | ||||
| 35 | $weekly_closed_days_sth->execute( $branch ); | ||||
| 36 | $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ]; | ||||
| 37 | Readonly::Scalar my $sunday => 7; | ||||
| 38 | while ( my $tuple = $weekly_closed_days_sth->fetchrow_hashref ) { | ||||
| 39 | $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1; | ||||
| 40 | } | ||||
| 41 | my $day_month_closed_days_sth = $dbh->prepare( | ||||
| 42 | 'SELECT day, month FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NULL' | ||||
| 43 | ); | ||||
| 44 | $day_month_closed_days_sth->execute( $branch ); | ||||
| 45 | $self->{day_month_closed_days} = {}; | ||||
| 46 | while ( my $tuple = $day_month_closed_days_sth->fetchrow_hashref ) { | ||||
| 47 | $self->{day_month_closed_days}->{ $tuple->{month} }->{ $tuple->{day} } = | ||||
| 48 | 1; | ||||
| 49 | } | ||||
| 50 | |||||
| 51 | $self->{days_mode} = C4::Context->preference('useDaysMode'); | ||||
| 52 | $self->{test} = 0; | ||||
| 53 | return; | ||||
| 54 | } | ||||
| 55 | |||||
| 56 | |||||
| 57 | # FIXME: use of package-level variables for caching the holiday | ||||
| 58 | # lists breaks persistance engines. As of 2013-12-10, the RM | ||||
| 59 | # is allowing this with the expectation that prior to release of | ||||
| 60 | # 3.16, bug 8089 will be fixed and we can switch the caching over | ||||
| 61 | # to Koha::Cache. | ||||
| 62 | 1 | 200ns | our ( $exception_holidays, $single_holidays ); | ||
| 63 | sub exception_holidays { | ||||
| 64 | my ( $self ) = @_; | ||||
| 65 | my $dbh = C4::Context->dbh; | ||||
| 66 | my $branch = $self->{branchcode}; | ||||
| 67 | if ( $exception_holidays ) { | ||||
| 68 | $self->{exception_holidays} = $exception_holidays; | ||||
| 69 | return $exception_holidays; | ||||
| 70 | } | ||||
| 71 | my $exception_holidays_sth = $dbh->prepare( | ||||
| 72 | 'SELECT day, month, year FROM special_holidays WHERE branchcode = ? AND isexception = 1' | ||||
| 73 | ); | ||||
| 74 | $exception_holidays_sth->execute( $branch ); | ||||
| 75 | my $dates = []; | ||||
| 76 | while ( my ( $day, $month, $year ) = $exception_holidays_sth->fetchrow ) { | ||||
| 77 | push @{$dates}, | ||||
| 78 | DateTime->new( | ||||
| 79 | day => $day, | ||||
| 80 | month => $month, | ||||
| 81 | year => $year, | ||||
| 82 | time_zone => C4::Context->tz() | ||||
| 83 | )->truncate( to => 'day' ); | ||||
| 84 | } | ||||
| 85 | $self->{exception_holidays} = | ||||
| 86 | DateTime::Set->from_datetimes( dates => $dates ); | ||||
| 87 | $exception_holidays = $self->{exception_holidays}; | ||||
| 88 | return $exception_holidays; | ||||
| 89 | } | ||||
| 90 | |||||
| 91 | sub single_holidays { | ||||
| 92 | my ( $self ) = @_; | ||||
| 93 | my $dbh = C4::Context->dbh; | ||||
| 94 | my $branch = $self->{branchcode}; | ||||
| 95 | if ( $single_holidays ) { | ||||
| 96 | $self->{single_holidays} = $single_holidays; | ||||
| 97 | return $single_holidays; | ||||
| 98 | } | ||||
| 99 | my $single_holidays_sth = $dbh->prepare( | ||||
| 100 | 'SELECT day, month, year FROM special_holidays WHERE branchcode = ? AND isexception = 0' | ||||
| 101 | ); | ||||
| 102 | $single_holidays_sth->execute( $branch ); | ||||
| 103 | my $dates = []; | ||||
| 104 | while ( my ( $day, $month, $year ) = $single_holidays_sth->fetchrow ) { | ||||
| 105 | push @{$dates}, | ||||
| 106 | DateTime->new( | ||||
| 107 | day => $day, | ||||
| 108 | month => $month, | ||||
| 109 | year => $year, | ||||
| 110 | time_zone => C4::Context->tz() | ||||
| 111 | )->truncate( to => 'day' ); | ||||
| 112 | } | ||||
| 113 | $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates ); | ||||
| 114 | $single_holidays = $self->{single_holidays}; | ||||
| 115 | return $single_holidays; | ||||
| 116 | } | ||||
| 117 | sub addDate { | ||||
| 118 | my ( $self, $startdate, $add_duration, $unit ) = @_; | ||||
| 119 | |||||
| 120 | # Default to days duration (legacy support I guess) | ||||
| 121 | if ( ref $add_duration ne 'DateTime::Duration' ) { | ||||
| 122 | $add_duration = DateTime::Duration->new( days => $add_duration ); | ||||
| 123 | } | ||||
| 124 | |||||
| 125 | $unit ||= 'days'; # default days ? | ||||
| 126 | my $dt; | ||||
| 127 | |||||
| 128 | if ( $unit eq 'hours' ) { | ||||
| 129 | # Fixed for legacy support. Should be set as a branch parameter | ||||
| 130 | Readonly::Scalar my $return_by_hour => 10; | ||||
| 131 | |||||
| 132 | $dt = $self->addHours($startdate, $add_duration, $return_by_hour); | ||||
| 133 | } else { | ||||
| 134 | # days | ||||
| 135 | $dt = $self->addDays($startdate, $add_duration); | ||||
| 136 | } | ||||
| 137 | |||||
| 138 | return $dt; | ||||
| 139 | } | ||||
| 140 | |||||
| 141 | sub addHours { | ||||
| 142 | my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_; | ||||
| 143 | my $base_date = $startdate->clone(); | ||||
| 144 | |||||
| 145 | $base_date->add_duration($hours_duration); | ||||
| 146 | |||||
| 147 | # If we are using the calendar behave for now as if Datedue | ||||
| 148 | # was the chosen option (current intended behaviour) | ||||
| 149 | |||||
| 150 | if ( $self->{days_mode} ne 'Days' && | ||||
| 151 | $self->is_holiday($base_date) ) { | ||||
| 152 | |||||
| 153 | if ( $hours_duration->is_negative() ) { | ||||
| 154 | $base_date = $self->prev_open_day($base_date); | ||||
| 155 | } else { | ||||
| 156 | $base_date = $self->next_open_day($base_date); | ||||
| 157 | } | ||||
| 158 | |||||
| 159 | $base_date->set_hour($return_by_hour); | ||||
| 160 | |||||
| 161 | } | ||||
| 162 | |||||
| 163 | return $base_date; | ||||
| 164 | } | ||||
| 165 | |||||
| 166 | sub addDays { | ||||
| 167 | my ( $self, $startdate, $days_duration ) = @_; | ||||
| 168 | my $base_date = $startdate->clone(); | ||||
| 169 | |||||
| 170 | if ( $self->{days_mode} eq 'Calendar' ) { | ||||
| 171 | # use the calendar to skip all days the library is closed | ||||
| 172 | # when adding | ||||
| 173 | my $days = abs $days_duration->in_units('days'); | ||||
| 174 | |||||
| 175 | if ( $days_duration->is_negative() ) { | ||||
| 176 | while ($days) { | ||||
| 177 | $base_date = $self->prev_open_day($base_date); | ||||
| 178 | --$days; | ||||
| 179 | } | ||||
| 180 | } else { | ||||
| 181 | while ($days) { | ||||
| 182 | $base_date = $self->next_open_day($base_date); | ||||
| 183 | --$days; | ||||
| 184 | } | ||||
| 185 | } | ||||
| 186 | |||||
| 187 | } else { # Days or Datedue | ||||
| 188 | # use straight days, then use calendar to push | ||||
| 189 | # the date to the next open day if Datedue | ||||
| 190 | $base_date->add_duration($days_duration); | ||||
| 191 | |||||
| 192 | if ( $self->{days_mode} eq 'Datedue' ) { | ||||
| 193 | # Datedue, then use the calendar to push | ||||
| 194 | # the date to the next open day if holiday | ||||
| 195 | if ( $self->is_holiday($base_date) ) { | ||||
| 196 | if ( $days_duration->is_negative() ) { | ||||
| 197 | $base_date = $self->prev_open_day($base_date); | ||||
| 198 | } else { | ||||
| 199 | $base_date = $self->next_open_day($base_date); | ||||
| 200 | } | ||||
| 201 | } | ||||
| 202 | } | ||||
| 203 | } | ||||
| 204 | |||||
| 205 | return $base_date; | ||||
| 206 | } | ||||
| 207 | |||||
| 208 | sub is_holiday { | ||||
| 209 | my ( $self, $dt ) = @_; | ||||
| 210 | my $localdt = $dt->clone(); | ||||
| 211 | my $day = $localdt->day; | ||||
| 212 | my $month = $localdt->month; | ||||
| 213 | |||||
| 214 | $localdt->truncate( to => 'day' ); | ||||
| 215 | |||||
| 216 | if ( $self->exception_holidays->contains($localdt) ) { | ||||
| 217 | # exceptions are not holidays | ||||
| 218 | return 0; | ||||
| 219 | } | ||||
| 220 | |||||
| 221 | my $dow = $localdt->day_of_week; | ||||
| 222 | # Representation fix | ||||
| 223 | # TODO: Shouldn't we shift the rest of the $dow also? | ||||
| 224 | if ( $dow == 7 ) { | ||||
| 225 | $dow = 0; | ||||
| 226 | } | ||||
| 227 | |||||
| 228 | if ( $self->{weekly_closed_days}->[$dow] == 1 ) { | ||||
| 229 | return 1; | ||||
| 230 | } | ||||
| 231 | |||||
| 232 | if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) { | ||||
| 233 | return 1; | ||||
| 234 | } | ||||
| 235 | |||||
| 236 | if ( $self->single_holidays->contains($localdt) ) { | ||||
| 237 | return 1; | ||||
| 238 | } | ||||
| 239 | |||||
| 240 | # damn have to go to work after all | ||||
| 241 | return 0; | ||||
| 242 | } | ||||
| 243 | |||||
| 244 | sub next_open_day { | ||||
| 245 | my ( $self, $dt ) = @_; | ||||
| 246 | my $base_date = $dt->clone(); | ||||
| 247 | |||||
| 248 | $base_date->add(days => 1); | ||||
| 249 | |||||
| 250 | while ($self->is_holiday($base_date)) { | ||||
| 251 | $base_date->add(days => 1); | ||||
| 252 | } | ||||
| 253 | |||||
| 254 | return $base_date; | ||||
| 255 | } | ||||
| 256 | |||||
| 257 | sub prev_open_day { | ||||
| 258 | my ( $self, $dt ) = @_; | ||||
| 259 | my $base_date = $dt->clone(); | ||||
| 260 | |||||
| 261 | $base_date->add(days => -1); | ||||
| 262 | |||||
| 263 | while ($self->is_holiday($base_date)) { | ||||
| 264 | $base_date->add(days => -1); | ||||
| 265 | } | ||||
| 266 | |||||
| 267 | return $base_date; | ||||
| 268 | } | ||||
| 269 | |||||
| 270 | sub days_between { | ||||
| 271 | my $self = shift; | ||||
| 272 | my $start_dt = shift; | ||||
| 273 | my $end_dt = shift; | ||||
| 274 | |||||
| 275 | if ( $start_dt->compare($end_dt) > 0 ) { | ||||
| 276 | # swap dates | ||||
| 277 | my $int_dt = $end_dt; | ||||
| 278 | $end_dt = $start_dt; | ||||
| 279 | $start_dt = $int_dt; | ||||
| 280 | } | ||||
| 281 | |||||
| 282 | |||||
| 283 | # start and end should not be closed days | ||||
| 284 | my $days = $start_dt->delta_days($end_dt)->delta_days; | ||||
| 285 | for (my $dt = $start_dt->clone(); | ||||
| 286 | $dt <= $end_dt; | ||||
| 287 | $dt->add(days => 1) | ||||
| 288 | ) { | ||||
| 289 | if ($self->is_holiday($dt)) { | ||||
| 290 | $days--; | ||||
| 291 | } | ||||
| 292 | } | ||||
| 293 | return DateTime::Duration->new( days => $days ); | ||||
| 294 | |||||
| 295 | } | ||||
| 296 | |||||
| 297 | sub hours_between { | ||||
| 298 | my ($self, $start_date, $end_date) = @_; | ||||
| 299 | my $start_dt = $start_date->clone(); | ||||
| 300 | my $end_dt = $end_date->clone(); | ||||
| 301 | my $duration = $end_dt->delta_ms($start_dt); | ||||
| 302 | $start_dt->truncate( to => 'day' ); | ||||
| 303 | $end_dt->truncate( to => 'day' ); | ||||
| 304 | # NB this is a kludge in that it assumes all days are 24 hours | ||||
| 305 | # However for hourly loans the logic should be expanded to | ||||
| 306 | # take into account open/close times then it would be a duration | ||||
| 307 | # of library open hours | ||||
| 308 | my $skipped_days = 0; | ||||
| 309 | for (my $dt = $start_dt->clone(); | ||||
| 310 | $dt <= $end_dt; | ||||
| 311 | $dt->add(days => 1) | ||||
| 312 | ) { | ||||
| 313 | if ($self->is_holiday($dt)) { | ||||
| 314 | ++$skipped_days; | ||||
| 315 | } | ||||
| 316 | } | ||||
| 317 | if ($skipped_days) { | ||||
| 318 | $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days)); | ||||
| 319 | } | ||||
| 320 | |||||
| 321 | return $duration; | ||||
| 322 | |||||
| 323 | } | ||||
| 324 | |||||
| 325 | sub set_daysmode { | ||||
| 326 | my ( $self, $mode ) = @_; | ||||
| 327 | |||||
| 328 | # if not testing this is a no op | ||||
| 329 | if ( $self->{test} ) { | ||||
| 330 | $self->{days_mode} = $mode; | ||||
| 331 | } | ||||
| 332 | |||||
| 333 | return; | ||||
| 334 | } | ||||
| 335 | |||||
| 336 | sub clear_weekly_closed_days { | ||||
| 337 | my $self = shift; | ||||
| 338 | $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ]; # Sunday only | ||||
| 339 | return; | ||||
| 340 | } | ||||
| 341 | |||||
| 342 | sub add_holiday { | ||||
| 343 | my $self = shift; | ||||
| 344 | my $new_dt = shift; | ||||
| 345 | my @dt = $self->single_holidays->as_list; | ||||
| 346 | push @dt, $new_dt; | ||||
| 347 | $self->{single_holidays} = | ||||
| 348 | DateTime::Set->from_datetimes( dates => \@dt ); | ||||
| 349 | $single_holidays = $self->{single_holidays}; | ||||
| 350 | |||||
| 351 | return; | ||||
| 352 | } | ||||
| 353 | |||||
| 354 | 1 | 3µs | 1; | ||
| 355 | __END__ |