From 08a2678ea37a59fba6c7ad64ec6618c975f0540a Mon Sep 17 00:00:00 2001
From: mehdi <mehdi@inlibro.com>
Date: Thu, 12 Jan 2017 11:57:33 -0500
Subject: [PATCH] Bug 17015 - DiscreteCalendar UI, Back-End and necessary
 scripts

---
 Koha/DiscreteCalendar.pm                           | 968 +++++++++++++++++++++
 .../bug_17015_koha_discrete_calendar.sql           |  14 +
 .../atomicupdate/generate_discrete_calendar.perl   | 165 ++++
 .../prog/en/modules/tools/discrete_calendar.tt     | 617 +++++++++++++
 misc/cronjobs/add_days_discrete_calendar.pl        | 113 +++
 tools/discrete_calendar.pl                         | 157 ++++
 6 files changed, 2034 insertions(+)
 create mode 100644 Koha/DiscreteCalendar.pm
 create mode 100644 installer/data/mysql/atomicupdate/bug_17015_koha_discrete_calendar.sql
 create mode 100755 installer/data/mysql/atomicupdate/generate_discrete_calendar.perl
 create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt
 create mode 100755 misc/cronjobs/add_days_discrete_calendar.pl
 create mode 100755 tools/discrete_calendar.pl

diff --git a/Koha/DiscreteCalendar.pm b/Koha/DiscreteCalendar.pm
new file mode 100644
index 0000000..7a21653
--- /dev/null
+++ b/Koha/DiscreteCalendar.pm
@@ -0,0 +1,968 @@
+package Koha::DiscreteCalendar;
+
+# This file is part of Koha.
+#
+# 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.
+#
+# 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 Koha; if not, see <http://www.gnu.org/licenses>.
+
+#####Sets holiday periods for each branch. Datedues will be extended if branch is closed -TG
+use strict;
+use warnings;
+
+use CGI qw ( -utf8 );
+use Carp;
+use DateTime;
+use DateTime::Format::Strptime;
+
+use C4::Context;
+use C4::Output;
+use Koha::Database;
+use Koha::DateUtils;
+
+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 ( !defined $self->{branchcode} ) {
+        croak 'No branchcode argument passed to Koha::DiscreteCalendar->new';
+    }
+    $self->_init();
+
+    return $self;
+}
+
+sub _init {
+    my $self = shift;
+    $self->{days_mode} = C4::Context->preference('useDaysMode');
+    #If the branchcode doesn't exist we use the default calendar.
+    my $schema = Koha::Database->new->schema;
+    my $branchcode = $self->{branchcode};
+    my $dtf = $schema->storage->datetime_parser;
+    my $today = $dtf->format_datetime(DateTime->today);
+    my $rs = $schema->resultset('DiscreteCalendar')->single(
+        {
+            branchcode  => $branchcode,
+            date        => $today
+        }
+    );
+    #use default if no calendar is found
+    if (!$rs){
+        $self->{branchcode} = 'DFLT';
+    }
+
+}
+
+sub getDatesInfo {
+    my $self = shift;
+    my $branchcode = $self->{branchcode};
+    my @datesInfos =();
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode
+        },
+        {
+            select  => [ 'date', { DATE => 'date' } ],
+            as      => [qw/ date date /],
+            columns =>[ qw/ holidaytype openhour closehour note/]
+        },
+    );
+
+    while (my $date = $rs->next()){
+        my $outputdate = dt_from_string( $date->date(), 'iso');
+        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
+        push @datesInfos, {
+            date        => $date->date(),
+            outputdate  => $outputdate,
+            holidaytype => $date->holidaytype() ,
+            openhour    => $date->openhour(),
+            closehour   => $date->closehour(),
+            note        => $date->note()
+        };
+    }
+
+    return @datesInfos;
+}
+#This methode will copy everything from a given branch found to the new branch
+sub add_new_branch {
+    my ($self, $copyBranch, $newBranch) = @_;
+    $copyBranch = 'DFLT' unless $copyBranch;
+    my $schema = Koha::Database->new->schema;
+
+    my $branch_rs = $schema->resultset('DiscreteCalendar')->search({
+            branchcode => $copyBranch
+    });
+
+    while(my $row = $branch_rs->next()){
+        $schema->resultset('DiscreteCalendar')->create({
+            date        => $row->date(),
+            branchcode  => $newBranch,
+            isopened    => $row->isopened(),
+            holidaytype => $row->holidaytype(),
+            openhour    => $row->openhour(),
+            closehour   => $row->closehour(),
+        });
+    }
+
+}
+#DiscreteCalendar data transfer object (DTO)
+sub get_date_info {
+    my ($self, $date) = @_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+    my $dtf = $schema->storage->datetime_parser;
+    #String dates for Database usage
+    my $date_string = $dtf->format_datetime($date);
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+        },
+        {
+            select  => [ 'date', { DATE => 'date' } ],
+            as      => [qw/ date date /],
+            where   => \['DATE(?) = date', $date_string ],
+            columns =>[ qw/ branchcode holidaytype openhour closehour note/]
+        },
+    );
+    my $dateDTO;
+    while (my $date = $rs->next()){
+        $dateDTO = {
+            date        => $date->date(),
+            branchcode  => $date->branchcode(),
+            holidaytype => $date->holidaytype() ,
+            openhour    => $date->openhour(),
+            closehour   => $date->closehour(),
+            note        => $date->note()
+        };
+    }
+
+    return $dateDTO;
+}
+
+
+sub getMaxDate {
+    my $self       = shift;
+    my $branchcode     = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode
+        },
+        {
+            select => [{ MAX => 'date' } ],
+            as     => [qw/ max /],
+        }
+    );
+
+    return $rs->next()->get_column('max');
+}
+
+sub getMinDate {
+    my $self       = shift;
+    my $branchcode     = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode
+        },
+        {
+            select => [{ MIN => 'date' } ],
+            as     => [qw/ min /],
+        }
+    );
+
+    return $rs->next()->get_column('min');
+}
+
+sub get_single_holidays {
+    my $self = shift;
+    my $branchcode = $self->{branchcode};
+    my @single_holidays;
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            holidaytype => 'E'
+        },
+        {
+            select => [{ DATE => 'date' }, 'note' ],
+            as     => [qw/ date note/],
+        }
+    );
+    while (my $date = $rs->next()){
+        my $outputdate = dt_from_string($date->date(), 'iso');
+        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
+        push @single_holidays, {
+            date =>  $date->date(),
+            outputdate => $outputdate,
+            note => $date->note()
+        }
+    }
+
+    return @single_holidays;
+}
+
+sub get_float_holidays {
+    my $self = shift;
+    my $branchcode = $self->{branchcode};
+    my @float_holidays;
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            holidaytype => 'F'
+        },
+        {
+            select => [{ DATE => 'date' }, 'note' ],
+            as     => [qw/ date note/],
+        }
+    );
+    while (my $date = $rs->next()){
+        my $outputdate = dt_from_string($date->date(), 'iso');
+        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
+        push @float_holidays, {
+            date        =>  $date->date(),
+            outputdate  => $outputdate,
+            note        => $date->note()
+        }
+    }
+
+    return @float_holidays;
+}
+
+sub get_need_valdation_holidays {
+    my $self = shift;
+    my $branchcode = $self->{branchcode};
+    my @need_validation_holidays;
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            holidaytype => 'N'
+        },
+        {
+            select => [{ DATE => 'date' }, 'note' ],
+            as     => [qw/ date note/],
+        }
+    );
+    while (my $date = $rs->next()){
+        my $outputdate = dt_from_string($date->date(), 'iso');
+        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
+        push @need_validation_holidays, {
+            date        =>  $date->date(),
+            outputdate  => $outputdate,
+            note        => $date->note()
+        }
+    }
+
+    return @need_validation_holidays;
+}
+
+sub get_day_month_holidays {
+    my $self = shift;
+    my $branchcode = $self->{branchcode};
+    my @repeatable_holidays;
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            holidaytype => 'R',
+
+        },
+        {
+            select  => \[ 'distinct DAY(date), MONTH(date), note'],
+            as      => [qw/ day month note/],
+        }
+    );
+
+    while (my $date = $rs->next()){
+        push @repeatable_holidays, {
+            day=> $date->get_column('day'),
+            month => $date->get_column('month'),
+            note => $date->note()
+        };
+    }
+
+    return @repeatable_holidays;
+}
+
+sub get_week_days_holidays {
+    my $self = shift;
+    my $branchcode = $self->{branchcode};
+    my @week_days;
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            holidaytype => 'W',
+            branchcode  => $branchcode,
+        },
+        {
+            select      => [{ DAYOFWEEK => 'date'}, 'note'],
+            as          => [qw/ weekday note /],
+            distinct    => 1,
+        }
+    );
+
+    while (my $date = $rs->next()){
+        push @week_days, {
+            weekday => ($date->get_column('weekday') -1),
+            note    => $date->note()
+        };
+    }
+
+    return @week_days;
+}
+=head1 edit_holiday
+
+Modifies a date or a range of dates
+
+C<$title> Is the title to be modified for the holiday formed by $year/$month/$day.
+
+C<$weekday> Is the day of week for the holiday
+
+C<$holidaytype> Is the type of the holiday :
+    E : Exception holiday, single day.
+    F : Floating holiday, different day each year.
+    N : Needs validation, copied float holiday from the past
+    R : Fixed holiday, repeated on same date.
+    W : Weekly holiday, same day of the week.
+
+C<$openHour> Is the opening hour.
+C<$closeHour> Is the closing hour.
+C<$startDate> Is the start of the range of dates.
+C<$endDate> Is the end of the range of dates.
+=back
+=cut
+
+sub edit_holiday {
+    my ($self, $title, $weekday, $holidaytype, $openHour, $closeHour, $startDate, $endDate, $deleteType) = @_;
+    my $branchcode = $self->{branchcode};
+    my $today = DateTime->today;
+    my $schema = Koha::Database->new->schema;
+    my $dtf = $schema->storage->datetime_parser;
+    #String dates for Database usage
+    my $startDate_String = $dtf->format_datetime($startDate);
+    my $endDate_String = $dtf->format_datetime($endDate);
+    $today = $dtf->format_datetime($today);
+
+    my %updateValues = (
+        isopened    => 0,
+        note        => $title,
+        holidaytype => $holidaytype,
+    );
+    $updateValues{openhour}  = $openHour if $openHour ne '';
+    $updateValues{closehour}  = $closeHour if $closeHour ne '';
+
+    if($holidaytype eq 'W') {
+        #Insert/Update weekly holidays
+        my $rs = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $branchcode,
+            },
+            {
+                where => \[ 'DAYOFWEEK(date) = ? and date >= ?', $weekday, $today],
+            }
+        );
+
+        while (my $date = $rs->next()){
+            $date->update(\%updateValues);
+        }
+    }elsif ($holidaytype eq 'E' || $holidaytype eq 'F' || $holidaytype eq 'N') {
+        #Insert/Update Exception Float and Needs Validation holidays
+        my $rs = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $branchcode,
+            },
+            {
+                where =>  \['date between DATE(?) and DATE(?) and date >= ?',$startDate_String, $endDate_String, $today]
+            }
+        );
+        while (my $date = $rs->next()){
+            $date->update(\%updateValues);
+        }
+
+    }elsif ($holidaytype eq 'R') {
+        #Insert/Update repeatable holidays
+        my $parser = DateTime::Format::Strptime->new(
+           pattern  => '%m-%d',
+           on_error => 'croak',
+        );
+        #Format the dates to have only month-day ex: 01-04 for January 4th
+        $startDate = $parser->format_datetime($startDate);
+        $endDate = $parser->format_datetime($endDate);
+        my $rs = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $branchcode,
+            },
+            {
+                where =>  \["(DATE_FORMAT(date,'\%m-\%d') BETWEEN ? AND ? ) AND date >= ?", $startDate, $endDate, $today],
+            }
+        );
+        while (my $date = $rs->next()){
+            $date->update(\%updateValues);
+        }
+
+    }else {
+        #Delete/Update date(s)
+        my $rs = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $branchcode,
+            },
+            {
+                where =>  \['date between DATE(?) and DATE(?) and date >= ?',$startDate_String, $endDate_String, $today],
+            }
+        );
+        #If none, the date(s) will be normal days, else,
+        if($holidaytype eq 'none'){
+            $updateValues{holidaytype}  ='';
+            $updateValues{isopened}  =1;
+        }else{
+            delete $updateValues{holidaytype};
+        }
+        while (my $date = $rs->next()){
+            if($deleteType){
+                if($date->holidaytype() eq 'W' && $startDate_String eq $endDate_String){
+                    $self->remove_weekly_holidays($weekday, \%updateValues, $today);
+                }elsif($date->holidaytype() eq 'R'){
+                    $self->remove_fixed_holidays($startDate, $endDate, \%updateValues, $today);
+                }
+            }else{
+                $date->update(\%updateValues);
+            }
+        }
+    }
+}
+
+sub remove_weekly_holidays {
+    my ($self, $weekday, $updateValues, $today) = @_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            isopened    => 0,
+            holidaytype => 'W'
+        },
+        {
+            where => \["DAYOFWEEK(date) = ? and date >= ?", $weekday,$today],
+        }
+    );
+
+    while (my $date = $rs->next()){
+        $date->update($updateValues);
+    }
+}
+
+sub remove_fixed_holidays {
+    my ($self, $startDate, $endDate, $updateValues, $today) = @_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+    my $parser = DateTime::Format::Strptime->new(
+        pattern   => '%m-%d',
+        on_error  => 'croak',
+    );
+    #Format the dates to have only month-day ex: 01-04 for January 4th
+    $startDate = $parser->format_datetime($startDate);
+    $endDate = $parser->format_datetime($endDate);
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            isopened    => 0,
+            holidaytype => 'R',
+        },
+        {
+            where =>  \["(DATE_FORMAT(date,'\%m-\%d') BETWEEN ? AND ? ) AND date >= ?", $startDate, $endDate, $today],
+        }
+    );
+
+    while (my $date = $rs->next()){
+        $date->update($updateValues);
+    }
+}
+
+sub copyToBranch {
+    my ($self,$newBranch) =@_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+
+    my $copyFrom = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode
+        },
+        {
+            columns     => [ qw/ date isopened note holidaytype openhour closehour /]
+        }
+    );
+    while (my $copyDate = $copyFrom->next()){
+        my $copyTo = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $newBranch,
+                date        => $copyDate->date(),
+            },
+            {
+                columns => [ qw/ date branchcode isopened note holidaytype openhour closehour /]
+            }
+        );
+        $copyTo->next()->update({
+            isopened    => $copyDate->isopened(),
+            holidaytype => $copyDate->holidaytype(),
+            note        => $copyDate->note(),
+            openhour    => $copyDate->openhour(),
+            closehour   => $copyDate->closehour()
+        });
+    }
+}
+
+sub isOpened {
+    my($self, $date) = @_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+    my $dtf = $schema->storage->datetime_parser;
+    $date= $dtf->format_datetime($date);
+    #if the date is not found
+    my $isOpened = -1;
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode => $branchcode,
+        },
+        {
+            where   => \['date = DATE(?)', $date]
+        }
+    );
+    $isOpened = $rs->next()->isopened() if $rs->count() != 0;
+
+    return $isOpened;
+}
+
+sub is_holiday {
+    my($self, $date) = @_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+    my $dtf = $schema->storage->datetime_parser;
+    $date= $dtf->format_datetime($date);
+    #if the date is not found
+    my $isHoliday = -1;
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode => $branchcode,
+        },
+        {
+            where   => \['date = DATE(?)', $date]
+        }
+    );
+
+    if($rs->count() != 0){
+        $isHoliday = 0 if $rs->first()->isopened();
+        $isHoliday = 1 if !$rs->first()->isopened();
+    }
+
+    return $isHoliday;
+}
+
+sub copyHoliday {
+    my ($self, $from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber) = @_;
+    my $branchcode = $self->{branchcode};
+    my $copyFromType =  $from_startDate && $from_endDate eq '' ? 'oneDay': 'range';
+    my $schema = Koha::Database->new->schema;
+    my $dtf = $schema->storage->datetime_parser;
+
+    if ($copyFromType eq 'oneDay'){
+        my $where;
+        $to_startDate = $dtf->format_datetime($to_startDate);
+        if ($to_startDate && $to_endDate) {
+            $to_endDate = $dtf->format_datetime($to_endDate);
+            $where = \["date between ? and ?", $to_startDate, $to_endDate];
+        } else {
+            $where = \['date = ?', $to_startDate];
+        }
+
+        $from_startDate = $dtf->format_datetime($from_startDate);
+        my $fromDate = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $branchcode,
+                date        => $from_startDate
+            }
+        );
+        my $toDates = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $branchcode,
+            },
+            {
+                where       => $where
+            }
+        );
+
+        my $copyDate = $fromDate->next();
+        while (my $date = $toDates->next()){
+            $date->update({
+                isopened    => $copyDate->isopened(),
+                holidaytype => $copyDate->holidaytype(),
+                note        => $copyDate->note(),
+                openhour    => $copyDate->openhour(),
+                closehour   => $copyDate->closehour()
+            })
+        }
+
+    }else{
+        my $endDate = dt_from_string($from_endDate);
+        $to_startDate = $dtf->format_datetime($to_startDate);
+        $to_endDate = $dtf->format_datetime($to_endDate);
+        if($daysnumber ==6){
+            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)){
+                my $formatedDate = $dtf->format_datetime($tempDate);
+                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
+                    {
+                        branchcode  => $branchcode,
+                        date        => $formatedDate,
+                    },
+                    {
+                        select  => [{ DAYOFWEEK => 'date' }],
+                        as      => [qw/ weekday /],
+                        columns =>[ qw/ holidaytype note openhour closehour note/]
+                    }
+                );
+                my $copyDate = $fromDate->next();
+                my $weekday = $copyDate->get_column('weekday');
+
+                my $toDate = $schema->resultset('DiscreteCalendar')->search(
+                    {
+                        branchcode  => $branchcode,
+
+                    },
+                    {
+                        where       => \['date between ? and ? and DAYOFWEEK(date) = ?',$to_startDate, $to_endDate, $weekday]
+                    }
+                );
+                my $copyToDate = $toDate->next();
+                $copyToDate->update({
+                    isopened    => $copyDate->isopened(),
+                    holidaytype => $copyDate->holidaytype(),
+                    note        => $copyDate->note(),
+                    openhour    => $copyDate->openhour(),
+                    closehour   => $copyDate->closehour()
+                });
+
+            }
+        }else{
+            my $to_startDate = dt_from_string($to_startDate);
+            my $to_endDate = dt_from_string($to_endDate);
+            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)){
+                my $from_formatedDate = $dtf->format_datetime($tempDate);
+                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
+                    {
+                        branchcode  => $branchcode,
+                        date        => $from_formatedDate,
+                    },
+                    {
+                        order_by    => { -asc => 'date' }
+                    }
+                );
+                my $to_formatedDate = $dtf->format_datetime($to_startDate);
+                my $toDate = $schema->resultset('DiscreteCalendar')->search(
+                    {
+                        branchcode  => $branchcode,
+                        date        => $to_formatedDate
+                    },
+                    {
+                        order_by    => { -asc => 'date' }
+                    }
+                );
+                my $copyDate = $fromDate->next();
+                $toDate->next()->update({
+                    isopened    => $copyDate->isopened(),
+                    holidaytype => $copyDate->holidaytype(),
+                    note        => $copyDate->note(),
+                    openhour    => $copyDate->openhour(),
+                    closehour   => $copyDate->closehour()
+                });
+                $to_startDate->add(days =>1);
+            }
+        }
+
+
+    }
+}
+
+sub days_between {
+    my ($self, $start_date, $end_date, ) = @_;
+    my $branchcode = $self->{branchcode};
+
+    if ( $start_date->compare($end_date) > 0 ) {
+        # swap dates
+        my $int_dt = $end_date;
+        $end_date = $start_date;
+        $start_date = $int_dt;
+    }
+
+    my $schema = Koha::Database->new->schema;
+    my $dtf = $schema->storage->datetime_parser;
+    $start_date = $dtf->format_datetime($start_date);
+    $end_date = $dtf->format_datetime($end_date);
+
+    my $days_between = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            isopened    => 1,
+        },
+        {
+            where       => \['date >= date(?) and date < date(?)',$start_date, $end_date]
+        }
+    );
+
+    return DateTime::Duration->new( days => $days_between->count());
+}
+
+sub next_open_day {
+    my ( $self, $date ) = @_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+    my $dtf = $schema->storage->datetime_parser;
+    $date = $dtf->format_datetime($date);
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            isopened    => 1,
+        },
+        {
+            where       => \['date > date(?)', $date],
+            order_by    => { -asc => 'date' },
+            rows        => 1
+        }
+    );
+    return dt_from_string( $rs->next()->date(), 'iso');
+}
+
+sub prev_open_day {
+    my ( $self, $date ) = @_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+    my $dtf = $schema->storage->datetime_parser;
+    $date = $dtf->format_datetime($date);
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            isopened    => 1,
+        },
+        {
+            where       => \['date < date(?)', $date],
+            order_by    => { -desc => 'date' },
+            rows        => 1
+        }
+    );
+    return dt_from_string( $rs->next()->date(), 'iso');
+}
+
+sub hours_between {
+    my ($self, $start_dt, $end_dt) = @_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+    my $dtf = $schema->storage->datetime_parser;
+    my $start_date = $start_dt->clone();
+    my $end_date = $end_dt->clone();
+    my $duration = $end_date->delta_ms($start_date);
+    $start_date->truncate( to => 'day' );
+    $end_date->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;
+    $start_date = $dtf->format_datetime($start_date);
+    $end_date = $dtf->format_datetime($end_date);
+    my $hours_between = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  =>  $branchcode,
+            isopened    => 0
+        },
+        {
+            where  => \[ 'date between ? and ?', $start_date, $end_date],
+        }
+    );
+
+    if ($skipped_days = $hours_between->count()) {
+        $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
+    }
+
+    return $duration;
+}
+
+sub open_hours_between {
+    my ($self, $start_date, $end_date) = @_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+    my $dtf = $schema->storage->datetime_parser;
+    $start_date = $dtf->format_datetime($start_date);
+    $end_date = $dtf->format_datetime($end_date);
+
+    my $working_hours_between = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            isopened    => 1,
+        },
+        {
+            select  => \['sum(time_to_sec(timediff(closehour, openhour)) / 3600)'],
+            as      => [qw /hours_between/],
+            where   => \['date BETWEEN DATE(?) AND DATE(?)', $start_date, $end_date]
+        }
+    );
+
+    my $loan_day = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+        },
+        {
+            where       => \['date = DATE(?)', $start_date],
+        }
+    );
+
+    my $return_day = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+        },
+        {
+            where       => \['date = DATE(?)', $end_date],
+        }
+    );
+
+    #Capture the time portion of the date
+    $start_date =~ /\s(.*)/;
+    my $loan_date_time = $1;
+    $end_date =~ /\s(.*)/;
+    my $return_date_time = $1;
+
+    my $not_used_hours = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            isopened    => 1,
+        },
+        {
+            select  => \[ '(time_to_sec(timediff(?, ?)) + time_to_sec(timediff(?, ?)) ) / 3600', $return_day->next()->closehour(), $return_date_time, $loan_date_time, $loan_day->next()->openhour()],
+            as      => [qw /not_used_hours/],
+        }
+    );
+
+    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
+}
+sub addDate {
+    my ( $self, $startdate, $add_duration, $unit ) = @_;
+
+    # Default to days duration (legacy support I guess)
+    if ( ref $add_duration ne 'DateTime::Duration' ) {
+        $add_duration = DateTime::Duration->new( days => $add_duration );
+    }
+
+    $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;
+
+        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
+    } else {
+        # days
+        $dt = $self->addDays($startdate, $add_duration);
+    }
+
+    return $dt;
+}
+
+sub addHours {
+    my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_;
+    my $base_date = $startdate->clone();
+
+    $base_date->add_duration($hours_duration);
+
+    # If we are using the calendar behave for now as if Datedue
+    # was the chosen option (current intended behaviour)
+
+    if ( $self->{days_mode} ne 'Days' &&
+    $self->is_holiday($base_date) ) {
+
+        if ( $hours_duration->is_negative() ) {
+            $base_date = $self->prev_open_day($base_date);
+        } else {
+            $base_date = $self->next_open_day($base_date);
+        }
+
+        $base_date->set_hour($return_by_hour);
+
+    }
+
+    return $base_date;
+}
+
+sub addDays {
+    my ( $self, $startdate, $days_duration ) = @_;
+    my $base_date = $startdate->clone();
+
+    $self->{days_mode} ||= q{};
+
+    if ( $self->{days_mode} eq 'Calendar' ) {
+        # use the calendar to skip all days the library is closed
+        # when adding
+        my $days = abs $days_duration->in_units('days');
+
+        if ( $days_duration->is_negative() ) {
+            while ($days) {
+                $base_date = $self->prev_open_day($base_date);
+                --$days;
+            }
+        } else {
+            while ($days) {
+                $base_date = $self->next_open_day($base_date);
+                --$days;
+            }
+        }
+
+    } else { # Days or Datedue
+        # use straight days, then use calendar to push
+        # the date to the next open day if Datedue
+        $base_date->add_duration($days_duration);
+
+        if ( $self->{days_mode} eq 'Datedue' ) {
+            # Datedue, then use the calendar to push
+            # the date to the next open day if holiday
+            if (!$self->isOpened($base_date) ) {
+
+                if ( $days_duration->is_negative() ) {
+                    $base_date = $self->prev_open_day($base_date);
+                } else {
+                    $base_date = $self->next_open_day($base_date);
+                }
+            }
+        }
+    }
+
+    return $base_date;
+}
+
+1;
diff --git a/installer/data/mysql/atomicupdate/bug_17015_koha_discrete_calendar.sql b/installer/data/mysql/atomicupdate/bug_17015_koha_discrete_calendar.sql
new file mode 100644
index 0000000..28399e1
--- /dev/null
+++ b/installer/data/mysql/atomicupdate/bug_17015_koha_discrete_calendar.sql
@@ -0,0 +1,14 @@
+-- Bugzilla 17015
+-- New koha calendar
+-- Create discrete_calendar table to keep track of library day's information
+
+CREATE TABLE `discrete_calendar` (
+    `date` datetime NOT NULL,
+    `branchcode` varchar(10) NOT NULL,
+    `isopened` tinyint(1) DEFAULT 1,
+    `holidaytype` varchar(1) DEFAULT '',
+    `note` varchar(30) DEFAULT '',
+    `openhour` time NOT NULL,
+    `closehour` time NOT NULL,
+    PRIMARY KEY (`branchcode`,`date`)
+);
diff --git a/installer/data/mysql/atomicupdate/generate_discrete_calendar.perl b/installer/data/mysql/atomicupdate/generate_discrete_calendar.perl
new file mode 100755
index 0000000..9af613b
--- /dev/null
+++ b/installer/data/mysql/atomicupdate/generate_discrete_calendar.perl
@@ -0,0 +1,165 @@
+#!/usr/bin/perl
+
+#
+#   Script that fills the discrete_calendar table with dates, using the other date-related tables
+#
+use strict;
+use warnings;
+use DateTime;
+use DateTime::Format::Strptime;
+use Getopt::Long;
+use C4::Context;
+
+# Options
+my $help = 0;
+my $daysInFuture = 365;
+GetOptions (
+            'days|?|d=i' => \$daysInFuture,
+            'help|?|h' => \$help);
+my $usage = << 'ENDUSAGE';
+
+Script that manages the discrete_calendar table.
+
+This script has the following parameters :
+    --days --d : how many days in the future will be created, by default it's 365
+    -h --help: this message
+    --generate: fills discrete_calendar table with dates from the last two years and the next one
+
+ENDUSAGE
+
+if ($help) {
+    print $usage;
+    exit;
+}
+my $dbh = C4::Context->dbh;
+
+my $currentDate = DateTime->today;
+
+# two years ago
+my $startDate = DateTime->new(
+day       => $currentDate->day(),
+month     => $currentDate->month(),
+year      => $currentDate->year()-2,
+time_zone => C4::Context->tz()
+)->truncate( to => 'day' );
+
+# a year into the future
+my $endDate = DateTime->new(
+day       => $currentDate->day(),
+month     => $currentDate->month(),
+year      => $currentDate->year(),
+time_zone => C4::Context->tz()
+)->truncate( to => 'day' );
+$endDate->add(days => $daysInFuture);
+
+#Added a default (standard) branch.
+my $add_default_branch = 'INSERT INTO branches (branchname, branchcode) VALUES(?,?)';
+my $add_Branch_Sth = $dbh->prepare($add_default_branch);
+$add_Branch_Sth->execute('Default', 'DFLT');
+# finds branches;
+my $selectBranchesSt = 'SELECT branchcode FROM branches';
+my $selectBranchesSth = $dbh->prepare($selectBranchesSt);
+$selectBranchesSth->execute();
+my @branches = ();
+while ( my $branchCode = $selectBranchesSth->fetchrow_array ) {
+
+    push @branches,$branchCode;
+}
+
+# finds what days are closed for each branch
+my %repeatableHolidaysPerBranch = ();
+my %specialHolidaysPerBranch = ();
+my $selectWeeklySt;
+my $selectWeeklySth;
+
+
+foreach my $branch (@branches){
+
+    $selectWeeklySt = 'SELECT weekday, title, day, month FROM repeatable_holidays WHERE branchcode = ?';
+    $selectWeeklySth = $dbh->prepare($selectWeeklySt);
+    $selectWeeklySth->execute($branch);
+
+    my @weeklyHolidays = ();
+
+    while ( my ($weekDay, $title, $day, $month) = $selectWeeklySth->fetchrow_array ) {
+        push @weeklyHolidays,{weekday => $weekDay, title => $title, day => $day, month => $month};
+
+    }
+
+    $repeatableHolidaysPerBranch{$branch} = \@weeklyHolidays;
+
+    my $selectSpecialHolidayDateSt = 'SELECT day,month,year,title FROM special_holidays WHERE branchcode = ? AND isexception = 0';
+    my $specialHolidayDatesSth = $dbh->prepare($selectSpecialHolidayDateSt);
+    $specialHolidayDatesSth -> execute($branch);
+    # Tranforms dates from specialHolidays table in DateTime for our new table
+    my @specialHolidayDates = ();
+    while ( my ($day, $month, $year, $title) = $specialHolidayDatesSth->fetchrow_array ) {
+
+        my $specialHolidayDate = DateTime->new(
+        day       => $day,
+        month     => $month,
+        year      => $year,
+        time_zone => C4::Context->tz()
+        )->truncate( to => 'day' );
+        push @specialHolidayDates,{date=>$specialHolidayDate, title=> $title};
+    }
+
+    $specialHolidaysPerBranch{$branch} = \@specialHolidayDates;
+}
+# Fills table with dates and sets 'isopened' according to the branch's weekly restrictions (repeatable_holidays)
+my $insertDateSt;
+my $insertDateSth;
+
+# Loop that does everything in the world
+for (my $tempDate = $startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)){
+    foreach my $branch (@branches){
+        my $dayOfWeek = $tempDate->day_of_week;
+        # Representation fix
+        # DateTime object dow (1-7) where Monday is 1
+        # Arrays are 0-based where 0 = Sunday, not 7.
+        $dayOfWeek -=1 unless $dayOfWeek ==7;
+        $dayOfWeek =0 if $dayOfWeek ==7;
+
+        my $openhour = "09:00:00";
+        my $closehour = "17:00:00";
+
+        # Finds closed days
+        my $isOpened =1;
+        my $specialDescription = "";
+        my $holidaytype ='';
+        $dayOfWeek = $tempDate->day_of_week;
+        foreach my $holidayWeekDay (@{$repeatableHolidaysPerBranch{$branch}}){
+            if($holidayWeekDay->{weekday} && $dayOfWeek == $holidayWeekDay->{weekday}){
+                $isOpened = 0;
+                $specialDescription = $holidayWeekDay->{title};
+                $holidaytype = 'W';
+            }elsif($holidayWeekDay->{day} && $holidayWeekDay->{month}){
+                my $date = DateTime->new(
+                day       => $holidayWeekDay->{day},
+                month     => $holidayWeekDay->{month},
+                year      => $tempDate->year(),
+                time_zone => C4::Context->tz()
+                )->truncate( to => 'day' );
+
+                if ($tempDate == $date) {
+                    $isOpened = 0;
+                    $specialDescription = $holidayWeekDay->{title};
+                    $holidaytype = 'R';
+                }
+            }
+        }
+
+        foreach my $specialDate (@{$specialHolidaysPerBranch{$branch}}){
+            if($tempDate->datetime() eq $specialDate->{date}->datetime() ){
+                $isOpened = 0;
+                $specialDescription = $specialDate->{title};
+                $holidaytype = 'E';
+            }
+        }
+        #final insert statement
+
+        $insertDateSt = 'INSERT INTO discrete_calendar (date,branchcode,isopened,holidaytype,note,openhour,closehour) VALUES (?,?,?,?,?,?,?)';
+        $insertDateSth = $dbh->prepare($insertDateSt);
+        $insertDateSth->execute($tempDate,$branch,$isOpened,$holidaytype,$specialDescription,$openhour,$closehour);
+    }
+}
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt
new file mode 100644
index 0000000..a3e309e
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt
@@ -0,0 +1,617 @@
+[% USE Branches %]
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Tools &rsaquo; [% Branches.GetName( branch ) %] calendar</title>
+[% INCLUDE 'doc-head-close.inc' %]
+[% INCLUDE 'calendar.inc' %]
+<link rel="stylesheet" type="text/css" href="[% interface %]/[% theme %]/css/datatables.css" />
+<script type="text/javascript" src="[% interface %]/lib/jquery/plugins/jquery-ui-timepicker-addon.min.js"></script>
+[% INCLUDE 'datatables.inc' %]
+<script type="text/javascript">
+//<![CDATA[
+    var weekdays = new Array(_("Sundays"),_("Mondays"),_("Tuesdays"),_("Wednesdays"),_("Thursdays"),_("Fridays"),_("Saturdays"));
+    /* Creates all the structures to deal with all different kinds of holidays */
+    var datesInfos = new Array();
+    [% FOREACH date IN datesInfos %]
+    datesInfos["[% date.date %]"] = {title : "[% date.note %]", outputdate : "[% date.outputdate %]", holidaytype:"[% date.holidaytype %]", openhour: "[% date.openhour %]", closehour: "[% date.closehour %]"};
+    [% END %]
+
+    function holidayOperation(formObject, opType) {
+        var op = document.getElementsByName('Operation');
+        op[0].value = opType;
+        formObject.submit();
+    }
+
+    // This function shows the "Show Holiday" panel
+    function showHoliday (dayName, day, month, year, weekDay, title, holidayType) {
+        $("#newHoliday").slideDown("fast");
+        $("#copyHoliday").slideUp("fast");
+        $('#newDaynameOutput').html(dayName);
+        $('#newDayname').val(dayName);
+        $('#newBranchNameOutput').html($("#branch :selected").text());
+        $(".newHoliday ,#BranchName").val($('#branch').val());
+        $('#newDayOutput').html(day);
+        $(".newHoliday #Day").val(day);
+        $(".newHoliday #Month").val(month);
+        $(".newHoliday #Year").val(year);
+        $("#newMonthOutput").html(month);
+        $("#newYearOutput").html(year);
+        $(".newHoliday, #Weekday").val(weekDay);
+
+        $('.newHoliday #title').val(title);
+        $('#HolidayType').val(holidayType);
+
+        //You can't delete holiday based on type  for single, floating, need validation holidays. This will show/hide the option based on type
+        if(holidayType !='weekday' && holidayType != 'daymonth'){
+            $('#deleteTypeOption').hide();
+        }else{
+            $('#deleteTypeOption').show();
+        }
+
+        if(holidayType == 'weekday') {
+            // $("#showOperationDelLabel").html(_("Delete this holiday."));
+            $("#holtype").attr("class","key repeatableweekly").html(_("Holiday repeating weekly"));
+        } else if(holidayType == 'daymonth') {
+            // $("#showOperationDelLabel").html(_("Delete this holiday."));
+            $("#holtype").attr("class","key repeatableyearly").html(_("Holiday repeating yearly"));
+        } else if(holidayType == 'float') {
+            // $("#showOperationDelLabel").html(_("Delete this holiday."));
+            $("#holtype").attr("class","key float").html(_("Floating holiday"));
+        } else if(holidayType == 'N') {
+            // $("#showOperationDelLabel").html(_("Delete this holiday."));
+            $("#holtype").attr("class","key exception").html(_("Needs validation"));
+        } else if(holidayType == 'ymd') {
+            // $("#showOperationDelLabel").html(_("Delete this holiday."));
+            $("#holtype").attr("class","key holiday").html(_("Unique holiday"));
+        } else{
+            $("#holtype").attr("class","key normalday").html(_("Working day "));
+        }
+    }
+
+    function hidePanel(aPanelName) {
+        $("#"+aPanelName).slideUp("fast");
+    }
+
+    function changeBranch () {
+        var branch = $("#branch option:selected").val();
+        location.href='/cgi-bin/koha/tools/discrete_calendar.pl?branch=' + branch + '&calendardate=' + "[% calendardate %]";
+    }
+
+    function Help() {
+        newin=window.open("/cgi-bin/koha/help.pl","KohaHelp",'width=600,height=600,toolbar=false,scrollbars=yes');
+    }
+
+    // This function gives css clases to each kind of day
+    function dateStatusHandler(date) {
+        date = getSeparetedDate(date);
+        var day = date.day;
+        var month = date.month;
+        var year = date.year;
+        var weekDay = date.weekDay;
+        var dayName = weekdays[weekDay];
+        var dayMonth = date.dayMonth;
+        var dateString = date.dateString;
+
+        if (datesInfos[dateString] && datesInfos[dateString].holidaytype =='W'){
+            return [true, "repeatableweekly", _("Weekly holiday: %s").format(datesInfos[dateString].title)];
+        } else if (datesInfos[dateString] && datesInfos[dateString].holidaytype == 'R') {
+            return [true, "repeatableyearly", _("Yearly holiday: %s").format(datesInfos[dateString].title)];
+        } else if (datesInfos[dateString] && datesInfos[dateString].holidaytype == 'N') {
+            return [true, "exception", _("Need validation: %s").format(datesInfos[dateString].title)];
+        } else if (datesInfos[dateString] && datesInfos[dateString].holidaytype == 'F') {
+            return [true, "float", _("Floating holiday: %s").format(datesInfos[dateString].title)];
+        } else if (datesInfos[dateString] && datesInfos[dateString].holidaytype == 'E') {
+            return [true, "holiday", _("Single holiday: %s").format(datesInfos[dateString].title)];
+        } else {
+            return [true, "normalday", _("Normal day")];
+        }
+    }
+
+    /* This function is in charge of showing the correct panel considering the kind of holiday */
+    function dateChanged(text, calendar) {
+        calendar = getSeparetedDate(calendar);
+        var day = calendar.day;
+        var month = calendar.month;
+        var year = calendar.year;
+        var weekDay = calendar.weekDay;
+        var dayName = weekdays[weekDay];
+        var dayMonth = calendar.dayMonth;
+        var dateString = calendar.dateString;
+        //set value of form hidden field
+        $('#from_copyFrom').val(text);
+        if (datesInfos[dateString].holidaytype =='E') {
+            showHoliday(dayName, day, month, year, weekDay, datesInfos[dateString].title,'ymd');
+            showDateInfo(dateString);
+        } else if (datesInfos[dateString].holidaytype =='W') {
+            showHoliday(dayName, day, month, year, weekDay, datesInfos[dateString].title, 'weekday');
+            showDateInfo(dateString);
+        } else if (datesInfos[dateString].holidaytype =='R') {
+            showHoliday(dayName, day, month, year, weekDay, datesInfos[dateString].title, 'daymonth');
+            showDateInfo(dateString);
+        } else if (datesInfos[dateString].holidaytype =='F') {
+            showHoliday(dayName, day, month, year, weekDay, datesInfos[dateString].title, 'float');
+            showDateInfo(dateString);
+        } else if (datesInfos[dateString].holidaytype =='N') {
+            showHoliday(dayName, day, month, year, weekDay, datesInfos[dateString].title, 'N');
+            showDateInfo(dateString);
+        }else {
+            showHoliday(dayName, day, month, year, weekDay, '', 'normalday');
+            showDateInfo(dateString);
+        }
+    }
+
+    function showDateInfo (date){
+        $("#dateInfo").css('visibility', 'visible');
+        $('#openHour').val(datesInfos[date].openhour);
+        $('#closeHour').val(datesInfos[date].closehour);
+        $('#openHour').timepicker({
+            showOn : 'focus',
+            timeFormat: 'HH:mm:ss',
+            defaultTime: datesInfos[date].openhour,
+            showSecond: false,
+            stepMinute: 5,
+        });
+        $('#closeHour').timepicker({
+            showOn : 'focus',
+            timeFormat: 'HH:mm:ss',
+            defaultTime: datesInfos[date].closehour,
+            showSecond: false,
+            stepMinute: 5,
+        });
+        $('#closeHour').val(datesInfos[date].closehour);
+        $('#date').html(datesInfos[date].outputdate);
+
+        if (datesInfos[date].holidaytype !=''){
+            var type = datesInfos[date].holidaytype;
+            $('#holidayType option[value="'+ type +'"]').attr('selected', true)
+        }else{
+            $('#holidayType option[value="none"]').attr('selected', true)
+        }
+        $(".dateDetailsPanel ,#BranchName").val($('#branch').val());
+        if(datesInfos[date].holidaytype == 'W' || datesInfos[date].holidaytype == 'R'){
+            $('#deleteType').show("fast");
+        }else{
+            $('#deleteType').hide("fast");
+        }
+
+    }
+    function getSeparetedDate(date){
+        var mydate = new Array();
+        var day = (date.getDate() < 10 ? '0' : '') + date.getDate();
+        var month = ((date.getMonth()+1) < 10 ? '0' : '') + (date.getMonth() +1);
+        var year = date.getFullYear();
+        var weekDay = date.getDay();
+        var dayMonth = (date.getMonth()+1) + '/' + date.getDate();
+        var dateString = year + '-' + month + '-' + day;
+        mydate = {dateObj : date, date : date ,dateString : dateString, dayMonth: dayMonth, weekDay: weekDay, year: year, month: month, day: day};
+
+        return mydate;
+    }
+
+    function validateForm(form){
+
+        if(form =='newHoliday' && $('#from_copyToDatePicker').val() && $('#CopyRadioButton').is(':checked')){
+            var from_DateFrom = new Date($("#jcalendar-container").datepicker("getDate"));
+            var from_DateTo = new Date($('#from_copyToDatePicker').datepicker("getDate"));
+            var to_DateFrom = new Date($('#to_copyFromDatePicker').datepicker("getDate"));
+            var to_DateTo = new Date($('#to_copyToDatePicker').datepicker("getDate"));
+
+            var from_start = Math.round( from_DateFrom.getTime() / (3600*24*1000)); //days as integer from..
+            var from_end   = Math.round( from_DateTo.getTime() / (3600*24*1000));
+            var to_start = Math.round( to_DateFrom.getTime() / (3600*24*1000));
+            var to_end   = Math.round( to_DateTo.getTime() / (3600*24*1000));
+
+            var from_daysDiff = from_end - from_start;
+            var to_daysDiff = to_end - to_start;
+
+            if(from_daysDiff == to_daysDiff || from_daysDiff ==1){
+                $('#daysnumber').val(to_daysDiff)
+                return true;
+            }else{
+                alert("You have to pick the same number of days if you choose 2 ranges");
+                return false;
+            }
+        }else {
+            return 1;
+        }
+    }
+
+    //Check if date range have the same opening, closing hours and holiday type if there's one.
+    function checkRange(date){
+        date = new Date(date);
+        $('#toDate').val(getSeparetedDate(date).dateString);
+        var fromDate = new Date($("#jcalendar-container").datepicker("getDate"));
+        var sameHoliday =true;
+        var sameOpenHours =true;
+        var sameCloseHours =true;
+
+        for (var i = fromDate; i <= date ; i.setDate(i.getDate() + 1)) {
+            var myDate1 = getSeparetedDate(i);
+            var date1 = myDate1.dateString;
+            var holidayType1 = datesInfos[date1].holidaytype;
+            var openhours1 = datesInfos[date1].openhour;
+            var closehours1 = datesInfos[date1].closehour;
+            for (var j = fromDate; j <= date ; j.setDate(j.getDate() + 1)) {
+                var myDate2 = getSeparetedDate(j);
+                var date2 = myDate2.dateString;
+                var holidayType2 = datesInfos[date2].holidaytype;
+                var openhours2 = datesInfos[date2].openhour;
+                var closehours2 = datesInfos[date2].closehour;
+
+                if (sameHoliday && holidayType1 != holidayType2){
+                    $('#holidayType option[value="empty"]').attr('selected', true)
+                    sameHoliday=false;
+                }
+                if(sameOpenHours && (openhours1 != openhours2)){
+                    $('#openHour').val('');
+                    sameOpenHours=false;
+                }
+                if(sameCloseHours && (closehours1 != closehours2)){
+                    $('#closeHour').val('');
+                    sameCloseHours=false;
+                }
+            }
+            if (!sameOpenHours && !sameCloseHours && !sameHoliday){
+                return false;
+            }
+        }
+        return true;
+}
+    $(document).ready(function() {
+        $(".hint").hide();
+        $("#branch").change(function(){
+            changeBranch();
+        });
+        $("#holidayweeklyrepeatable>tbody>tr").each(function(){
+            var first_td = $(this).find('td').first();
+            first_td.html(weekdays[first_td.html()]);
+        });
+        $("a.helptext").click(function(){
+            $(this).parent().find(".hint").toggle(); return false;
+        });
+        $("#from_copyFrom").each(function () { this.value = "" });
+        $("#copyToDp").each(function () { this.value = "" });
+        $("#to_copyFrom").each(function () { this.value = "" });
+        $("#to_copyTo").each(function () { this.value = "" });
+        $("#jcalendar-container").datepicker({
+          beforeShowDay: function(thedate) {
+            return dateStatusHandler(thedate);
+            },
+        onSelect: function(dateText, inst) {
+            dateChanged(dateText, $(this).datepicker("getDate"));
+        },
+        defaultDate: new Date("[% keydate %]"),
+        minDate: new Date("[% minDate %]"),
+        maxDate: new Date("[% maxDate %]")
+        });
+
+        $("#from_copyToDatePicker").datepicker();
+        $("#from_copyToDatePicker").change(function(){
+            checkRange($(this).datepicker("getDate"));
+            $('#from_copyTo').val(($(this).val()));
+        });
+        $('#to_copyFromDatePicker').datepicker();
+        $("#to_copyFromDatePicker").change(function(){
+            $('#to_copyFrom').val(($(this).val()));
+        });
+        $('#to_copyToDatePicker').datepicker();
+        $("#to_copyToDatePicker").change(function(){
+            $('#to_copyTo').val(($(this).val()));
+        });
+        $('.newHoliday input[type="radio"]').click(function () {
+            if ($(this).attr("id") == "CopyRadioButton") {
+                $(".CopyToBranchPanel").hide('fast');
+                $(".CopyDatePanel").show('fast');
+            } else if ($(this).attr("id") == "CopyToBranchRadioButton"){
+                $(".CopyDatePanel").hide('fast');
+                $(".CopyToBranchPanel").show('fast');
+            } else{
+                $(".CopyDatePanel").hide('fast');
+                $(".CopyToBranchPanel").hide('fast');
+            }
+        });
+        $(".hidePanel").on("click",function(){
+            if( $(this).hasClass("showHoliday") ){
+                hidePanel("showHoliday");
+            }if ($(this).hasClass('newHoliday')) {
+                hidePanel("newHoliday");
+            }else {
+                hidePanel("copyHoliday");
+            }
+        })
+    });
+//]]>
+</script>
+<!-- Datepicker colors -->
+<style type="text/css"> .key { padding : 3px; white-space:nowrap; line-height:230%; }
+.ui-datepicker { font-size : 150%; }
+#jcalendar-container .ui-datepicker { font-size : 185%; }
+.ui-datepicker th, .ui-datepicker .ui-datepicker-title select { font-size : 80%; }
+.ui-datepicker td a { padding : .5em; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { font-size : 80%; }
+.key { padding : 3px; white-space:nowrap; line-height:230%; }
+.normalday { background-color :  #EDEDED; color :  Black; border : 1px solid #BCBCBC; }
+.ui-datepicker-unselectable { padding :.5em; white-space:nowrap;}
+.ui-state-disabled { padding :.5em; white-space:nowrap;}
+.exception { background-color :  #b3d4ff; color :  Black; border : 1px solid #BCBCBC; }
+.float { background-color :  #66ff33; color :  Black; border : 1px solid #BCBCBC; }
+.holiday {  background-color :  #ffaeae; color :  Black;  border : 1px solid #BCBCBC; }
+.repeatableweekly {  background-color :  #FFFF99; color :  Black;  border : 1px solid #BCBCBC; }
+.repeatableyearly {  background-color :  #FFCC66; color :  Black;  border : 1px solid #BCBCBC; }
+td.exception a.ui-state-default { background:  #b3d4ff none; color :  Black; border : 1px solid #BCBCBC; }
+td.float a.ui-state-default { background:  #66ff33 none; color :  Black; border : 1px solid #BCBCBC; }
+td.holiday a.ui-state-default {  background:  #ffaeae none; color :  Black;  border : 1px solid #BCBCBC; }
+td.repeatableweekly a.ui-state-default {  background:  #FFFF99 none; color :  Black;  border : 1px solid #BCBCBC; }
+td.repeatableyearly a.ui-state-default {  background:  #FFCC66 none; color :  Black;  border : 1px solid #BCBCBC; }
+.information { z-index : ; background-color :  #DCD2F1; width : 300px; display : none; border : 1px solid #000000; color :  #000000; font-size :  8pt; font-weight :  bold; background-color :  #FFD700; cursor :  pointer; padding : 2px; }
+.panel { z-index : 1; display : none; border : 3px solid #CCC; padding : 3px; margin-top: .3em;  background-color: #FEFEFE; } fieldset.brief { border : 0; margin-top: 0; }
+#showHoliday { margin : .5em 0; } h1 select { width: 20em; } div.yui-b fieldset.brief ol { font-size:100%; } div.yui-b fieldset.brief li, div.yui-b fieldset.brief li.radio  { padding:0.2em 0; } .help { margin:.3em 0;border:1px solid #EEE;padding:.3em .7em; font-size : 90%; } #holidayweeklyrepeatable, #holidaysyearlyrepeatable, #holidaysunique, #holidayexceptions { font-size : 90%; margin-bottom : 1em;} .calendar td, .calendar th, .calendar .button, .calendar tbody .day { padding : .7em; font-size: 110%; } .calendar { width: auto; border : 0; }
+.copyHoliday form li{ display:table-row } .copyHoliday form li b, .copyHoliday form li input{ display:table-cell; margin-bottom: 2px; }
+</style>
+</head>
+<body id="tools_holidays" class="tools">
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'cat-search.inc' %]
+
+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo; [% Branches.GetName( branch ) %] calendar</div>
+
+<div id="doc3" class="yui-t1">
+
+   <div id="bd">
+    <div id="yui-main">
+    <div class="yui-b">
+    <h2>[% Branches.GetName( branch ) %] calendar</h2>
+    <div class="yui-g">
+    <div class="yui-u first" style="width:60%">
+        <label for="branch">Define the holidays for:</label>
+            <select id="branch" name="branch">
+                [% PROCESS options_for_libraries libraries => Branches.all( selected => branch ) %]
+            </select>
+
+            <h3>Calendar information</h3>
+            <div id="jcalendar-container" style="float: left"></div>
+    <!-- ***************************** Panel to deal with new holidays **********************  -->
+    [% UNLESS  datesInfos %]
+    <div class="alert alert-danger" style="float: left; margin-left:15px">
+        <strong>Error!</strong> You have to run generate_discrete_calendar.pl in order to use Discrete Calendar.
+
+    </div>
+    [% END %]
+    <div class="panel newHoliday" id="newHoliday" style="float: left; margin-left:15px">
+        <form method="post" onsubmit="return validateForm('newHoliday')">
+            <fieldset class="brief">
+                <h3>Add new holiday</h3>
+                <ol>
+                    <li>
+                        <strong>Library:</strong>
+                        <span id="newBranchNameOutput"></span>
+                        <input type="hidden" id="BranchName" name="BranchName" />
+                    </li>
+                    <li>
+                        <strong>From date:</strong>
+                        <span id="newDaynameOutput"></span>,
+
+                        [% IF ( dateformat == "us" ) %]<span id="newMonthOutput"></span>/<span id="newDayOutput"></span>/<span id="newYearOutput"></span>[% ELSIF ( dateformat == "metric" ) %]<span id="newDayOutput"></span>/<span id="newMonthOutput"></span>/<span id="newYearOutput"></span>[% ELSIF ( dateformat == "dmydot" ) %]<span id="newDayOutput"></span>.<span id="newMonthOutput"></span>.<span id="newYearOutput"></span>[% ELSE %]<span id="newYearOutput"></span>/<span id="newMonthOutput"></span>/<span id="newDayOutput"></span>[% END %]
+
+                        <input type="hidden" id="newDayname" name="showDayname" />
+                        <input type="hidden" id="Weekday" name="Weekday" />
+                        <input type="hidden" id="Day" name="Day" />
+                        <input type="hidden" id="Month" name="Month" />
+                        <input type="hidden" id="Year" name="Year" />
+                    </li>
+                    <li class="dateinsert">
+                        <b>To date: </b>
+                        <input type="text" id="from_copyToDatePicker" name="toDate" size="20" class="datepicker" />
+                    </li>
+                    <li>
+                        <label for="title">Title: </label><input type="text" name="Title" id="title" size="35" />
+                    </li>
+                    <li id="holidayType">
+                        <label for="holidayType">Date type</label>
+                        <select name ='holidayType'>
+                            <option value="empty"></option>
+                            <option value="none">Normal day</option>
+                            <option value="R">Fixed</option>
+                            <option value="E">Exception</option>
+                            <option value="W">Weekly</option>
+                            <option value="F">Floating</option>
+                            <option value="N" disabled>Need validation</option>
+                        </select>
+                    </li>
+                    <li class="radio" id="deleteType" style="display : none;" >
+                        <input type="checkbox" name="deleteType" id="deleteType_checkbox" value="1" ><label for="deleteType_checkbox"> Delete this type</label>
+                        <a href="#" class="helptext">[?]</a>
+                        <div class="hint">Remove all repeated or weekly holidays of the selected date or week day <br> if normal day is selected.</div>
+                    </li>
+                    <li>
+                        <label for="openHour">Open hours: </label><input type="text" name="openHour" id='openHour' style="display :flex"  >
+                    </li>
+                    <li>
+                        <label for="closeHour">Close hours: </label><input type="text" name="closeHour" id='closeHour' style="display :flex" >
+                    </li>
+                    <li class="radio">
+                        <input type="radio" name="action" id="EditRadioButton" value="edit" checked/>
+                        <label for="EditRadioButton">Edit selected dates</label>
+                    </li>
+                    <li class="radio">
+                        <input type="radio" name="action" id="CopyRadioButton" value="copyDates" />
+                        <label for="CopyRadioButton">Copy to different dates</label>
+                        <div class="CopyDatePanel" style="display:none; padding-left:15px">
+
+                            <b>From : </b>
+                            <input type="text" id="to_copyFromDatePicker" size="20" class="datepicker"/>
+                            <b>To : </b>
+                            <input type="text" id="to_copyToDatePicker" size="20" class="datepicker"/>
+                        </div>
+                        <input type="hidden" name="daysnumber" id='daysnumber'>
+                        <!-- These  yyyy-mm-dd -->
+                        <input type="hidden" name="from_copyFrom" id='from_copyFrom'>
+                        <input type="hidden" name="from_copyTo" id='from_copyTo'>
+                        <input type="hidden" name="to_copyFrom" id='to_copyFrom'>
+                        <input type="hidden" name="to_copyTo" id='to_copyTo'>
+                    </li>
+                    <li class="radio">
+                        <input type="radio" name="action" id="CopyToBranchRadioButton" value="copyBranch" />
+                        <label for="CopyToBranchRadioButton">Copy calendar to an other branch</label>
+                        <div class="CopyToBranchPanel" style="display:none; padding-left:15px">
+                            <label>Copy to : </label>
+                            <select name ='newBranch'>
+                                [% PROCESS options_for_libraries libraries => Branches.all( selected => branch ) %]
+                            </select>
+                        </div>
+                    </li>
+                </ol>
+                <fieldset class="action">
+                    <input type="submit" name="submit" value="Save" />
+                    <a href="#" class="cancel hidePanel newHoliday">Cancel</a>
+                </fieldset>
+            </fieldset>
+        </form>
+    </div>
+
+<!-- ************************************************************************************** -->
+<!-- ******                              MAIN SCREEN CODE                            ****** -->
+<!-- ************************************************************************************** -->
+
+</div>
+<div class="yui-u" style="width : 40%">
+    <div class="help">
+        <h4>Hints</h4>
+        <ul>
+            <li>Search in the calendar the day you want to set as holiday.</li>
+            <li>Click the date to add or edit a holiday.</li>
+            <li>Specify how the holiday should repeat.</li>
+            <li>Click Save to finish.</li>
+            <li>PS:
+                <ul>
+                    <li>You can't edit passed dates</li>
+                    <li>Weekly holidays change open/close hours for all the days affected unless inputs are empty</li>
+                </ul>
+            </li>
+        </ul>
+        <h4>Key</h4>
+        <p>
+            <span class="key normalday">Working day </span>
+            <span class="key holiday">Unique holiday</span>
+            <span class="key repeatableweekly">Holiday repeating weekly</span>
+            <span class="key repeatableyearly">Holiday repeating yearly</span>
+            <span class="key float">Floating holiday</span>
+            <span class="key exception">Need validation</span>
+        </p>
+    </div>
+<div id="holiday-list">
+
+    [% IF ( NEED_VALIDATION_HOLIDAYS ) %]
+    <h3>Need validation holidays</h3>
+    <table id="holidaysunique">
+        <thead>
+            <tr>
+                <th class="exception">Date</th>
+                <th class="exception">Title</th>
+            </tr>
+        </thead>
+        <tbody>
+            [% FOREACH need_validation_holiday IN NEED_VALIDATION_HOLIDAYS %]
+            <tr>
+                <td><a href="/cgi-bin/koha/tools/discrete_calendarte_holiday.pl?branch=[% branch %]&amp;calendardate=[% need_validation_holiday.date %]"><span title="[% need_validation_holiday.DATE_SORT %]">[% need_validation_holiday.outputdate %]</span></a></td>
+                <td>[% need_validation_holiday.note %]</td>
+            </tr>
+            [% END %]
+        </tbody>
+    </table>
+    [% END %]
+
+    [% IF ( week_days ) %]
+    <h3>Weekly - Repeatable holidays</h3>
+    <table id="holidayweeklyrepeatable">
+        <thead>
+            <tr>
+                <th class="repeatableweekly">Day of week</th>
+                <th class="repeatableweekly">Title</th>
+            </tr>
+        </thead>
+        <tbody>
+            [% FOREACH WEEK_DAYS_LOO IN week_days %]
+            <tr>
+                <td>[% WEEK_DAYS_LOO.weekday %]</td>
+            </td>
+            <td>[% WEEK_DAYS_LOO.note %]</td>
+        </tr>
+        [% END %]
+    </tbody>
+</table>
+[% END %]
+
+[% IF ( repeatable_holidays ) %]
+<h3>Yearly - Repeatable holidays</h3>
+<table id="holidaysyearlyrepeatable">
+    <thead>
+        <tr>
+            [% IF ( dateformat == "metric" ) %]
+            <th class="repeatableyearly">Day/month</th>
+            [% ELSE %]
+            <th class="repeatableyearly">Month/day</th>
+            [% END %]
+            <th class="repeatableyearly">Title</th>
+        </tr>
+    </thead>
+    <tbody>
+        [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN repeatable_holidays %]
+        <tr>
+            <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT %]">[%DAY_MONTH_HOLIDAYS_LOO.month%]/[% DAY_MONTH_HOLIDAYS_LOO.day %]</span></td>
+            <td>[% DAY_MONTH_HOLIDAYS_LOO.note %]</td>
+        </tr>
+        [% END %]
+    </tbody>
+</table>
+[% END %]
+
+[% IF ( HOLIDAYS_LOOP ) %]
+<h3>Unique holidays</h3>
+<table id="holidaysunique">
+    <thead>
+        <tr>
+            <th class="holiday">Date</th>
+            <th class="holiday">Title</th>
+        </tr>
+    </thead>
+    <tbody>
+        [% FOREACH HOLIDAYS_LOO IN HOLIDAYS_LOOP %]
+        <tr>
+            <td><a href="/cgi-bin/koha/tools/discrete_calendarte_holiday.pl?branch=[% branch %]&amp;calendardate=[% HOLIDAYS_LOO.date %]"><span title="[% HOLIDAYS_LOO.DATE_SORT %]">[% HOLIDAYS_LOO.outputdate %]</span></a></td>
+            <td>[% HOLIDAYS_LOO.note %]</td>
+        </tr>
+        [% END %]
+    </tbody>
+</table>
+[% END %]
+
+[% IF ( FLOAT_HOLIDAYS ) %]
+<h3>Floating upcoming holidays</h3>
+<table id="holidaysunique">
+    <thead>
+        <tr>
+            <th class="float">Date</th>
+            <th class="float">Title</th>
+        </tr>
+    </thead>
+    <tbody>
+        [% FOREACH float_holiday IN FLOAT_HOLIDAYS %]
+        <tr>
+            <td><a href="/cgi-bin/koha/tools/discrete_calendarte_holiday.pl?branch=[% branch %]&amp;calendardate=[% float_holiday.date %]"><span title="[% float_holiday.DATE_SORT %]">[% float_holiday.outputdate %]</span></a></td>
+            <td>[% float_holiday.note %]</td>
+        </tr>
+        [% END %]
+    </tbody>
+</table>
+[% END %]
+</div>
+</div>
+</div>
+</div>
+</div>
+
+<div class="yui-b noprint">
+[% INCLUDE 'tools-menu.inc' %]
+</div>
+</div>
+[% INCLUDE 'intranet-bottom.inc' %]
diff --git a/misc/cronjobs/add_days_discrete_calendar.pl b/misc/cronjobs/add_days_discrete_calendar.pl
new file mode 100755
index 0000000..7c1ee8c
--- /dev/null
+++ b/misc/cronjobs/add_days_discrete_calendar.pl
@@ -0,0 +1,113 @@
+#!/usr/bin/perl
+
+#
+#   This script adds one day into discrete_calendar table based on the same day from the week before
+#
+use strict;
+use warnings;
+use DateTime;
+use DateTime::Format::Strptime;
+use Getopt::Long;
+use C4::Context;
+
+# Options
+my $help = 0;
+my $daysInFuture =1;
+GetOptions (
+    'help|?|h'  => \$help,
+    'n=i'       => \$daysInFuture);
+
+my $usage = << 'ENDUSAGE';
+
+This script adds days into discrete_calendar table based on the same day from the week before.
+
+Examples :
+    The latest date on discrete_calendar is : 28-07-2017
+    The current date : 01-08-2016
+    The dates that will be added are : 29-07-2017, 30-07-2017, 31-07-2017, 01-08-2017
+Open close exemples :
+    Date added is : 29-07-2017
+    Opening/closing hours will be base on : 22-07-2017 (- 7 days)
+    Library open or closed will be based on : 29-07-2017 (- 1 year)
+This script has the following parameters:
+    -h --help: this message
+    -n : number of days to add in the futre, default : 1
+
+PS: This is for testing purposes, the method of knowing whether it's opened or not may be changed.
+
+ENDUSAGE
+my $dbh = C4::Context->dbh;
+my $query = "SELECT distinct weekday(date), note FROM discrete_calendar where holidaytype='W'";
+my $stmt = $dbh->prepare($query);
+$stmt->execute();
+my @week_days_discrete;
+while (my ($weekday,$note) = $stmt->fetchrow_array){
+    push @week_days_discrete, {weekday => $weekday, note => $note};
+}
+
+if ($help) {
+    print $usage;
+    exit;
+}
+
+#getting the all the branches
+my $selectBranchesSt = 'SELECT branchcode FROM branches';
+my $selectBranchesSth = $dbh->prepare($selectBranchesSt);
+$selectBranchesSth->execute();
+my @branches = ();
+while ( my $branchCode = $selectBranchesSth->fetchrow_array ) {
+
+    push @branches,$branchCode;
+}
+
+#get the latest date in the table
+$query = "SELECT MAX(date) FROM discrete_calendar";
+$stmt = $dbh->prepare($query);
+$stmt->execute();
+my $latestedDate = $stmt->fetchrow_array;
+my $parser = DateTime::Format::Strptime->new(
+    pattern => '%Y-%m-%d %H:%M:%S',
+    on_error => 'croak',
+);
+$latestedDate = $parser->parse_datetime($latestedDate);
+
+my $endDate = DateTime->today->add( years => 1, days => $daysInFuture);
+my $newDay = $latestedDate->clone();
+
+for ($newDay->add(days => 1);$newDay <= $endDate;$newDay->add(days => 1)){
+    my $lastWeekDay = $newDay->clone();
+    $lastWeekDay->add(days=> -8);
+    my $dayOfWeek = $lastWeekDay->day_of_week;
+    # Representation fix
+    # DateTime object dow (1-7) where Monday is 1
+    # Arrays are 0-based where 0 = Sunday, not 7.
+    $dayOfWeek -= 1 unless $dayOfWeek == 7;
+    $dayOfWeek = 0 if $dayOfWeek == 7;
+
+    #checking if it was open on the same day from last year
+    my $yearAgo = $newDay->clone();
+    $yearAgo = $yearAgo->add(years => -1);
+    my $last_year = 'SELECT isopened, holidaytype, note, openhour, closehour FROM discrete_calendar WHERE date=? AND branchcode=?';
+    my $day_last_week = "SELECT openhour, closehour FROM discrete_calendar WHERE DAYOFWEEk(date)=DAYOFWEEK(?) and date < ? order by date desc limit 1";
+    my $add_Day = 'INSERT INTO discrete_calendar (date,branchcode,isopened,openhour,closehour) VALUES (?,?,?,?,?)';
+    my $note ='';
+    #insert into discrete_calendar for each branch
+    foreach my $branchCode(@branches){
+        $stmt = $dbh->prepare($last_year);
+        $stmt->execute($yearAgo,$branchCode);
+        my ($isOpened, $holidaytype, $note) = $stmt->fetchrow_array;
+        if ($holidaytype && $holidaytype eq "W"){
+            $isOpened = 1;
+            $note='';
+        }elsif ($holidaytype && $holidaytype eq "F" || $holidaytype eq 'E'){
+            $holidaytype = 'N';
+        }
+        $holidaytype = '' if $isOpened;
+        $stmt = $dbh->prepare($day_last_week);
+        $stmt->execute($newDay, $newDay);
+        my ($openhour,$closehour ) = $stmt->fetchrow_array;
+        my $add_Day = 'INSERT INTO discrete_calendar (date,branchcode,isopened,holidaytype, note,openhour,closehour) VALUES (?,?,?,?,?,?,?)';
+        $stmt = $dbh->prepare($add_Day);
+        $stmt->execute($newDay,$branchCode,$isOpened,$holidaytype,$note, $openhour,$closehour);
+    }
+}
diff --git a/tools/discrete_calendar.pl b/tools/discrete_calendar.pl
new file mode 100755
index 0000000..9b035f5
--- /dev/null
+++ b/tools/discrete_calendar.pl
@@ -0,0 +1,157 @@
+#!/usr/bin/perl
+
+# This file is part of Koha.
+#
+# 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.
+#
+# 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 Koha; if not, see <http://www.gnu.org/licenses>.
+
+#####Sets holiday periods for each branch. Datedues will be extended if branch is closed -TG
+use strict;
+use warnings;
+
+use CGI qw ( -utf8 );
+
+use C4::Auth;
+use C4::Output;
+
+use Koha::DateUtils;
+use Koha::DiscreteCalendar;
+
+my $input = new CGI;
+
+# Get the template to use
+my ($template, $loggedinuser, $cookie)
+    = get_template_and_user({template_name => "tools/discrete_calendar.tt",
+                             type => "intranet",
+                             query => $input,
+                             authnotrequired => 0,
+                             flagsrequired => {tools => 'edit_calendar'},
+                             debug => 1,
+                           });
+
+my $branchcode = $input->param('BranchName');
+my $branch = $input->param('branch') || C4::Context->userenv->{'branch'};
+my $calendar = Koha::DiscreteCalendar->new(branchcode => $branch);
+
+my $weekday = $input->param('Weekday');
+
+my $holidaytype = $input->param('holidayType');
+my $allbranches = $input->param('allBranches');
+
+my $title = $input->param('Title');
+
+my $action = $input->param('action') || '';
+
+# calendardate - date passed in url for human readability (syspref)
+# if the url has an invalid date default to 'now.'
+my $calendarinput_dt = eval { dt_from_string( scalar $input->param('calendardate')); } || dt_from_string;
+my $calendardate = output_pref( { dt => $calendarinput_dt, dateonly => 1 } );
+
+if($action eq 'copyBranch'){
+    copyToBranch();
+} elsif($action eq 'copyDates'){
+    copyDates();
+} elsif($action eq 'edit'){
+    edit_holiday();
+}
+
+sub copyToBranch {
+    $calendar->copyToBranch(scalar $input->param('newBranch'));
+}
+sub copyDates {
+    my $from_startDate = $input->param('from_copyFrom') ||'';
+    my $from_endDate = $input->param('toDate') || '';
+    my $to_startDate = $input->param('to_copyFrom') || '';
+    my $to_endDate = $input->param('to_copyTo') || '';
+    my $daysnumber= $input->param('daysnumber');
+
+    $from_startDate = dt_from_string(scalar $from_startDate) if$from_startDate  ne '';
+    $to_startDate = dt_from_string(scalar $to_startDate) if $to_startDate ne '';
+    $to_startDate = dt_from_string(scalar $to_startDate) if $to_startDate ne '';
+    $to_endDate = dt_from_string(scalar $to_endDate) if $to_endDate ne '';
+
+    $calendar->copyHoliday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber);
+}
+
+sub edit_holiday {
+    my $openHour = $input->param('openHour');
+    my $closeHour = $input->param('closeHour');
+    my $toDate = $input->param('toDate');
+    my $deleteType = $input->param('deleteType') || 0;
+
+    my $startDate = dt_from_string(scalar $input->param('from_copyFrom'));
+
+    if($toDate ne '' ) {
+        $toDate = dt_from_string(scalar $toDate);
+    } else{
+        $toDate = $startDate->clone();
+    }
+    #MYSQL DAYOFWEEK Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday).
+    #JavaScript getDay() Returns the day of the week (from 0 to 6) for the specified date. Sunday is 0, Monday is 1, and so on.
+    $weekday+=1;
+    $calendar->edit_holiday($title, $weekday, $holidaytype, $openHour, $closeHour, $startDate, $toDate, $deleteType);
+}
+# keydate - date passed to calendar.js.  calendar.js does not process dashes within a date.
+
+my $keydate = output_pref( { dt => $calendarinput_dt, dateonly => 1, dateformat => 'iso' } );
+$keydate =~ s/-/\//g;
+
+# Set all the branches.
+my $onlymine =
+  (      C4::Context->preference('IndependentBranches')
+      && C4::Context->userenv
+      && !C4::Context->IsSuperLibrarian()
+      && C4::Context->userenv->{branch} ? 1 : 0 );
+if ( $onlymine ) {
+    $branch = C4::Context->userenv->{'branch'};
+}
+
+# Get all the holidays
+
+#discrete_calendar weekdays
+my @week_days = $calendar->get_week_days_holidays();
+
+#discrete_calendar repeatable
+my @repeatable_holidays = $calendar->get_day_month_holidays();
+
+#single hildays (exceptions)
+my @single_holidays =$calendar->get_single_holidays();
+#floating holidays
+my @float_holidays =$calendar->get_float_holidays();
+#need validation holidays
+my @need_validation_holidays =$calendar->get_need_valdation_holidays();
+
+#Calendar maximum date
+my $minDate = $calendar->getMinDate($branch);
+
+#Calendar minimum date
+my $maxDate = $calendar->getMaxDate($branch);
+
+my @datesInfos = $calendar->getDatesInfo($branch);
+
+$template->param(
+    HOLIDAYS_LOOP            => \@single_holidays,
+    FLOAT_HOLIDAYS           => \@float_holidays,
+    NEED_VALIDATION_HOLIDAYS => \@need_validation_holidays,
+    calendardate             => $calendardate,
+    keydate                  => $keydate,
+    branch                   => $branch,
+    week_days                => \@week_days,
+    repeatable_holidays      => \@repeatable_holidays,
+    minDate                  => $minDate,
+    maxDate                  => $maxDate,
+    datesInfos               => \@datesInfos,
+);
+
+# Shows the template with the real values replaced
+output_html_with_http_headers $input, $cookie, $template->output;
-- 
1.9.1