Bug 5549 : Calendar needed a change array_ref to hash_ref
[koha.git] / Koha / Calendar.pm
index b901326..bdd4b8f 100644 (file)
@@ -4,6 +4,8 @@ use warnings;
 use 5.010;
 
 use DateTime;
+use DateTime::Set;
+use DateTime::Duration;
 use C4::Context;
 use Carp;
 use Readonly;
@@ -11,14 +13,18 @@ use Readonly;
 sub new {
     my ( $classname, %options ) = @_;
     my $self = {};
+    bless $self, $classname;
     for my $o_name ( keys %options ) {
         my $o = lc $o_name;
         $self->{$o} = $options{$o_name};
     }
+    if ( exists $options{TEST_MODE} ) {
+        $self->_mockinit();
+        return $self;
+    }
     if ( !defined $self->{branchcode} ) {
         croak 'No branchcode argument passed to Koha::Calendar->new';
     }
-    bless $self, $classname;
     $self->_init();
     return $self;
 }
@@ -31,17 +37,16 @@ sub _init {
 'SELECT * from repeatable_holidays WHERE branchcode = ? AND ISNULL(weekday) = ?'
     );
     $repeat_sth->execute( $branch, 0 );
-    $self->{weekly_closed_days} = [];
+    $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];
     Readonly::Scalar my $sunday => 7;
     while ( my $tuple = $repeat_sth->fetchrow_hashref ) {
-        my $day = $tuple->{weekday} == 0 ? $sunday : $tuple->{weekday};
-        push @{ $self->{weekly_closed_days} }, $day;
+        $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1;
     }
     $repeat_sth->execute( $branch, 1 );
-    $self->{day_month_closed_days} = [];
+    $self->{day_month_closed_days} = {};
     while ( my $tuple = $repeat_sth->fetchrow_hashref ) {
-        push @{ $self->{day_month_closed_days} },
-          { day => $tuple->{day}, month => $tuple->{month}, };
+        $self->{day_month_closed_days}->{ $tuple->{day} }->{ $tuple->{month} } =
+          1;
     }
     my $special = $dbh->prepare(
 'SELECT day, month, year, title, description FROM special_holidays WHERE ( branchcode = ? ) AND (isexception = ?)'
@@ -73,33 +78,50 @@ sub _init {
           )->truncate( to => 'day' );
     }
     $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates );
+    $self->{days_mode} = C4::Context->preference('useDaysMode');
     return;
 }
 
 sub addDate {
     my ( $self, $base_date, $add_duration, $unit ) = @_;
-    my $days_mode = C4::Context->preference('useDaysMode');
+    if ( ref $add_duration ne 'DateTime::Duration' ) {
+        $add_duration = DateTime::Duration->new( days => $add_duration );
+    }
+    my $days_mode = $self->{days_mode};
     Readonly::Scalar my $return_by_hour => 10;
+    my $day_dur = DateTime::Duration->new( days => 1 );
+    if ( $add_duration->is_negative() ) {
+        $day_dur->inverse();
+    }
     if ( $days_mode eq 'Datedue' ) {
 
         my $dt = $base_date + $add_duration;
         while ( $self->is_holiday($dt) ) {
 
             # TODOP if hours set to 10 am
-            $dt->add( days => 1 );
+            $dt->add_duration($day_dur);
             if ( $unit eq 'hours' ) {
                 $dt->set_hour($return_by_hour);    # Staffs specific
             }
         }
         return $dt;
     } elsif ( $days_mode eq 'Calendar' ) {
-        my $days = $add_duration->in_units('days');
-        while ($days) {
-            $base_date->add( days => 1 );
-            if ( $self->is_holiday($base_date) ) {
-                next;
-            } else {
-                --$days;
+        if ( $unit eq 'hours' ) {
+            $base_date->add_duration($add_duration);
+            while ( $self->is_holiday($base_date) ) {
+                $base_date->add_duration($day_dur);
+
+            }
+
+        } else {
+            my $days = $add_duration->in_units('days');
+            while ($days) {
+                $base_date->add_duration($day_dur);
+                if ( $self->is_holiday($base_date) ) {
+                    next;
+                } else {
+                    --$days;
+                }
             }
         }
         if ( $unit eq 'hours' ) {
@@ -117,17 +139,17 @@ sub addDate {
 sub is_holiday {
     my ( $self, $dt ) = @_;
     my $dow = $dt->day_of_week;
-    my @matches = grep { $_ == $dow } @{ $self->{weekly_closed_days} };
-    if (@matches) {
+    if ( $dow == 7 ) {
+        $dow = 0;
+    }
+    if ( $self->{weekly_closed_days}->[$dow] == 1 ) {
         return 1;
     }
-    $dt->truncate('days');
+    $dt->truncate( to => 'days' );
     my $day   = $dt->day;
     my $month = $dt->month;
-    for my $dm ( @{ $self->{day_month_closed_days} } ) {
-        if ( $month == $dm->{month} && $day == $dm->{day} ) {
-            return 1;
-        }
+    if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) {
+        return 1;
     }
     if ( $self->{exception_holidays}->contains($dt) ) {
         return 1;
@@ -140,6 +162,46 @@ sub is_holiday {
     return 0;
 }
 
+sub days_between {
+    my $self     = shift;
+    my $start_dt = shift;
+    my $end_dt   = shift;
+    $start_dt->truncate( to => 'hours' );
+    $end_dt->truncate( to => 'hours' );
+
+    # start and end should not be closed days
+    my $duration = $end_dt - $start_dt;
+    $start_dt->truncate( to => 'days' );
+    $end_dt->truncate( to => 'days' );
+    while ( DateTime->compare( $start_dt, $end_dt ) == -1 ) {
+        $start_dt->add( days => 1 );
+        if ( $self->is_holiday($start_dt) ) {
+            $duration->subtract( days => 1 );
+        }
+    }
+    return $duration;
+
+}
+
+sub _mockinit {
+    my $self = shift;
+    $self->{weekly_closed_days} = [ 1, 0, 0, 0, 0, 0, 0 ];    # Sunday only
+    $self->{day_month_closed_days} = { 6 => { 16 => 1, } };
+    my $dates = [];
+    $self->{exception_holidays} =
+      DateTime::Set->from_datetimes( dates => $dates );
+    my $special = DateTime->new(
+        year      => 2011,
+        month     => 6,
+        day       => 1,
+        time_zone => 'Europe/London',
+    );
+    push @{$dates}, $special;
+    $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates );
+    $self->{days_mode} = 'Calendar';
+    return;
+}
+
 1;
 __END__
 
@@ -198,6 +260,13 @@ $yesno = $calendar->is_holiday($dt);
 passed at DateTime object returns 1 if it is a closed day
 0 if not according to the calendar
 
+=head2 days_between
+
+$duration = $calendar->days_between($start_dt, $end_dt);
+
+Passed two dates returns a DateTime::Duration object measuring the length between them
+ignoring closed days
+
 =head1 DIAGNOSTICS
 
 Will croak if not passed a branchcode in new