Filename | /usr/share/koha/lib/Koha/Calendar.pm |
Statements | Executed 31 statements in 2.34ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 4.07ms | 29.8ms | BEGIN@7 | Koha::Calendar::
1 | 1 | 1 | 2.58ms | 3.90ms | BEGIN@11 | Koha::Calendar::
1 | 1 | 1 | 77µs | 144µs | BEGIN@4 | Koha::Calendar::
1 | 1 | 1 | 18µs | 23µs | BEGIN@9 | Koha::Calendar::
1 | 1 | 1 | 18µs | 22µs | BEGIN@2 | Koha::Calendar::
1 | 1 | 1 | 16µs | 80µs | BEGIN@10 | Koha::Calendar::
1 | 1 | 1 | 16µs | 17µs | BEGIN@8 | Koha::Calendar::
1 | 1 | 1 | 14µs | 68µs | BEGIN@4.10 | Koha::Calendar::
1 | 1 | 1 | 13µs | 16µs | BEGIN@6 | Koha::Calendar::
1 | 1 | 1 | 12µs | 26µs | BEGIN@3 | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | _init | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | _mockinit | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | addDate | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | addDays | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | addHours | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | add_holiday | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | clear_weekly_closed_days | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | days_between | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | hours_between | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | is_holiday | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | new | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | next_open_day | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | prev_open_day | Koha::Calendar::
0 | 0 | 0 | 0s | 0s | set_daysmode | Koha::Calendar::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package Koha::Calendar; | ||||
2 | 3 | 34µs | 2 | 27µs | # spent 22µs (18+4) within Koha::Calendar::BEGIN@2 which was called:
# once (18µs+4µs) by C4::Circulation::BEGIN@49 at line 2 # spent 22µs making 1 call to Koha::Calendar::BEGIN@2
# spent 4µs making 1 call to strict::import |
3 | 3 | 27µs | 2 | 40µs | # spent 26µs (12+14) within Koha::Calendar::BEGIN@3 which was called:
# once (12µs+14µs) by C4::Circulation::BEGIN@49 at line 3 # spent 26µs making 1 call to Koha::Calendar::BEGIN@3
# spent 14µs making 1 call to warnings::import |
4 | 6 | 115µs | 3 | 266µs | use 5.010; # spent 144µs making 1 call to Koha::Calendar::BEGIN@4
# spent 68µs making 1 call to Koha::Calendar::BEGIN@4.10
# spent 54µs making 1 call to feature::import |
5 | |||||
6 | 3 | 28µs | 2 | 18µs | # spent 16µs (13+3) within Koha::Calendar::BEGIN@6 which was called:
# once (13µs+3µs) by C4::Circulation::BEGIN@49 at line 6 # spent 16µs making 1 call to Koha::Calendar::BEGIN@6
# spent 3µs making 1 call to UNIVERSAL::import |
7 | 3 | 141µs | 2 | 29.8ms | # spent 29.8ms (4.07+25.7) within Koha::Calendar::BEGIN@7 which was called:
# once (4.07ms+25.7ms) by C4::Circulation::BEGIN@49 at line 7 # spent 29.8ms making 1 call to Koha::Calendar::BEGIN@7
# spent 7µs making 1 call to UNIVERSAL::import |
8 | 3 | 28µs | 2 | 18µs | # spent 17µs (16+1) within Koha::Calendar::BEGIN@8 which was called:
# once (16µs+1µs) by C4::Circulation::BEGIN@49 at line 8 # spent 17µs making 1 call to Koha::Calendar::BEGIN@8
# spent 2µs making 1 call to UNIVERSAL::import |
9 | 3 | 53µs | 2 | 28µs | # spent 23µs (18+5) within Koha::Calendar::BEGIN@9 which was called:
# once (18µs+5µs) by C4::Circulation::BEGIN@49 at line 9 # spent 23µs making 1 call to Koha::Calendar::BEGIN@9
# spent 5µs making 1 call to C4::Context::import |
10 | 3 | 34µs | 2 | 144µs | # spent 80µs (16+64) within Koha::Calendar::BEGIN@10 which was called:
# once (16µs+64µs) by C4::Circulation::BEGIN@49 at line 10 # spent 80µs making 1 call to Koha::Calendar::BEGIN@10
# spent 64µs making 1 call to Exporter::import |
11 | 3 | 1.87ms | 2 | 3.95ms | # spent 3.90ms (2.58+1.33) within Koha::Calendar::BEGIN@11 which was called:
# once (2.58ms+1.33ms) by C4::Circulation::BEGIN@49 at line 11 # spent 3.90ms making 1 call to Koha::Calendar::BEGIN@11
# spent 48µ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__ |