Bug 24545: Fix license statements
[srvgit] / Koha / Calendar.pm
index 4b48ae2..dfc358d 100644 (file)
@@ -7,7 +7,7 @@ use DateTime;
 use DateTime::Set;
 use DateTime::Duration;
 use C4::Context;
-use Koha::Cache;
+use Koha::Caches;
 use Carp;
 
 sub new {
@@ -47,7 +47,7 @@ sub _init {
           1;
     }
 
-    $self->{days_mode}       = C4::Context->preference('useDaysMode');
+    $self->{days_mode}       ||= C4::Context->preference('useDaysMode');
     $self->{test}            = 0;
     return;
 }
@@ -72,7 +72,7 @@ sub exception_holidays {
             day       => $day,
             month     => $month,
             year      => $year,
-            time_zone => C4::Context->tz()
+            time_zone => "floating",
           )->truncate( to => 'day' );
     }
     $self->{exception_holidays} =
@@ -118,14 +118,14 @@ sub single_holidays {
                     day       => $day,
                     month     => $month,
                     year      => $year,
-                    time_zone => C4::Context->tz()
+                    time_zone => 'floating',
                 )->truncate( to => 'day' );
                 push @ymd_arr, $dt->ymd('');
             }
             $single_holidays->{$br} = \@ymd_arr;
         }    # br
         $cache->set_in_cache( 'single_holidays', $single_holidays,
-            76800 )    #24 hrs ;
+            { expiry => 76800 } )    #24 hrs ;
     }
     my $holidays  = ( $single_holidays->{$branchcode} );
     for my $hols  (@$holidays ) {
@@ -144,7 +144,6 @@ sub addDate {
 
     $unit ||= 'days'; # default days ?
     my $dt;
-
     if ( $unit eq 'hours' ) {
         # Fixed for legacy support. Should be set as a branch parameter
         my $return_by_hour = 10;
@@ -154,7 +153,6 @@ sub addDate {
         # days
         $dt = $self->addDays($startdate, $add_duration);
     }
-
     return $dt;
 }
 
@@ -171,9 +169,9 @@ sub addHours {
           $self->is_holiday($base_date) ) {
 
         if ( $hours_duration->is_negative() ) {
-            $base_date = $self->prev_open_day($base_date);
+            $base_date = $self->prev_open_days($base_date, 1);
         } else {
-            $base_date = $self->next_open_day($base_date);
+            $base_date = $self->next_open_days($base_date, 1);
         }
 
         $base_date->set_hour($return_by_hour);
@@ -196,30 +194,36 @@ sub addDays {
 
         if ( $days_duration->is_negative() ) {
             while ($days) {
-                $base_date = $self->prev_open_day($base_date);
+                $base_date = $self->prev_open_days($base_date, 1);
                 --$days;
             }
         } else {
             while ($days) {
-                $base_date = $self->next_open_day($base_date);
+                $base_date = $self->next_open_days($base_date, 1);
                 --$days;
             }
         }
 
-    } else { # Days or Datedue
+    } else { # Days, Datedue or Dayweek
         # use straight days, then use calendar to push
-        # the date to the next open day if Datedue
+        # the date to the next open day as appropriate
+        # if Datedue or Dayweek
         $base_date->add_duration($days_duration);
 
-        if ( $self->{days_mode} eq 'Datedue' ) {
-            # Datedue, then use the calendar to push
+        if ( $self->{days_mode} eq 'Datedue' ||
+            $self->{days_mode} eq 'Dayweek') {
+            # Datedue or Dayweek, then use the calendar to push
             # the date to the next open day if holiday
             if ( $self->is_holiday($base_date) ) {
-
+                my $dow = $base_date->day_of_week;
+                my $days = $days_duration->in_units('days');
+                # Is it a period based on weeks
+                my $push_amt = $days % 7 == 0 ?
+                    $self->get_push_amt($base_date) : 1;
                 if ( $days_duration->is_negative() ) {
-                    $base_date = $self->prev_open_day($base_date);
+                    $base_date = $self->prev_open_days($base_date, $push_amt);
                 } else {
-                    $base_date = $self->next_open_day($base_date);
+                    $base_date = $self->next_open_days($base_date, $push_amt);
                 }
             }
         }
@@ -228,6 +232,18 @@ sub addDays {
     return $base_date;
 }
 
+sub get_push_amt {
+    my ( $self, $base_date) = @_;
+
+    my $dow = $base_date->day_of_week;
+    return (
+        # We're using Dayweek useDaysMode option
+        $self->{days_mode} eq 'Dayweek' &&
+        # It's not a permanently closed day
+        !$self->{weekly_closed_days}->[$dow] == 1
+    ) ? 7 : 1;
+}
+
 sub is_holiday {
     my ( $self, $dt ) = @_;
 
@@ -235,6 +251,8 @@ sub is_holiday {
     my $day   = $localdt->day;
     my $month = $localdt->month;
 
+    #Change timezone to "floating" before doing any calculations or comparisons
+    $localdt->set_time_zone("floating");
     $localdt->truncate( to => 'day' );
 
 
@@ -268,85 +286,99 @@ sub is_holiday {
     return 0;
 }
 
-sub next_open_day {
-    my ( $self, $dt ) = @_;
+sub next_open_days {
+    my ( $self, $dt, $to_add ) = @_;
     my $base_date = $dt->clone();
 
-    $base_date->add(days => 1);
-
+    $base_date->add(days => $to_add);
     while ($self->is_holiday($base_date)) {
-        $base_date->add(days => 1);
+        my $add_next = $self->get_push_amt($base_date);
+        $base_date->add(days => $add_next);
     }
-
     return $base_date;
 }
 
-sub prev_open_day {
-    my ( $self, $dt ) = @_;
+sub prev_open_days {
+    my ( $self, $dt, $to_sub ) = @_;
     my $base_date = $dt->clone();
 
-    $base_date->add(days => -1);
+    # It feels logical to be passed a positive number, though we're
+    # subtracting, so do the right thing
+    $to_sub = $to_sub > 0 ? 0 - $to_sub : $to_sub;
+
+    $base_date->add(days => $to_sub);
 
     while ($self->is_holiday($base_date)) {
-        $base_date->add(days => -1);
+        my $sub_next = $self->get_push_amt($base_date);
+        # Ensure we're subtracting when we need to be
+        $sub_next = $sub_next > 0 ? 0 - $sub_next : $sub_next;
+        $base_date->add(days => $sub_next);
     }
 
     return $base_date;
 }
 
+sub days_forward {
+    my $self     = shift;
+    my $start_dt = shift;
+    my $num_days = shift;
+
+    return $start_dt unless $num_days > 0;
+
+    my $base_dt = $start_dt->clone();
+
+    while ($num_days--) {
+        $base_dt = $self->next_open_days($base_dt, 1);
+    }
+
+    return $base_dt;
+}
+
 sub days_between {
     my $self     = shift;
     my $start_dt = shift;
     my $end_dt   = shift;
 
-    if ( $start_dt->compare($end_dt) > 0 ) {
-        # swap dates
-        my $int_dt = $end_dt;
-        $end_dt = $start_dt;
-        $start_dt = $int_dt;
+    # Change time zone for date math and swap if needed
+    $start_dt = $start_dt->clone->set_time_zone('floating');
+    $end_dt = $end_dt->clone->set_time_zone('floating');
+    if( $start_dt->compare($end_dt) > 0 ) {
+        ( $start_dt, $end_dt ) = ( $end_dt, $start_dt );
     }
 
-
     # start and end should not be closed days
-    my $days = $start_dt->delta_days($end_dt)->delta_days;
-    for (my $dt = $start_dt->clone();
-        $dt <= $end_dt;
-        $dt->add(days => 1)
-    ) {
-        if ($self->is_holiday($dt)) {
-            $days--;
-        }
+    my $delta_days = $start_dt->delta_days($end_dt)->delta_days;
+    while( $start_dt->compare($end_dt) < 1 ) {
+        $delta_days-- if $self->is_holiday($start_dt);
+        $start_dt->add( days => 1 );
     }
-    return DateTime::Duration->new( days => $days );
-
+    return DateTime::Duration->new( days => $delta_days );
 }
 
 sub hours_between {
     my ($self, $start_date, $end_date) = @_;
-    my $start_dt = $start_date->clone();
-    my $end_dt = $end_date->clone();
+    my $start_dt = $start_date->clone()->set_time_zone('floating');
+    my $end_dt = $end_date->clone()->set_time_zone('floating');
+
     my $duration = $end_dt->delta_ms($start_dt);
     $start_dt->truncate( to => 'day' );
     $end_dt->truncate( to => 'day' );
+
     # NB this is a kludge in that it assumes all days are 24 hours
     # However for hourly loans the logic should be expanded to
     # take into account open/close times then it would be a duration
     # of library open hours
     my $skipped_days = 0;
-    for (my $dt = $start_dt->clone();
-        $dt <= $end_dt;
-        $dt->add(days => 1)
-    ) {
-        if ($self->is_holiday($dt)) {
-            ++$skipped_days;
-        }
+    while( $start_dt->compare($end_dt) < 1 ) {
+        $skipped_days++ if $self->is_holiday($start_dt);
+        $start_dt->add( days => 1 );
     }
+
     if ($skipped_days) {
         $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
     }
 
     return $duration;
-
 }
 
 sub set_daysmode {
@@ -423,6 +455,14 @@ C<$offset> is a DateTime::Duration to add to it
 
 C<$return_by_hour> is an integer value representing the opening hour for the branch
 
+=head2 get_push_amt
+
+    my $amt = $calendar->get_push_amt($date)
+
+C<$date> is a DateTime object representing a closed return date
+
+Using the days_mode syspref value and the nature of the closed return
+date, return how many days we should jump forward to find another return date
 
 =head2 addDays
 
@@ -458,23 +498,37 @@ $duration = $calendar->days_between($start_dt, $end_dt);
 
 Passed two dates returns a DateTime::Duration object measuring the length between them
 ignoring closed days. Always returns a positive number irrespective of the
-relative order of the parameters
+relative order of the parameters.
+
+Note: This routine assumes neither the passed start_dt nor end_dt can be a closed day
+
+=head2 hours_between
+
+$duration = $calendar->hours_between($start_dt, $end_dt);
+
+Passed two dates returns a DateTime::Duration object measuring the length between them
+ignoring closed days. Always returns a positive number irrespective of the
+relative order of the parameters.
+
+Note: This routine assumes neither the passed start_dt nor end_dt can be a closed day
 
-=head2 next_open_day
+=head2 next_open_days
 
-$datetime = $calendar->next_open_day($duedate_dt)
+$datetime = $calendar->next_open_days($duedate_dt, $to_add)
 
-Passed a Datetime returns another Datetime representing the next open day. It is
-intended for use to calculate the due date when useDaysMode syspref is set to either
-'Datedue' or 'Calendar'.
+Passed a Datetime and number of days,  returns another Datetime representing
+the next open day after adding the passed number of days. It is intended for
+use to calculate the due date when useDaysMode syspref is set to either
+'Datedue', 'Calendar' or 'Dayweek'.
 
-=head2 prev_open_day
+=head2 prev_open_days
 
-$datetime = $calendar->prev_open_day($duedate_dt)
+$datetime = $calendar->prev_open_days($duedate_dt, $to_sub)
 
-Passed a Datetime returns another Datetime representing the previous open day. It is
-intended for use to calculate the due date when useDaysMode syspref is set to either
-'Datedue' or 'Calendar'.
+Passed a Datetime and a number of days, returns another Datetime
+representing the previous open day after subtracting the number of passed
+days. It is intended for use to calculate the due date when useDaysMode
+syspref is set to either 'Datedue', 'Calendar' or 'Dayweek'.
 
 =head2 set_daysmode
 
@@ -509,15 +563,15 @@ Colin Campbell colin.campbell@ptfs-europe.com
 
 Copyright (c) 2011 PTFS-Europe Ltd All rights reserved
 
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 2 of the License, or
+Koha is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version.
 
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+Koha is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
+along with Koha; if not, see <http://www.gnu.org/licenses>.