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