From a8aa3d8c2c58ff9148cb5842fdf9166bc184471d Mon Sep 17 00:00:00 2001
From: Charles Farmer <charles.farmer@inLibro.com>
Date: Wed, 4 Sep 2019 14:56:01 -0400
Subject: [PATCH] Bug 17015: DiscreteCalendar UI, Back-End and necessary
 scripts

---
 C4/Calendar.pm                                |  736 ---------
 C4/Circulation.pm                             |   13 +-
 C4/HoldsQueue.pm                              |    8 +-
 C4/Overdues.pm                                |    5 +-
 C4/Reserves.pm                                |    4 +-
 Koha/Calendar.pm                              |  555 -------
 Koha/Charges/Fees.pm                          |    4 +-
 Koha/DiscreteCalendar.pm                      | 1317 +++++++++++++++++
 Koha/Hold.pm                                  |    6 +-
 circ/returns.pl                               |    3 +-
 .../prog/css/discretecalendar.css             |  206 +++
 .../prog/en/includes/tools-menu.inc           |    2 +-
 .../en/modules/tools/discrete_calendar.tt     |  677 +++++++++
 .../prog/en/modules/tools/tools-home.tt       |    2 +-
 misc/cronjobs/add_days_discrete_calendar.pl   |  128 ++
 misc/cronjobs/fines.pl                        |    2 +-
 misc/cronjobs/holds/cancel_unfilled_holds.pl  |    2 +-
 misc/cronjobs/overdue_notices.pl              |   18 +-
 misc/cronjobs/staticfines.pl                  |    4 +-
 .../thirdparty/TalkingTech_itiva_outbound.pl  |    4 +-
 tools/copy-holidays.pl                        |   40 -
 tools/discrete_calendar.pl                    |  155 ++
 tools/exceptionHolidays.pl                    |  143 --
 tools/holidays.pl                             |  131 --
 tools/newHolidays.pl                          |  144 --
 25 files changed, 2520 insertions(+), 1789 deletions(-)
 delete mode 100644 C4/Calendar.pm
 delete mode 100644 Koha/Calendar.pm
 create mode 100644 Koha/DiscreteCalendar.pm
 create mode 100644 koha-tmpl/intranet-tmpl/prog/css/discretecalendar.css
 create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt
 create mode 100755 misc/cronjobs/add_days_discrete_calendar.pl
 delete mode 100755 tools/copy-holidays.pl
 create mode 100755 tools/discrete_calendar.pl
 delete mode 100755 tools/exceptionHolidays.pl
 delete mode 100755 tools/holidays.pl
 delete mode 100755 tools/newHolidays.pl

diff --git a/C4/Calendar.pm b/C4/Calendar.pm
deleted file mode 100644
index 6a5d8a79a4..0000000000
--- a/C4/Calendar.pm
+++ /dev/null
@@ -1,736 +0,0 @@
-package C4::Calendar;
-
-# 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>.
-
-use strict;
-use warnings;
-use vars qw(@EXPORT);
-
-use Carp;
-use Date::Calc qw( Date_to_Days Today);
-
-use C4::Context;
-use Koha::Caches;
-
-use constant ISO_DATE_FORMAT => "%04d-%02d-%02d";
-
-=head1 NAME
-
-C4::Calendar::Calendar - Koha module dealing with holidays.
-
-=head1 SYNOPSIS
-
-    use C4::Calendar::Calendar;
-
-=head1 DESCRIPTION
-
-This package is used to deal with holidays. Through this package, you can set 
-all kind of holidays for the library.
-
-=head1 FUNCTIONS
-
-=head2 new
-
-  $calendar = C4::Calendar->new(branchcode => $branchcode);
-
-Each library branch has its own Calendar.  
-C<$branchcode> specifies which Calendar you want.
-
-=cut
-
-sub new {
-    my $classname = shift @_;
-    my %options = @_;
-    my $self = bless({}, $classname);
-    foreach my $optionName (keys %options) {
-        $self->{lc($optionName)} = $options{$optionName};
-    }
-    defined($self->{branchcode}) or croak "No branchcode argument to new.  Should be C4::Calendar->new(branchcode => \$branchcode)";
-    $self->_init($self->{branchcode});
-    return $self;
-}
-
-sub _init {
-    my $self = shift @_;
-    my $branch = shift;
-    defined($branch) or die "No branchcode sent to _init";  # must test for defined here and above to allow ""
-    my $dbh = C4::Context->dbh();
-    my $repeatable = $dbh->prepare( 'SELECT *
-                                       FROM repeatable_holidays
-                                      WHERE ( branchcode = ? )
-                                        AND (ISNULL(weekday) = ?)' );
-    $repeatable->execute($branch,0);
-    my %week_days_holidays;
-    while (my $row = $repeatable->fetchrow_hashref) {
-        my $key = $row->{weekday};
-        $week_days_holidays{$key}{title}       = $row->{title};
-        $week_days_holidays{$key}{description} = $row->{description};
-    }
-    $self->{'week_days_holidays'} = \%week_days_holidays;
-
-    $repeatable->execute($branch,1);
-    my %day_month_holidays;
-    while (my $row = $repeatable->fetchrow_hashref) {
-        my $key = $row->{month} . "/" . $row->{day};
-        $day_month_holidays{$key}{title}       = $row->{title};
-        $day_month_holidays{$key}{description} = $row->{description};
-        $day_month_holidays{$key}{day} = sprintf("%02d", $row->{day});
-        $day_month_holidays{$key}{month} = sprintf("%02d", $row->{month});
-    }
-    $self->{'day_month_holidays'} = \%day_month_holidays;
-
-    my $special = $dbh->prepare( 'SELECT day, month, year, title, description
-                                    FROM special_holidays
-                                   WHERE ( branchcode = ? )
-                                     AND (isexception = ?)' );
-    $special->execute($branch,1);
-    my %exception_holidays;
-    while (my ($day, $month, $year, $title, $description) = $special->fetchrow) {
-        $exception_holidays{"$year/$month/$day"}{title} = $title;
-        $exception_holidays{"$year/$month/$day"}{description} = $description;
-        $exception_holidays{"$year/$month/$day"}{date} = 
-		sprintf(ISO_DATE_FORMAT, $year, $month, $day);
-    }
-    $self->{'exception_holidays'} = \%exception_holidays;
-
-    $special->execute($branch,0);
-    my %single_holidays;
-    while (my ($day, $month, $year, $title, $description) = $special->fetchrow) {
-        $single_holidays{"$year/$month/$day"}{title} = $title;
-        $single_holidays{"$year/$month/$day"}{description} = $description;
-        $single_holidays{"$year/$month/$day"}{date} = 
-		sprintf(ISO_DATE_FORMAT, $year, $month, $day);
-    }
-    $self->{'single_holidays'} = \%single_holidays;
-    return $self;
-}
-
-=head2 get_week_days_holidays
-
-   $week_days_holidays = $calendar->get_week_days_holidays();
-
-Returns a hash reference to week days holidays.
-
-=cut
-
-sub get_week_days_holidays {
-    my $self = shift @_;
-    my $week_days_holidays = $self->{'week_days_holidays'};
-    return $week_days_holidays;
-}
-
-=head2 get_day_month_holidays
-
-   $day_month_holidays = $calendar->get_day_month_holidays();
-
-Returns a hash reference to day month holidays.
-
-=cut
-
-sub get_day_month_holidays {
-    my $self = shift @_;
-    my $day_month_holidays = $self->{'day_month_holidays'};
-    return $day_month_holidays;
-}
-
-=head2 get_exception_holidays
-
-    $exception_holidays = $calendar->exception_holidays();
-
-Returns a hash reference to exception holidays. This kind of days are those
-which stands for a holiday, but you wanted to make an exception for this particular
-date.
-
-=cut
-
-sub get_exception_holidays {
-    my $self = shift @_;
-    my $exception_holidays = $self->{'exception_holidays'};
-    return $exception_holidays;
-}
-
-=head2 get_single_holidays
-
-    $single_holidays = $calendar->get_single_holidays();
-
-Returns a hash reference to single holidays. This kind of holidays are those which
-happened just one time.
-
-=cut
-
-sub get_single_holidays {
-    my $self = shift @_;
-    my $single_holidays = $self->{'single_holidays'};
-    return $single_holidays;
-}
-
-=head2 insert_week_day_holiday
-
-    insert_week_day_holiday(weekday => $weekday,
-                            title => $title,
-                            description => $description);
-
-Inserts a new week day for $self->{branchcode}.
-
-C<$day> Is the week day to make holiday.
-
-C<$title> Is the title to store for the holiday formed by $year/$month/$day.
-
-C<$description> Is the description to store for the holiday formed by $year/$month/$day.
-
-=cut
-
-sub insert_week_day_holiday {
-    my $self = shift @_;
-    my %options = @_;
-
-    my $weekday = $options{weekday};
-    croak "Invalid weekday $weekday" unless $weekday =~ m/^[0-6]$/;
-
-    my $dbh = C4::Context->dbh();
-    my $insertHoliday = $dbh->prepare("insert into repeatable_holidays (branchcode,weekday,day,month,title,description) values ( ?,?,NULL,NULL,?,? )");
-	$insertHoliday->execute( $self->{branchcode}, $weekday, $options{title}, $options{description});
-    $self->{'week_days_holidays'}->{$weekday}{title} = $options{title};
-    $self->{'week_days_holidays'}->{$weekday}{description} = $options{description};
-    return $self;
-}
-
-=head2 insert_day_month_holiday
-
-    insert_day_month_holiday(day => $day,
-                             month => $month,
-                             title => $title,
-                             description => $description);
-
-Inserts a new day month holiday for $self->{branchcode}.
-
-C<$day> Is the day month to make the date to insert.
-
-C<$month> Is month to make the date to insert.
-
-C<$title> Is the title to store for the holiday formed by $year/$month/$day.
-
-C<$description> Is the description to store for the holiday formed by $year/$month/$day.
-
-=cut
-
-sub insert_day_month_holiday {
-    my $self = shift @_;
-    my %options = @_;
-
-    my $dbh = C4::Context->dbh();
-    my $insertHoliday = $dbh->prepare("insert into repeatable_holidays (branchcode,weekday,day,month,title,description) values (?, NULL, ?, ?, ?,? )");
-	$insertHoliday->execute( $self->{branchcode}, $options{day},$options{month},$options{title}, $options{description});
-    $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{title} = $options{title};
-    $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{description} = $options{description};
-    return $self;
-}
-
-=head2 insert_single_holiday
-
-    insert_single_holiday(day => $day,
-                          month => $month,
-                          year => $year,
-                          title => $title,
-                          description => $description);
-
-Inserts a new single holiday for $self->{branchcode}.
-
-C<$day> Is the day month to make the date to insert.
-
-C<$month> Is month to make the date to insert.
-
-C<$year> Is year to make the date to insert.
-
-C<$title> Is the title to store for the holiday formed by $year/$month/$day.
-
-C<$description> Is the description to store for the holiday formed by $year/$month/$day.
-
-=cut
-
-sub insert_single_holiday {
-    my $self = shift @_;
-    my %options = @_;
-    @options{qw(year month day)} = ( $options{date} =~ m/(\d+)-(\d+)-(\d+)/o )
-      if $options{date} && !$options{day};
-
-	my $dbh = C4::Context->dbh();
-    my $isexception = 0;
-    my $insertHoliday = $dbh->prepare("insert into special_holidays (branchcode,day,month,year,isexception,title,description) values (?,?,?,?,?,?,?)");
-	$insertHoliday->execute( $self->{branchcode}, $options{day},$options{month},$options{year}, $isexception, $options{title}, $options{description});
-    $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
-    $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
-
-
-    # changed the 'single_holidays' table, lets force/reset its cache
-    my $cache = Koha::Caches->get_instance();
-    my $key   = $self->{branchcode} . "_holidays";
-    $cache->clear_from_cache($key);
-
-    return $self;
-
-}
-
-=head2 insert_exception_holiday
-
-    insert_exception_holiday(day => $day,
-                             month => $month,
-                             year => $year,
-                             title => $title,
-                             description => $description);
-
-Inserts a new exception holiday for $self->{branchcode}.
-
-C<$day> Is the day month to make the date to insert.
-
-C<$month> Is month to make the date to insert.
-
-C<$year> Is year to make the date to insert.
-
-C<$title> Is the title to store for the holiday formed by $year/$month/$day.
-
-C<$description> Is the description to store for the holiday formed by $year/$month/$day.
-
-=cut
-
-sub insert_exception_holiday {
-    my $self = shift @_;
-    my %options = @_;
-
-    @options{qw(year month day)} = ( $options{date} =~ m/(\d+)-(\d+)-(\d+)/o )
-      if $options{date} && !$options{day};
-
-    my $dbh = C4::Context->dbh();
-    my $isexception = 1;
-    my $insertException = $dbh->prepare("insert into special_holidays (branchcode,day,month,year,isexception,title,description) values (?,?,?,?,?,?,?)");
-	$insertException->execute( $self->{branchcode}, $options{day},$options{month},$options{year}, $isexception, $options{title}, $options{description});
-    $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
-    $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
-
-    # changed the 'single_holidays' table, lets force/reset its cache
-    my $cache = Koha::Caches->get_instance();
-    my $key   = $self->{branchcode} . "_holidays";
-    $cache->clear_from_cache($key);
-
-    return $self;
-}
-
-=head2 ModWeekdayholiday
-
-    ModWeekdayholiday(weekday =>$weekday,
-                      title => $title,
-                      description => $description)
-
-Modifies the title and description of a weekday for $self->{branchcode}.
-
-C<$weekday> Is the title to update for the holiday.
-
-C<$description> Is the description to update for the holiday.
-
-=cut
-
-sub ModWeekdayholiday {
-    my $self = shift @_;
-    my %options = @_;
-
-    my $dbh = C4::Context->dbh();
-    my $updateHoliday = $dbh->prepare("UPDATE repeatable_holidays SET title = ?, description = ? WHERE branchcode = ? AND weekday = ?");
-    $updateHoliday->execute( $options{title},$options{description},$self->{branchcode},$options{weekday}); 
-    $self->{'week_days_holidays'}->{$options{weekday}}{title} = $options{title};
-    $self->{'week_days_holidays'}->{$options{weekday}}{description} = $options{description};
-    return $self;
-}
-
-=head2 ModDaymonthholiday
-
-    ModDaymonthholiday(day => $day,
-                       month => $month,
-                       title => $title,
-                       description => $description);
-
-Modifies the title and description for a day/month holiday for $self->{branchcode}.
-
-C<$day> The day of the month for the update.
-
-C<$month> The month to be used for the update.
-
-C<$title> The title to be updated for the holiday.
-
-C<$description> The description to be update for the holiday.
-
-=cut
-
-sub ModDaymonthholiday {
-    my $self = shift @_;
-    my %options = @_;
-
-    my $dbh = C4::Context->dbh();
-    my $updateHoliday = $dbh->prepare("UPDATE repeatable_holidays SET title = ?, description = ? WHERE month = ? AND day = ? AND branchcode = ?");
-       $updateHoliday->execute( $options{title},$options{description},$options{month},$options{day},$self->{branchcode}); 
-    $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{title} = $options{title};
-    $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{description} = $options{description};
-    return $self;
-}
-
-=head2 ModSingleholiday
-
-    ModSingleholiday(day => $day,
-                     month => $month,
-                     year => $year,
-                     title => $title,
-                     description => $description);
-
-Modifies the title and description for a single holiday for $self->{branchcode}.
-
-C<$day> Is the day of the month to make the update.
-
-C<$month> Is the month to make the update.
-
-C<$year> Is the year to make the update.
-
-C<$title> Is the title to update for the holiday formed by $year/$month/$day.
-
-C<$description> Is the description to update for the holiday formed by $year/$month/$day.
-
-=cut
-
-sub ModSingleholiday {
-    my $self = shift @_;
-    my %options = @_;
-
-    my $dbh = C4::Context->dbh();
-    my $isexception = 0;
-
-    my $updateHoliday = $dbh->prepare("
-UPDATE special_holidays SET title = ?, description = ?
-    WHERE day = ? AND month = ? AND year = ? AND branchcode = ? AND isexception = ?");
-      $updateHoliday->execute($options{title},$options{description},$options{day},$options{month},$options{year},$self->{branchcode},$isexception);    
-    $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
-    $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
-
-    # changed the 'single_holidays' table, lets force/reset its cache
-    my $cache = Koha::Caches->get_instance();
-    my $key   = $self->{branchcode} . "_holidays";
-    $cache->clear_from_cache($key);
-
-    return $self;
-}
-
-=head2 ModExceptionholiday
-
-    ModExceptionholiday(day => $day,
-                        month => $month,
-                        year => $year,
-                        title => $title,
-                        description => $description);
-
-Modifies the title and description for an exception holiday for $self->{branchcode}.
-
-C<$day> Is the day of the month for the holiday.
-
-C<$month> Is the month for the holiday.
-
-C<$year> Is the year for the holiday.
-
-C<$title> Is the title to be modified for the holiday formed by $year/$month/$day.
-
-C<$description> Is the description to be modified for the holiday formed by $year/$month/$day.
-
-=cut
-
-sub ModExceptionholiday {
-    my $self = shift @_;
-    my %options = @_;
-
-    my $dbh = C4::Context->dbh();
-    my $isexception = 1;
-    my $updateHoliday = $dbh->prepare("
-UPDATE special_holidays SET title = ?, description = ?
-    WHERE day = ? AND month = ? AND year = ? AND branchcode = ? AND isexception = ?");
-    $updateHoliday->execute($options{title},$options{description},$options{day},$options{month},$options{year},$self->{branchcode},$isexception);
-    $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
-    $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
-
-    # changed the 'single_holidays' table, lets force/reset its cache
-    my $cache = Koha::Caches->get_instance();
-    my $key   = $self->{branchcode} . "_holidays";
-    $cache->clear_from_cache($key);
-
-    return $self;
-}
-
-=head2 delete_holiday
-
-    delete_holiday(weekday => $weekday
-                   day => $day,
-                   month => $month,
-                   year => $year);
-
-Delete a holiday for $self->{branchcode}.
-
-C<$weekday> Is the week day to delete.
-
-C<$day> Is the day month to make the date to delete.
-
-C<$month> Is month to make the date to delete.
-
-C<$year> Is year to make the date to delete.
-
-=cut
-
-sub delete_holiday {
-    my $self = shift @_;
-    my %options = @_;
-
-    # Verify what kind of holiday that day is. For example, if it is
-    # a repeatable holiday, this should check if there are some exception
-    # for that holiday rule. Otherwise, if it is a regular holiday, it´s
-    # ok just deleting it.
-
-    my $dbh = C4::Context->dbh();
-    my $isSingleHoliday = $dbh->prepare("SELECT id FROM special_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?) AND (year = ?)");
-    $isSingleHoliday->execute($self->{branchcode}, $options{day}, $options{month}, $options{year});
-    if ($isSingleHoliday->rows) {
-        my $id = $isSingleHoliday->fetchrow;
-        $isSingleHoliday->finish; # Close the last query
-
-        my $deleteHoliday = $dbh->prepare("DELETE FROM special_holidays WHERE id = ?");
-        $deleteHoliday->execute($id);
-        delete($self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"});
-    } else {
-        $isSingleHoliday->finish; # Close the last query
-
-        my $isWeekdayHoliday = $dbh->prepare("SELECT id FROM repeatable_holidays WHERE branchcode = ? AND weekday = ?");
-        $isWeekdayHoliday->execute($self->{branchcode}, $options{weekday});
-        if ($isWeekdayHoliday->rows) {
-            my $id = $isWeekdayHoliday->fetchrow;
-            $isWeekdayHoliday->finish; # Close the last query
-
-            my $updateExceptions = $dbh->prepare("UPDATE special_holidays SET isexception = 0 WHERE (WEEKDAY(CONCAT(special_holidays.year,'-',special_holidays.month,'-',special_holidays.day)) = ?) AND (branchcode = ?)");
-            $updateExceptions->execute($options{weekday}, $self->{branchcode});
-            $updateExceptions->finish; # Close the last query
-
-            my $deleteHoliday = $dbh->prepare("DELETE FROM repeatable_holidays WHERE id = ?");
-            $deleteHoliday->execute($id);
-            delete($self->{'week_days_holidays'}->{$options{weekday}});
-        } else {
-            $isWeekdayHoliday->finish; # Close the last query
-
-            my $isDayMonthHoliday = $dbh->prepare("SELECT id FROM repeatable_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?)");
-            $isDayMonthHoliday->execute($self->{branchcode}, $options{day}, $options{month});
-            if ($isDayMonthHoliday->rows) {
-                my $id = $isDayMonthHoliday->fetchrow;
-                $isDayMonthHoliday->finish;
-                my $updateExceptions = $dbh->prepare("UPDATE special_holidays SET isexception = 0 WHERE (special_holidays.branchcode = ?) AND (special_holidays.day = ?) and (special_holidays.month = ?)");
-                $updateExceptions->execute($self->{branchcode}, $options{day}, $options{month});
-                $updateExceptions->finish; # Close the last query
-
-                my $deleteHoliday = $dbh->prepare("DELETE FROM repeatable_holidays WHERE (id = ?)");
-                $deleteHoliday->execute($id);
-                delete($self->{'day_month_holidays'}->{"$options{month}/$options{day}"});
-            }
-        }
-    }
-
-    # changed the 'single_holidays' table, lets force/reset its cache
-    my $cache = Koha::Caches->get_instance();
-    my $key   = $self->{branchcode} . "_holidays";
-    $cache->clear_from_cache($key);
-
-    return $self;
-}
-=head2 delete_holiday_range
-
-    delete_holiday_range(day => $day,
-                   month => $month,
-                   year => $year);
-
-Delete a holiday range of dates for $self->{branchcode}.
-
-C<$day> Is the day month to make the date to delete.
-
-C<$month> Is month to make the date to delete.
-
-C<$year> Is year to make the date to delete.
-
-=cut
-
-sub delete_holiday_range {
-    my $self = shift;
-    my %options = @_;
-
-    my $dbh = C4::Context->dbh();
-    my $sth = $dbh->prepare("DELETE FROM special_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?) AND (year = ?)");
-    $sth->execute($self->{branchcode}, $options{day}, $options{month}, $options{year});
-
-    # changed the 'single_holidays' table, lets force/reset its cache
-    my $cache = Koha::Caches->get_instance();
-    my $key   = $self->{branchcode} . "_holidays";
-    $cache->clear_from_cache($key);
-
-}
-
-=head2 delete_holiday_range_repeatable
-
-    delete_holiday_range_repeatable(day => $day,
-                   month => $month);
-
-Delete a holiday for $self->{branchcode}.
-
-C<$day> Is the day month to make the date to delete.
-
-C<$month> Is month to make the date to delete.
-
-=cut
-
-sub delete_holiday_range_repeatable {
-    my $self = shift;
-    my %options = @_;
-
-    my $dbh = C4::Context->dbh();
-    my $sth = $dbh->prepare("DELETE FROM repeatable_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?)");
-    $sth->execute($self->{branchcode}, $options{day}, $options{month});
-}
-
-=head2 delete_exception_holiday_range
-
-    delete_exception_holiday_range(weekday => $weekday
-                   day => $day,
-                   month => $month,
-                   year => $year);
-
-Delete a holiday for $self->{branchcode}.
-
-C<$day> Is the day month to make the date to delete.
-
-C<$month> Is month to make the date to delete.
-
-C<$year> Is year to make the date to delete.
-
-=cut
-
-sub delete_exception_holiday_range {
-    my $self = shift;
-    my %options = @_;
-
-    my $dbh = C4::Context->dbh();
-    my $sth = $dbh->prepare("DELETE FROM special_holidays WHERE (branchcode = ?) AND (isexception = 1) AND (day = ?) AND (month = ?) AND (year = ?)");
-    $sth->execute($self->{branchcode}, $options{day}, $options{month}, $options{year});
-
-    # changed the 'single_holidays' table, lets force/reset its cache
-    my $cache = Koha::Caches->get_instance();
-    my $key   = $self->{branchcode} . "_holidays";
-    $cache->clear_from_cache($key);
-}
-
-=head2 isHoliday
-
-    $isHoliday = isHoliday($day, $month $year);
-
-C<$day> Is the day to check whether if is a holiday or not.
-
-C<$month> Is the month to check whether if is a holiday or not.
-
-C<$year> Is the year to check whether if is a holiday or not.
-
-=cut
-
-sub isHoliday {
-    my ($self, $day, $month, $year) = @_;
-	# FIXME - date strings are stored in non-padded metric format. should change to iso.
-	$month=$month+0;
-	$year=$year+0;
-	$day=$day+0;
-    my $weekday = &Date::Calc::Day_of_Week($year, $month, $day) % 7;
-    my $weekDays   = $self->get_week_days_holidays();
-    my $dayMonths  = $self->get_day_month_holidays();
-    my $exceptions = $self->get_exception_holidays();
-    my $singles    = $self->get_single_holidays();
-    if (defined($exceptions->{"$year/$month/$day"})) {
-        return 0;
-    } else {
-        if ((exists($weekDays->{$weekday})) ||
-            (exists($dayMonths->{"$month/$day"})) ||
-            (exists($singles->{"$year/$month/$day"}))) {
-            return 1;
-        } else {
-            return 0;
-        }
-    }
-
-}
-
-=head2 copy_to_branch
-
-    $calendar->copy_to_branch($target_branch)
-
-=cut
-
-sub copy_to_branch {
-    my ($self, $target_branch) = @_;
-
-    croak "No target_branch" unless $target_branch;
-
-    my $target_calendar = C4::Calendar->new(branchcode => $target_branch);
-
-    my ($y, $m, $d) = Today();
-    my $today = sprintf ISO_DATE_FORMAT, $y,$m,$d;
-
-    my $wdh = $self->get_week_days_holidays;
-    my $target_wdh = $target_calendar->get_week_days_holidays;
-    foreach my $key (keys %$wdh) {
-        unless (grep { $_ eq $key } keys %$target_wdh) {
-            $target_calendar->insert_week_day_holiday( weekday => $key, %{ $wdh->{$key} } )
-        }
-    }
-
-    my $dmh = $self->get_day_month_holidays;
-    my $target_dmh = $target_calendar->get_day_month_holidays;
-    foreach my $values (values %$dmh) {
-        unless (grep { $_->{day} eq $values->{day} && $_->{month} eq $values->{month} } values %$target_dmh) {
-            $target_calendar->insert_day_month_holiday(%{ $values });
-        }
-    }
-
-    my $exception_holidays = $self->get_exception_holidays;
-    my $target_exceptions = $target_calendar->get_exception_holidays;
-    foreach my $values ( grep {$_->{date} gt $today} values %{ $exception_holidays }) {
-        unless ( grep { $_->{date} eq $values->{date} } values %$target_exceptions) {
-            $target_calendar->insert_exception_holiday(%{ $values });
-        }
-    }
-
-    my $single_holidays = $self->get_single_holidays;
-    my $target_singles = $target_calendar->get_single_holidays;
-    foreach my $values ( grep {$_->{date} gt $today} values %{ $single_holidays }) {
-        unless ( grep { $_->{date} eq $values->{date} } values %$target_singles){
-            $target_calendar->insert_single_holiday(%{ $values });
-        }
-    }
-
-    return 1;
-}
-
-1;
-
-__END__
-
-=head1 AUTHOR
-
-Koha Physics Library UNLP <matias_veleda@hotmail.com>
-
-=cut
-
diff --git a/C4/Circulation.pm b/C4/Circulation.pm
index 50c436ecba..38624fa1b5 100644
--- a/C4/Circulation.pm
+++ b/C4/Circulation.pm
@@ -42,7 +42,7 @@ use Koha::Account;
 use Koha::AuthorisedValues;
 use Koha::Biblioitems;
 use Koha::DateUtils;
-use Koha::Calendar;
+use Koha::DiscreteCalendar;
 use Koha::Checkouts;
 use Koha::Illrequests;
 use Koha::Items;
@@ -1325,7 +1325,7 @@ sub checkHighHolds {
                 branchcode   => $branchcode,
             }
         );
-        my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode );
+        my $calendar = Koha::DiscreteCalendar->new( branchcode => $branchcode, days_mode => $daysmode );
 
         my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $borrower );
 
@@ -2462,7 +2462,7 @@ sub _calculate_new_debar_dt {
         my $new_debar_dt;
         # Use the calendar or not to calculate the debarment date
         if ( C4::Context->preference('SuspensionsCalendar') eq 'noSuspensionsWhenClosed' ) {
-            my $calendar = Koha::Calendar->new(
+            my $calendar = Koha::DiscreteCalendar->new(
                 branchcode => $branchcode,
                 days_mode  => 'Calendar'
             );
@@ -3650,7 +3650,7 @@ sub CalcDateDue {
         else { # days
             $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
         }
-        my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode );
+        my $calendar = Koha::DiscreteCalendar->new( branchcode => $branch, days_mode => $daysmode );
         $datedue = $calendar->addDate( $datedue, $dur, $loanlength->{lengthunit} );
         if ($loanlength->{lengthunit} eq 'days') {
             $datedue->set_hour(23);
@@ -3689,14 +3689,13 @@ sub CalcDateDue {
             }
         }
         if ( $daysmode ne 'Days' ) {
-          my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode );
+          my $calendar = Koha::DiscreteCalendar->new( branchcode => $branch, days_mode => $daysmode );
           if ( $calendar->is_holiday($datedue) ) {
               # Don't return on a closed day
-              $datedue = $calendar->prev_open_days( $datedue, 1 );
+              $datedue = $calendar->prev_open_days( $datedue )->set(hour => 23, minute => 59);
           }
         }
     }
-
     return $datedue;
 }
 
diff --git a/C4/HoldsQueue.pm b/C4/HoldsQueue.pm
index 87144193ff..fdd06d77e8 100644
--- a/C4/HoldsQueue.pm
+++ b/C4/HoldsQueue.pm
@@ -31,7 +31,7 @@ use C4::Biblio;
 use Koha::DateUtils;
 use Koha::Items;
 use Koha::Patrons;
-
+use Koha::DiscreteCalendar;
 use List::Util qw(shuffle);
 use List::MoreUtils qw(any);
 use Data::Dumper;
@@ -81,7 +81,7 @@ sub TransportCostMatrix {
         };
 
         if ( !$ignore_holds_queue_skip_closed && C4::Context->preference("HoldsQueueSkipClosed") ) {
-            $calendars->{$from} ||= Koha::Calendar->new( branchcode => $from );
+            $calendars->{$from} ||= Koha::DiscreteCalendar->new( branchcode => $from );
             $transport_cost_matrix{$to}{$from}{disable_transfer} ||=
               $calendars->{$from}->is_holiday( $today );
         }
@@ -208,7 +208,7 @@ sub CreateQueue {
         $total_requests        += scalar(@$hold_requests);
         $total_available_items += scalar(@$available_items);
 
-        my $item_map = MapItemsToHoldRequests($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix);
+       my $item_map = MapItemsToHoldRequests($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix);
         $item_map  or next;
         my $item_map_size = scalar(keys %$item_map)
           or next;
@@ -765,7 +765,7 @@ sub load_branches_to_pull_from {
     my $today = dt_from_string();
     if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
         @branches_to_use = grep {
-            !Koha::Calendar->new( branchcode => $_ )
+            !Koha::DiscreteCalendar->new({ branchcode => $_ })
               ->is_holiday( $today )
         } @branches_to_use;
     }
diff --git a/C4/Overdues.pm b/C4/Overdues.pm
index d484c67b0f..920890cc54 100644
--- a/C4/Overdues.pm
+++ b/C4/Overdues.pm
@@ -33,6 +33,7 @@ use C4::Accounts;
 use C4::Log; # logaction
 use C4::Debug;
 use Koha::DateUtils;
+use Koha::DiscreteCalendar;
 use Koha::Account::Lines;
 use Koha::Account::Offsets;
 use Koha::Libraries;
@@ -304,7 +305,7 @@ sub get_chargeable_units {
     my $charge_duration;
     if ($unit eq 'hours') {
         if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
-            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
+            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
             $charge_duration = $calendar->hours_between( $date_due, $date_returned );
         } else {
             $charge_duration = $date_returned->delta_ms( $date_due );
@@ -316,7 +317,7 @@ sub get_chargeable_units {
     }
     else { # days
         if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
-            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
+            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
             $charge_duration = $calendar->days_between( $date_due, $date_returned );
         } else {
             $charge_duration = $date_returned->delta_days( $date_due );
diff --git a/C4/Reserves.pm b/C4/Reserves.pm
index a658646407..229ca74de5 100644
--- a/C4/Reserves.pm
+++ b/C4/Reserves.pm
@@ -34,10 +34,10 @@ use C4::Members::Messaging;
 use C4::Members;
 use Koha::Account::Lines;
 use Koha::Biblios;
-use Koha::Calendar;
 use Koha::CirculationRules;
 use Koha::Database;
 use Koha::DateUtils;
+use Koha::DiscreteCalendar;
 use Koha::Hold;
 use Koha::Holds;
 use Koha::ItemTypes;
@@ -941,7 +941,7 @@ sub CancelExpiredReserves {
     my $holds = Koha::Holds->search( $params );
 
     while ( my $hold = $holds->next ) {
-        my $calendar = Koha::Calendar->new( branchcode => $hold->branchcode );
+        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $hold->branchcode });
 
         next if !$cancel_on_holidays && $calendar->is_holiday( $today );
 
diff --git a/Koha/Calendar.pm b/Koha/Calendar.pm
deleted file mode 100644
index c3210d469d..0000000000
--- a/Koha/Calendar.pm
+++ /dev/null
@@ -1,555 +0,0 @@
-package Koha::Calendar;
-
-use Modern::Perl;
-
-use Carp;
-use DateTime;
-use DateTime::Duration;
-use C4::Context;
-use Koha::Caches;
-use Koha::Exceptions;
-
-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::Calendar->new';
-    }
-    $self->_init();
-    return $self;
-}
-
-sub _init {
-    my $self       = shift;
-    my $branch     = $self->{branchcode};
-    my $dbh        = C4::Context->dbh();
-    my $weekly_closed_days_sth = $dbh->prepare(
-'SELECT weekday FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NOT NULL'
-    );
-    $weekly_closed_days_sth->execute( $branch );
-    $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];
-    while ( my $tuple = $weekly_closed_days_sth->fetchrow_hashref ) {
-        $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1;
-    }
-    my $day_month_closed_days_sth = $dbh->prepare(
-'SELECT day, month FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NULL'
-    );
-    $day_month_closed_days_sth->execute( $branch );
-    $self->{day_month_closed_days} = {};
-    while ( my $tuple = $day_month_closed_days_sth->fetchrow_hashref ) {
-        $self->{day_month_closed_days}->{ $tuple->{month} }->{ $tuple->{day} } =
-          1;
-    }
-
-    $self->{test}            = 0;
-    return;
-}
-
-sub _holidays {
-    my ($self) = @_;
-
-    my $key      = $self->{branchcode} . "_holidays";
-    my $cache    = Koha::Caches->get_instance();
-    my $holidays = $cache->get_from_cache($key);
-
-    # $holidays looks like:
-    # {
-    #    20131122 => 1, # single_holiday
-    #    20131123 => 0, # exception_holiday
-    #    ...
-    # }
-
-    # Populate the cache if necessary
-    unless ($holidays) {
-        my $dbh = C4::Context->dbh;
-        $holidays = {};
-
-        # Add holidays for each branch
-        my $holidays_sth = $dbh->prepare(
-'SELECT day, month, year, MAX(isexception) FROM special_holidays WHERE branchcode = ? GROUP BY day, month, year'
-        );
-        $holidays_sth->execute($self->{branchcode});
-
-        while ( my ( $day, $month, $year, $exception ) =
-            $holidays_sth->fetchrow )
-        {
-            my $datestring =
-                sprintf( "%04d", $year )
-              . sprintf( "%02d", $month )
-              . sprintf( "%02d", $day );
-
-            $holidays->{$datestring} = $exception ? 0 : 1;
-        }
-        $cache->set_in_cache( $key, $holidays, { expiry => 76800 } );
-    }
-    return $holidays // {};
-}
-
-sub addDate {
-    my ( $self, $startdate, $add_duration, $unit ) = @_;
-
-
-    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addDate: days_mode")
-        unless exists $self->{days_mode};
-
-    # 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)
-
-    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addHours: days_mode")
-        unless exists $self->{days_mode};
-
-    if ( $self->{days_mode} ne 'Days' &&
-          $self->is_holiday($base_date) ) {
-
-        if ( $hours_duration->is_negative() ) {
-            $base_date = $self->prev_open_days($base_date, 1);
-        } else {
-            $base_date = $self->next_open_days($base_date, 1);
-        }
-
-        $base_date->set_hour($return_by_hour);
-
-    }
-
-    return $base_date;
-}
-
-sub addDays {
-    my ( $self, $startdate, $days_duration ) = @_;
-    my $base_date = $startdate->clone();
-
-    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addDays: days_mode")
-        unless exists $self->{days_mode};
-
-    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_days($base_date, 1);
-                --$days;
-            }
-        } else {
-            while ($days) {
-                $base_date = $self->next_open_days($base_date, 1);
-                --$days;
-            }
-        }
-
-    } else { # Days, Datedue or Dayweek
-        # use straight days, then use calendar to push
-        # 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' ||
-            $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_days($base_date, $push_amt);
-                } else {
-                    $base_date = $self->next_open_days($base_date, $push_amt);
-                }
-            }
-        }
-    }
-
-    return $base_date;
-}
-
-sub get_push_amt {
-    my ( $self, $base_date) = @_;
-
-    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->get_push_amt: days_mode")
-        unless exists $self->{days_mode};
-
-    my $dow = $base_date->day_of_week;
-    # Representation fix
-    # DateTime object dow (1-7) where Monday is 1
-    # Arrays are 0-based where 0 = Sunday, not 7.
-    if ( $dow == 7 ) {
-        $dow = 0;
-    }
-
-    return (
-        # We're using Dayweek useDaysMode option
-        $self->{days_mode} eq 'Dayweek' &&
-        # It's not a permanently closed day
-        !$self->{weekly_closed_days}->[$dow]
-    ) ? 7 : 1;
-}
-
-sub is_holiday {
-    my ( $self, $dt ) = @_;
-
-    my $localdt = $dt->clone();
-    my $day   = $localdt->day;
-    my $month = $localdt->month;
-    my $ymd   = $localdt->ymd('');
-
-    #Change timezone to "floating" before doing any calculations or comparisons
-    $localdt->set_time_zone("floating");
-    $localdt->truncate( to => 'day' );
-
-    return $self->_holidays->{$ymd} if defined($self->_holidays->{$ymd});
-
-    my $dow = $localdt->day_of_week;
-    # Representation fix
-    # DateTime object dow (1-7) where Monday is 1
-    # Arrays are 0-based where 0 = Sunday, not 7.
-    if ( $dow == 7 ) {
-        $dow = 0;
-    }
-
-    if ( $self->{weekly_closed_days}->[$dow] == 1 ) {
-        return 1;
-    }
-
-    if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) {
-        return 1;
-    }
-
-    # damn have to go to work after all
-    return 0;
-}
-
-sub next_open_days {
-    my ( $self, $dt, $to_add ) = @_;
-
-    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->next_open_days: days_mode")
-        unless exists $self->{days_mode};
-
-    my $base_date = $dt->clone();
-
-    $base_date->add(days => $to_add);
-    while ($self->is_holiday($base_date)) {
-        my $add_next = $self->get_push_amt($base_date);
-        $base_date->add(days => $add_next);
-    }
-    return $base_date;
-}
-
-sub prev_open_days {
-    my ( $self, $dt, $to_sub ) = @_;
-
-    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->get_open_days: days_mode")
-        unless exists $self->{days_mode};
-
-    my $base_date = $dt->clone();
-
-    # 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)) {
-        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;
-
-    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->days_forward: days_mode")
-        unless exists $self->{days_mode};
-
-    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;
-
-    # 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 $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 => $delta_days );
-}
-
-sub hours_between {
-    my ($self, $start_date, $end_date) = @_;
-    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;
-    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 {
-    my ( $self, $mode ) = @_;
-
-    # if not testing this is a no op
-    if ( $self->{test} ) {
-        $self->{days_mode} = $mode;
-    }
-
-    return;
-}
-
-sub clear_weekly_closed_days {
-    my $self = shift;
-    $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];    # Sunday only
-    return;
-}
-
-1;
-__END__
-
-=head1 NAME
-
-Koha::Calendar - Object containing a branches calendar
-
-=head1 SYNOPSIS
-
-  use Koha::Calendar
-
-  my $c = Koha::Calendar->new( branchcode => 'MAIN' );
-  my $dt = dt_from_string();
-
-  # are we open
-  $open = $c->is_holiday($dt);
-  # when will item be due if loan period = $dur (a DateTime::Duration object)
-  $duedate = $c->addDate($dt,$dur,'days');
-
-
-=head1 DESCRIPTION
-
-  Implements those features of C4::Calendar needed for Staffs Rolling Loans
-
-=head1 METHODS
-
-=head2 new : Create a calendar object
-
-my $calendar = Koha::Calendar->new( branchcode => 'MAIN' );
-
-The option branchcode is required
-
-
-=head2 addDate
-
-    my $dt = $calendar->addDate($date, $dur, $unit)
-
-C<$date> is a DateTime object representing the starting date of the interval.
-
-C<$offset> is a DateTime::Duration to add to it
-
-C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
-
-Currently unit is only used to invoke Staffs return Monday at 10 am rule this
-parameter will be removed when issuingrules properly cope with that
-
-
-=head2 addHours
-
-    my $dt = $calendar->addHours($date, $dur, $return_by_hour )
-
-C<$date> is a DateTime object representing the starting date of the interval.
-
-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
-
-    my $dt = $calendar->addDays($date, $dur)
-
-C<$date> is a DateTime object representing the starting date of the interval.
-
-C<$offset> is a DateTime::Duration to add to it
-
-C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
-
-Currently unit is only used to invoke Staffs return Monday at 10 am rule this
-parameter will be removed when issuingrules properly cope with that
-
-=head2 is_holiday
-
-$yesno = $calendar->is_holiday($dt);
-
-passed a 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. 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 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_days
-
-$datetime = $calendar->next_open_days($duedate_dt, $to_add)
-
-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_days
-
-$datetime = $calendar->prev_open_days($duedate_dt, $to_sub)
-
-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 days_forward
-
-$datetime = $calendar->days_forward($start_dt, $to_add)
-
-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 set_daysmode
-
-For testing only allows the calling script to change days mode
-
-=head2 clear_weekly_closed_days
-
-In test mode changes the testing set of closed days to a new set with
-no closed days. TODO passing an array of closed days to this would
-allow testing of more configurations
-
-=head2 add_holiday
-
-Passed a datetime object this will add it to the calendar's list of
-closed days. This is for testing so that we can alter the Calenfar object's
-list of specified dates
-
-=head1 DIAGNOSTICS
-
-Will croak if not passed a branchcode in new
-
-=head1 BUGS AND LIMITATIONS
-
-This only contains a limited subset of the functionality in C4::Calendar
-Only enough to support Staffs Rolling loans
-
-=head1 AUTHOR
-
-Colin Campbell colin.campbell@ptfs-europe.com
-
-=head1 LICENSE AND COPYRIGHT
-
-Copyright (c) 2011 PTFS-Europe Ltd All rights reserved
-
-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>.
diff --git a/Koha/Charges/Fees.pm b/Koha/Charges/Fees.pm
index 7eff76ba79..c600bdd4c9 100644
--- a/Koha/Charges/Fees.pm
+++ b/Koha/Charges/Fees.pm
@@ -21,7 +21,7 @@ use Modern::Perl;
 
 use Carp qw( carp confess );
 
-use Koha::Calendar;
+use Koha::DiscreteCalendar;
 use Koha::DateUtils qw( dt_from_string );
 use Koha::Exceptions;
 
@@ -109,7 +109,7 @@ sub accumulate_rentalcharge {
     return 0 unless $rentalcharge_increment && $rentalcharge_increment > 0;
 
     my $duration;
-    my $calendar = Koha::Calendar->new( branchcode => $self->library->id );
+    my $calendar = Koha::DiscreteCalendar->new( branchcode => $self->library->id );
 
     if ( $units eq 'hours' ) {
         if ( $itemtype->rentalcharge_hourly_calendar ) {
diff --git a/Koha/DiscreteCalendar.pm b/Koha/DiscreteCalendar.pm
new file mode 100644
index 0000000000..90682f2025
--- /dev/null
+++ b/Koha/DiscreteCalendar.pm
@@ -0,0 +1,1317 @@
+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 Modern::Perl;
+
+use CGI qw ( -utf8 );
+use Carp;
+use DateTime;
+use DateTime::Format::Strptime;
+use Data::Dumper;
+
+use C4::Context;
+use C4::Output;
+use Koha::Database;
+use Koha::DateUtils;
+
+# Global variables to make code more readable
+our $HOLIDAYS = {
+    EXCEPTION => 'E',
+    REPEATABLE => 'R',
+    SINGLE => 'S',
+    NEED_VALIDATION => 'N',
+    FLOAT => 'F',
+    WEEKLY => 'W',
+    NONE => 'none'
+};
+
+=head1 NAME
+
+Koha::DiscreteCalendar - Object containing a branches calendar, working with the SQL database
+
+=head1 SYNOPSIS
+
+  use Koha::DiscreteCalendar
+
+  my $c = Koha::DiscreteCalendar->new({ branchcode => 'MAIN' });
+  my $dt = DateTime->now();
+
+  # are we open
+  $open = $c->is_holiday($dt);
+  # when will item be due if loan period = $dur (a DateTime::Duration object)
+  $duedate = $c->addDate($dt,$dur,'days');
+
+
+=head1 DESCRIPTION
+
+  Implements a new Calendar object, but uses the SQL database to keep track of days and holidays.
+  This results in a performance gain since the optimization is done by the MySQL database/team.
+
+=head1 METHODS
+
+=head2 new : Create a (discrete) calendar object
+
+my $calendar = Koha::DiscreteCalendar->new({ branchcode => 'MAIN' });
+
+The option branchcode is required
+
+=cut
+
+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} = '';
+        $self->{no_branch_selected} = 1;
+    }
+
+}
+
+=head2 get_dates_info
+
+  my @dates = $calendar->get_dates_info();
+
+Returns an array of hashes representing the dates in this calendar. The hash
+contains the fields C<$date>, C<$outputdate>, C<$holiday_type>, C<$open_hour>,
+C<$close_hour> and C<$note>.
+
+=cut
+
+sub get_dates_info {
+    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/ holiday_type open_hour close_hour 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,
+            holiday_type => $date->holiday_type() ,
+            open_hour    => $date->open_hour(),
+            close_hour   => $date->close_hour(),
+            note        => $date->note()
+        };
+    }
+
+    return @datesInfos;
+}
+
+=head2 add_new_branch
+
+    Koha::DiscreteCalendar->add_new_branch($copyBranch, $newBranch)
+
+This method will copy everything from a given branch to a new branch
+C<$copyBranch> is the branch to copy from
+C<$newBranch> is the branch to be created, and copy into
+
+=cut
+
+sub add_new_branch {
+    my ( $classname, $copyBranch, $newBranch) = @_;
+
+    $copyBranch = '' 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,
+            is_opened    => $row->is_opened(),
+            holiday_type => $row->holiday_type(),
+            open_hour    => $row->open_hour(),
+            close_hour   => $row->close_hour(),
+        });
+    }
+
+}
+
+=head2 get_date_info
+
+    my $date = $calendar->get_date_info;
+
+Returns a reference-to-hash representing a DiscreteCalendar date data object.
+The hash contains the fields C<$date>, C<$outputdate>, C<$holiday_type>,
+C<$open_hour>, C<$close_hour> and C<$note>.
+
+=cut
+
+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 holiday_type open_hour close_hour note/]
+        },
+    );
+    my $dateDTO;
+    while (my $date = $rs->next()){
+        $dateDTO = {
+            date        => $date->date(),
+            branchcode  => $date->branchcode(),
+            holiday_type => $date->holiday_type() ,
+            open_hour    => $date->open_hour(),
+            close_hour   => $date->close_hour(),
+            note        => $date->note()
+        };
+    }
+
+    return $dateDTO;
+}
+
+=head2 get_max_date
+
+    my $maxDate = $calendar->get_max_date();
+
+Returns the furthest date available in the databse of current branch.
+
+=cut
+
+sub get_max_date {
+    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');
+}
+
+=head2 get_min_date
+
+    my $minDate = $calendar->get_min_date();
+
+Returns the oldest date available in the databse of current branch.
+
+=cut
+
+sub get_min_date {
+    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');
+}
+
+=head2 get_unique_holidays
+
+  my @unique_holidays = $calendar->get_unique_holidays();
+
+Returns an array of all the date objects that are unique holidays.
+
+=cut
+
+sub get_unique_holidays {
+    my $self = shift;
+    my $exclude_past = shift || 1;
+    my $branchcode = $self->{branchcode};
+    my @unique_holidays;
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            holiday_type => $HOLIDAYS->{EXCEPTION}
+        },
+        {
+            select => [{ DATE => 'date' }, 'note' ],
+            as     => [qw/ date note/],
+            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
+        }
+    );
+    while (my $date = $rs->next()){
+        my $outputdate = dt_from_string($date->date(), 'iso');
+        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
+        push @unique_holidays, {
+            date =>  $date->date(),
+            outputdate => $outputdate,
+            note => $date->note()
+        }
+    }
+
+    return @unique_holidays;
+}
+
+=head2 get_float_holidays
+
+  my @float_holidays = $calendar->get_float_holidays();
+
+Returns an array of all the date objects that are float holidays.
+
+=cut
+
+sub get_float_holidays {
+    my $self = shift;
+    my $exclude_past = shift || 1;
+    my $branchcode = $self->{branchcode};
+    my @float_holidays;
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            holiday_type => $HOLIDAYS->{FLOAT}
+        },
+        {
+            select => [{ DATE => 'date' }, 'note' ],
+            as     => [qw/ date note/],
+            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
+        }
+    );
+    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;
+}
+
+=head2 get_need_validation_holidays
+
+  my @need_validation_holidays = $calendar->get_need_validation_holidays();
+
+Returns an array of all the date objects that are float holidays in need of validation.
+
+=cut
+
+sub get_need_validation_holidays {
+    my $self = shift;
+    my $exclude_past = shift || 1;
+    my $branchcode = $self->{branchcode};
+    my @need_validation_holidays;
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            holiday_type => $HOLIDAYS->{NEED_VALIDATION}
+        },
+        {
+            select => [{ DATE => 'date' }, 'note' ],
+            as     => [qw/ date note/],
+            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
+        }
+    );
+    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;
+}
+
+=head2 get_repeatable_holidays
+
+  my @repeatable_holidays = $calendar->get_repeatable_holidays();
+
+Returns an array of all the date objects that are repeatable holidays.
+
+=cut
+
+sub get_repeatable_holidays {
+    my $self = shift;
+    my $exclude_past = shift || 1;
+    my $branchcode = $self->{branchcode};
+    my @repeatable_holidays;
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            holiday_type => $HOLIDAYS->{'REPEATABLE'},
+
+        },
+        {
+            select  => \[ 'distinct DAY(date), MONTH(date), note'],
+            as      => [qw/ day month note/],
+            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
+        }
+    );
+
+    while (my $date = $rs->next()){
+        push @repeatable_holidays, {
+            day=> $date->get_column('day'),
+            month => $date->get_column('month'),
+            note => $date->note()
+        };
+    }
+
+    return @repeatable_holidays;
+}
+
+=head2 get_week_days_holidays
+
+  my @week_days_holidays = $calendar->get_week_days_holidays;
+
+Returns an array of all the date objects that are weekly holidays.
+
+=cut
+
+sub get_week_days_holidays {
+    my $self = shift;
+    my $exclude_past = shift || 1;
+    my $branchcode = $self->{branchcode};
+    my @week_days;
+    my $schema = Koha::Database->new->schema;
+
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            holiday_type => $HOLIDAYS->{WEEKLY},
+            branchcode  => $branchcode,
+        },
+        {
+            select      => [{ DAYOFWEEK => 'date'}, 'note'],
+            as          => [qw/ weekday note /],
+            distinct    => 1,
+            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
+        }
+    );
+
+    while (my $date = $rs->next()){
+        push @week_days, {
+            weekday => ($date->get_column('weekday') -1),
+            note    => $date->note()
+        };
+    }
+
+    return @week_days;
+}
+
+=head2 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 or the value everyday when it's for the whole range.
+
+C<$holiday_type> 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 : Repeatable holiday, repeated on same date.
+    W : Weekly holiday, same day of the week.
+
+C<$open_hour> Is the opening hour.
+C<$close_hour> Is the closing hour.
+C<$start_date> Is the start of the range of dates.
+C<$end_date> Is the end of the range of dates.
+C<$delete_type> Delete all
+C<$today> Today based on the local date, using JavaScript.
+
+=cut
+
+sub edit_holiday {
+    my $self = shift;
+    my ($params) = @_;
+
+    my $title        = $params->{title};
+    my $weekday      = $params->{weekday} || '';
+    my $holiday_type = $params->{holiday_type};
+
+    my $start_date   = $params->{start_date};
+    my $end_date     = $params->{end_date};
+
+    my $open_hour    = $params->{open_hour} || '';
+    my $close_hour   = $params->{close_hour} || '';
+
+    my $delete_type  = $params->{delete_type} || undef;
+    my $today        = $params->{today} || dt_from_string()->truncate( to => 'day' );
+
+    my $branchcode = $self->{branchcode};
+
+    # When override param is set, this function will allow past dates to be set as holidays,
+    # otherwise it will not. This is meant to only be used for testing.
+    my $override = $params->{override} || 0;
+
+    my $schema = Koha::Database->new->schema;
+    $schema->{AutoCommit} = 0;
+    $schema->storage->txn_begin;
+    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
+
+    #String dates for Database usage
+    my $start_date_string = $dtf->format_datetime($start_date->clone->truncate(to => 'day'));
+    my $end_date_string = $dtf->format_datetime($end_date->clone->truncate(to => 'day'));
+    $today = $dtf->format_datetime($today->clone->truncate(to => 'day'));
+    my %updateValues = (
+        is_opened    => 0,
+        note         => $title,
+        holiday_type => $holiday_type,
+    );
+    $updateValues{open_hour}  = $open_hour if $open_hour ne '';
+    $updateValues{close_hour}  = $close_hour if $close_hour ne '';
+
+    if($holiday_type eq $HOLIDAYS->{WEEKLY}) {
+        #Update weekly holidays
+        if($start_date_string eq $end_date_string ){
+            $end_date_string = $self->get_max_date();
+        }
+        my $rs = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $branchcode,
+            },
+            {
+                where => \[ 'DAYOFWEEK(date) = ? AND date >= DATE(?) AND date <= DATE(?)', $weekday, $start_date_string, $end_date_string],
+            }
+        );
+
+        while (my $date = $rs->next()){
+            $date->update(\%updateValues);
+        }
+    }elsif ($holiday_type eq $HOLIDAYS->{EXCEPTION} || $holiday_type eq $HOLIDAYS->{FLOAT} || $holiday_type eq $HOLIDAYS->{NEED_VALIDATION}) {
+        #Update Exception Float and Needs Validation holidays
+        my $where = { date => { -between => [$start_date_string, $end_date_string]}};
+        if($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday'){
+            $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]};
+        }
+        $where->{date}{'>='} = $today unless $override;
+
+        my $rs = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $branchcode,
+            },
+            {
+                where =>  $where,
+            }
+        );
+        while (my $date = $rs->next()){
+            $date->update(\%updateValues);
+        }
+
+    }elsif ($holiday_type eq $HOLIDAYS->{REPEATABLE}) {
+        #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
+        $start_date = $parser->format_datetime($start_date);
+        $end_date = $parser->format_datetime($end_date);
+        my $where = { -and => [ \["(DATE_FORMAT(date,'\%m-\%d') BETWEEN ? AND ?)", $start_date, $end_date] ] };
+        push @{$where->{'-and'}}, { 'date' => { '>=' => $today } } unless $override;
+        my $rs = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $branchcode,
+            },
+            {
+                where => $where,
+            }
+        );
+        while (my $date = $rs->next()){
+            $date->update(\%updateValues);
+        }
+
+    }else {
+        #Update date(s)/Remove holidays
+        my $where = { date => { -between => [$start_date_string, $end_date_string]}};
+        if($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday'){
+            $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]};
+        }
+        $where->{date}{'>='} = $today unless $override;
+
+        my $rs = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $branchcode,
+            },
+            {
+                where =>  $where,
+            }
+        );
+        #If none, the date(s) will be normal days, else,
+        if($holiday_type eq 'none'){
+            $updateValues{holiday_type}  ='';
+            $updateValues{is_opened}  =1;
+        }else{
+            delete $updateValues{holiday_type};
+            delete $updateValues{is_opened};
+        }
+
+        while (my $date = $rs->next()){
+            if($delete_type){
+                if($date->holiday_type() eq $HOLIDAYS->{WEEKLY}){
+                    $self->remove_weekly_holidays($weekday, \%updateValues, $today);
+                }elsif($date->holiday_type() eq $HOLIDAYS->{REPEATABLE}){
+                    $self->remove_repeatable_holidays($start_date, $end_date, \%updateValues, $today);
+                }
+            }else{
+                $date->update(\%updateValues);
+            }
+        }
+    }
+    $schema->storage->txn_commit;
+}
+
+=head2 remove_weekly_holidays
+
+    $calendar->remove_weekly_holidays($weekday, $updateValues, $today);
+
+Removes a weekly holiday and updates the days' parameters
+C<$weekday> is the weekday to un-holiday
+C<$updatevalues> is hashref containing the new parameters
+C<$today> is today's date
+
+=cut
+
+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,
+            is_opened    => 0,
+            holiday_type => $HOLIDAYS->{WEEKLY}
+        },
+        {
+            where => {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { '>=' => $today}]},
+        }
+    );
+
+    while (my $date = $rs->next()){
+        $date->update($updateValues);
+    }
+}
+
+=head2 remove_repeatable_holidays
+
+    $calendar->remove_repeatable_holidays($startDate, $endDate, $today);
+
+Removes a repeatable holiday and updates the days' parameters
+C<$startDatey> is the start date of the repeatable holiday
+C<$endDate> is the end date of the repeatble holiday
+C<$updatevalues> is hashref containing the new parameters
+C<$today> is today's date
+
+=cut
+
+sub remove_repeatable_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,
+            is_opened    => 0,
+            holiday_type => $HOLIDAYS->{REPEATABLE},
+        },
+        {
+            where =>  { -and => [date => { '>=' => $today}, \["(DATE_FORMAT(date,'\%m-\%d') BETWEEN ? AND ?)", $startDate, $endDate]]},
+        }
+    );
+
+    while (my $date = $rs->next()){
+        $date->update($updateValues);
+    }
+}
+
+=head2 copy_to_branch
+
+  $calendar->copy_to_branch($branch2);
+
+Copies the days and holidays from this branch to $branch2, ignoring dates in C<$self>
+but not in C<$branch2>
+
+C<$branch2> the branch to copy into
+
+=cut
+
+sub copy_to_branch {
+    my ($self,$newBranch) =@_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+
+    my $copyFrom = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode
+        },
+        {
+            columns     => [ qw/ date is_opened note holiday_type open_hour close_hour /]
+        }
+    );
+    while (my $copyDate = $copyFrom->next()){
+        my $copyTo = $schema->resultset('DiscreteCalendar')->search(
+            {
+                branchcode  => $newBranch,
+                date        => $copyDate->date(),
+            },
+            {
+                columns => [ qw/ date branchcode is_opened note holiday_type open_hour close_hour /]
+            }
+        );
+        #if the date does not exist in the copyTO branch, than skip it.
+        if($copyTo->count ==0){
+            next;
+        }
+        $copyTo->next()->update({
+            is_opened    => $copyDate->is_opened(),
+            holiday_type => $copyDate->holiday_type(),
+            note        => $copyDate->note(),
+            open_hour    => $copyDate->open_hour(),
+            close_hour   => $copyDate->close_hour()
+        });
+    }
+}
+
+=head2 is_opened
+
+    $calendar->is_opened($date)
+
+Returns whether the library is open on C<$date>
+
+=cut
+
+sub is_opened {
+    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 $is_opened = -1;
+    my $rs = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode => $branchcode,
+        },
+        {
+            where   => \['date = DATE(?)', $date]
+        }
+    );
+    $is_opened = $rs->next()->is_opened() if $rs->count() != 0;
+
+    return $is_opened;
+}
+
+=head2 is_holiday
+
+    $calendar->is_holiday($date)
+
+Returns whether C<$date> is a holiday or not
+
+=cut
+
+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 = ($rs->first()->is_opened() ? 0 : 1);
+    }
+
+    return $isHoliday;
+}
+
+=head2 copy_holiday
+
+  $calendar->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber);
+
+Copies a holiday's parameters from a range to a new range
+C<$from_startDate> the source holiday's start date
+C<$from_endDate> the source holiday's end date
+C<$to_startDate> the destination holiday's start date
+C<$to_endDate> the destination holiday's end date
+C<$daysnumber> the number of days in the range.
+
+Both ranges should have the same number of days in them.
+
+=cut
+
+sub copy_holiday {
+    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 => [$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({
+                is_opened    => $copyDate->is_opened(),
+                holiday_type => $copyDate->holiday_type(),
+                note        => $copyDate->note(),
+                open_hour    => $copyDate->open_hour(),
+                close_hour   => $copyDate->close_hour()
+            })
+        }
+
+    }else{
+        my $endDate = dt_from_string($from_endDate);
+        $to_startDate = $dtf->format_datetime($to_startDate);
+        $to_endDate = $dtf->format_datetime($to_endDate);
+        if($daysnumber == 7){
+            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/ holiday_type note open_hour close_hour note/]
+                    }
+                );
+                my $copyDate = $fromDate->next();
+                my $weekday = $copyDate->get_column('weekday');
+
+                my $toDate = $schema->resultset('DiscreteCalendar')->search(
+                    {
+                        branchcode  => $branchcode,
+
+                    },
+                    {
+                        where       => {date => {-between => [$to_startDate, $to_endDate]}, "DAYOFWEEK(date)" => $weekday},
+                    }
+                );
+                my $copyToDate = $toDate->next();
+                $copyToDate->update({
+                    is_opened    => $copyDate->is_opened(),
+                    holiday_type => $copyDate->holiday_type(),
+                    note        => $copyDate->note(),
+                    open_hour    => $copyDate->open_hour(),
+                    close_hour   => $copyDate->close_hour()
+                });
+
+            }
+        }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({
+                    is_opened    => $copyDate->is_opened(),
+                    holiday_type => $copyDate->holiday_type(),
+                    note        => $copyDate->note(),
+                    open_hour    => $copyDate->open_hour(),
+                    close_hour   => $copyDate->close_hour()
+                });
+                $to_startDate->add(days =>1);
+            }
+        }
+
+
+    }
+}
+
+=head2 days_between
+
+   $cal->days_between( $start_date, $end_date )
+
+Calculates the number of days the library is opened between C<$start_date> and C<$end_date>
+
+=cut
+
+sub days_between {
+    my ($self, $start_date, $end_date, ) = @_;
+    my $branchcode = $self->{branchcode};
+
+    if ( $start_date->compare($end_date) > 0 ) {
+        # swap dates
+        ($start_date, $end_date) = ($end_date, $start_date);
+    }
+
+    my $schema = Koha::Database->new->schema;
+    my $dtf = $schema->storage->datetime_parser;
+    $start_date = $dtf->format_datetime($start_date->clone->truncate(to => 'day'));
+    $end_date = $dtf->format_datetime($end_date->clone->truncate(to => 'day'));
+
+    my $days_between = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            is_opened    => 1,
+        },
+        {
+            where       => \['date >= date(?) AND date < date(?)',$start_date, $end_date]
+        }
+    );
+
+    return DateTime::Duration->new( days => $days_between->count());
+}
+
+=head2 next_open_day
+
+   $open_date = $self->next_open_day($base_date);
+
+Returns a string representing the next day the library is open, starting from C<$base_date>
+
+=cut
+
+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,
+            is_opened    => 1,
+        },
+        {
+            where       => \['date > date(?)', $date],
+            order_by    => { -asc => 'date' },
+            rows        => 1
+        }
+    );
+    return dt_from_string( $rs->next()->date(), 'iso');
+}
+
+=head2 prev_open_day
+
+   $open_date = $self->prev_open_day($base_date);
+
+Returns a string representing the closest previous day the library was open, starting from C<$base_date>
+
+=cut
+
+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,
+            is_opened    => 1,
+        },
+        {
+            where       => \['date < date(?)', $date],
+            order_by    => { -desc => 'date' },
+            rows        => 1
+        }
+    );
+    return dt_from_string( $rs->next()->date(), 'iso');
+}
+
+=head2 days_forward
+
+    $fwrd_date = $calendar->days_forward($start, $count)
+
+Returns the date C<$count> days in the future from C<$start>, ignoring days where the library is closed.
+
+=cut
+
+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_day($base_dt);
+    }
+
+    return $base_dt;
+}
+
+=head2 hours_between
+
+    $hours = $calendar->hours_between($start_dt, $end_dt)
+
+Returns the number of hours between C<$start_dt> and C<$end_dt>. This is the imprecise
+version, which simply calculates the number of day times 24. To take opening hours into account
+see C<open_hours_between>/
+
+=cut
+
+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,
+            is_opened    => 0
+        },
+        {
+            where  => {date => {-between => [$start_date, $end_date]}},
+        }
+    );
+
+    if ($skipped_days = $hours_between->count()) {
+        $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
+    }
+
+    return $duration;
+}
+
+=head2 open_hours_between
+
+  $hours = $calendar->open_hours_between($start_date, $end_date)
+
+Returns the number of hours between C<$start_date> and C<$end_date>, taking into
+account the opening hours of the library.
+
+=cut
+
+sub open_hours_between {
+    my ($self, $start_date, $end_date) = @_;
+    my $branchcode = $self->{branchcode};
+    my $schema = Koha::Database->new->schema;
+    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
+    $start_date = $dtf->format_datetime($start_date);
+    $end_date = $dtf->format_datetime($end_date);
+
+    my $working_hours_between = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+            is_opened    => 1,
+        },
+        {
+            select  => \['sum(time_to_sec(timediff(close_hour, open_hour)) / 3600)'],
+            as      => [qw /hours_between/],
+            where   => \['date BETWEEN DATE(?) AND DATE(?)', $start_date, $end_date]
+        }
+    );
+
+    my $loan_day = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+        },
+        {
+            order_by => \[ 'ABS(DATEDIFF(date, ?))', $start_date ],
+            rows => 1,
+        }
+    );
+
+    my $return_day = $schema->resultset('DiscreteCalendar')->search(
+        {
+            branchcode  => $branchcode,
+        },
+        {
+            order_by => \[ 'ABS(DATEDIFF(date, ?))', $end_date ],
+            rows => 1,
+        }
+    );
+
+    #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,
+            is_opened    => 1,
+        },
+        {
+            select  => \[ '(time_to_sec(timediff(?, ?)) + time_to_sec(timediff(?, ?)) ) / 3600', $return_day->next()->close_hour(), $return_date_time, $loan_date_time, $loan_day->next()->open_hour()],
+            as      => [qw /not_used_hours/],
+        }
+    );
+
+    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
+}
+
+=head2 addDate
+
+  my $dt = $calendar->addDate($date, $dur, $unit)
+
+C<$date> is a DateTime object representing the starting date of the interval.
+C<$offset> is a duration to add to it (DateTime::Duration objects are supported as legacy)
+C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
+
+=cut
+
+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;
+}
+
+=head2 addHours
+
+  $end = $calendar->addHours($start, $hours_duration, $return_by_hour)
+
+Add C<$hours_duration> to C<$start> date.
+C<$return_by_hour> is an integer value representing the opening hour for the branch
+
+=cut
+
+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;
+}
+
+=head2 addDays
+
+  $date = $calendar->addDays($start, $duration)
+
+Add C<$days_duration> to C<$start> date. If the calendar's days_mode is set
+to 'Calendar', it ignores closed days. Else if the calendar is set to 'Datedue'
+it calculates the date normally, and then pushes to result to the next open day.
+
+=cut
+
+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->is_opened($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/Koha/Hold.pm b/Koha/Hold.pm
index 892fb20cd5..be16fcef01 100644
--- a/Koha/Hold.pm
+++ b/Koha/Hold.pm
@@ -35,7 +35,7 @@ use Koha::Biblios;
 use Koha::Items;
 use Koha::Libraries;
 use Koha::Old::Holds;
-use Koha::Calendar;
+use Koha::DiscreteCalendar;
 
 use Koha::Exceptions::Hold;
 
@@ -67,7 +67,7 @@ sub age {
     my $age;
 
     if ( $use_calendar ) {
-        my $calendar = Koha::Calendar->new( branchcode => $self->branchcode );
+        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $self->branchcode });
         $age = $calendar->days_between( dt_from_string( $self->reservedate ), $today );
     }
     else {
@@ -207,7 +207,7 @@ sub set_waiting {
                 branchcode   => $self->branchcode,
             }
         );
-        my $calendar = Koha::Calendar->new( branchcode => $self->branchcode, days_mode => $daysmode );
+        my $calendar = Koha::DiscreteCalendar->new( branchcode => $self->branchcode, days_mode => $daysmode );
 
         $expirationdate = $calendar->days_forward( dt_from_string(), $max_pickup_delay );
     }
diff --git a/circ/returns.pl b/circ/returns.pl
index 116ac3c15e..ed3db5df35 100755
--- a/circ/returns.pl
+++ b/circ/returns.pl
@@ -50,9 +50,9 @@ use C4::Reserves;
 use C4::RotatingCollections;
 use Koha::AuthorisedValues;
 use Koha::BiblioFrameworks;
-use Koha::Calendar;
 use Koha::Checkouts;
 use Koha::DateUtils;
+use Koha::DiscreteCalendar;
 use Koha::Holds;
 use Koha::Items;
 use Koha::Patrons;
@@ -193,6 +193,7 @@ my $dropboxmode = $query->param('dropboxmode');
 my $dotransfer  = $query->param('dotransfer');
 my $canceltransfer = $query->param('canceltransfer');
 my $dest = $query->param('dest');
+my $calendar    = Koha::DiscreteCalendar->new({ branchcode => $userenv_branch });
 #dropbox: get last open day (today - 1)
 my $dropboxdate = Koha::Checkouts::calculate_dropbox_date();
 
diff --git a/koha-tmpl/intranet-tmpl/prog/css/discretecalendar.css b/koha-tmpl/intranet-tmpl/prog/css/discretecalendar.css
new file mode 100644
index 0000000000..3b7625e1dd
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/css/discretecalendar.css
@@ -0,0 +1,206 @@
+#jcalendar-container .ui-datepicker {
+    font-size:185%;
+}
+
+#holidayweeklyrepeatable,
+#holidaysyearlyrepeatable,
+#holidaysunique,
+#holidayexceptions {
+    font-size:90%;
+    margin-bottom:1em;
+}
+
+#showHoliday {
+    margin:.5em 0;
+}
+
+.key {
+    padding:3px;
+    white-space:nowrap;
+    line-height:230%;
+}
+
+.ui-datepicker {
+    font-size:150%;
+}
+
+.ui-datepicker th,
+.ui-datepicker .ui-datepicker-title select {
+    font-size:80%;
+}
+
+.ui-datepicker td a {
+    padding:.5em;
+}
+
+.ui-datepicker td span {
+    padding:.5em;
+    border:1px solid #BCBCBC;
+}
+
+.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:#000;
+    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:#000;
+    border:1px solid #BCBCBC;
+}
+
+.past-date {
+    background-color:#e6e6e6;
+    color:#555;
+    border:1px solid #BCBCBC;
+}
+
+td.past-date a.ui-state-default {
+    background:#e6e6e6;
+    color:#555;
+}
+
+.float {
+    background-color:#6f3;
+    color:#000;
+    border:1px solid #BCBCBC;
+}
+
+.holiday {
+    background-color:#ffaeae;
+    color:#000;
+    border:1px solid #BCBCBC;
+}
+
+.repeatableweekly {
+    background-color:#FF9;
+    color:#000;
+    border:1px solid #BCBCBC;
+}
+
+.repeatableyearly {
+    background-color:#FC6;
+    color:#000;
+    border:1px solid #BCBCBC;
+}
+
+td.exception a.ui-state-default {
+    background:#b3d4ff none;
+    color:#000;
+    border:1px solid #BCBCBC;
+}
+
+td.float a.ui-state-default {
+    background:#6f3 none;
+    color:#000;
+    border:1px solid #BCBCBC;
+}
+
+td.holiday a.ui-state-default {
+    background:#ffaeae none;
+    color:#000;
+    border:1px solid #BCBCBC;
+}
+
+td.repeatableweekly a.ui-state-default {
+    background:#FF9 none;
+    color:#000;
+    border:1px solid #BCBCBC;
+}
+
+td.repeatableyearly a.ui-state-default {
+    background:#FC6 none;
+    color:#000;
+    border:1px solid #BCBCBC;
+}
+
+.information {
+    background-color:#DCD2F1;
+    width:300px;
+    display:none;
+    border:1px solid #000;
+    color:#000;
+    font-size:8pt;
+    font-weight:700;
+    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;
+}
+
+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:.2em 0;
+}
+
+.help {
+    margin:.3em 0;
+    border:1px solid #EEE;
+    padding:.3em .7em;
+    font-size:90%;
+}
+
+.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;
+}
\ No newline at end of file
diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc
index ba3f54a281..97e43fbbcd 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc
@@ -107,7 +107,7 @@
 <h5>Additional tools</h5>
 <ul>
     [% IF ( CAN_user_tools_edit_calendar ) %]
-	<li><a href="/cgi-bin/koha/tools/holidays.pl">Calendar</a></li>
+    <li><a href="/cgi-bin/koha/tools/discrete_calendar.pl">Calendar</a></li>
     [% END %]
     [% IF ( CAN_user_tools_manage_csv_profiles ) %]
 	<li><a href="/cgi-bin/koha/tools/csv-profiles.pl">CSV profiles</a></li>
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 0000000000..52b52c72c0
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt
@@ -0,0 +1,677 @@
+[% USE Asset %]
+[% USE Branches %]
+[% SET footerjs = 1 %]
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Tools &rsaquo; [% Branches.GetName( branch ) %] calendar</title>
+[% INCLUDE 'doc-head-close.inc' %]
+[% Asset.css("css/discretecalendar.css") %]
+</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>
+        <form method="post" onsubmit="return validateForm('CopyCalendar')">
+            <select id="branch" name="branch">
+                [% PROCESS options_for_libraries libraries => Branches.all( selected => branch ) %]
+            </select>
+            Copy calendar to
+            <select id='newBranch' name ='newBranch'>
+                <option value=""></option>
+                [% FOREACH l IN Branches.all() %]
+                    [% UNLESS branch == l.branchcode %]
+                    <option value="[% l.branchcode %]">[% l.branchname %]</option>
+                    [% END %]
+                [% END %]
+            </select>
+            <input type="hidden" name="action" value="copyBranch" />
+            <input type="submit" value="Clone">
+        </form>
+            <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 %]
+
+    [% IF  no_branch_selected %]
+    <div class="alert alert-danger" style="float: left; margin-left:15px">
+        <strong>No library set!</strong> You are using the default 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>Edit date details</h3>
+                <span id="holtype"></span>
+                <ol>
+                    <li>
+                        <strong>Library:</strong>
+                        <span id="newBranchNameOutput"></span>
+                        <input type="hidden" id="branch" name="branch" />
+                    </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="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">Working day</option>
+                            <option value="E">Unique holiday</option>
+                            <option value="W">Weekly holiday</option>
+                            <option value="R">Repeatable holiday</option>
+                            <option value="F">Floating holiday</option>
+                            <option value="N" disabled>Need validation</option>
+                        </select>
+                    </li>
+                    <li id="days_of_week" style="display :none">
+                        <label for="day_of_week">Week day</label>
+                        <select name ='day_of_week'>
+                            <option value="everyday">Everyday</option>
+                            <option value="1">Sundays</option>
+                            <option value="2">Mondays</option>
+                            <option value="3">Tuesdays</option>
+                            <option value="4">Wednesdays</option>
+                            <option value="5">Thursdays</option>
+                            <option value="6">Fridays</option>
+                            <option value="7">Saturdays</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 working 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'>
+                        <input type="hidden" name="local_today" id='local_today'>
+                    </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>Past dates cannot be changed</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="#doc3" onclick="go_to_date('[% 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 ( WEEKLY_HOLIDAYS ) %]
+    <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 WEEKLY_HOLIDAYS %]
+            <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>
+            [% IF ( dateformat == "metric" ) %]
+            <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT %]">[% DAY_MONTH_HOLIDAYS_LOO.day %]/[% DAY_MONTH_HOLIDAYS_LOO.month %]</span></td>
+            [% ELSE %]
+            <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT %]">[% DAY_MONTH_HOLIDAYS_LOO.month %]/[% DAY_MONTH_HOLIDAYS_LOO.day %]</span></td>
+            [% END %]
+            <td>[% DAY_MONTH_HOLIDAYS_LOO.note %]</td>
+        </tr>
+        [% END %]
+    </tbody>
+</table>
+[% END %]
+
+[% IF ( UNIQUE_HOLIDAYS ) %]
+<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 UNIQUE_HOLIDAYS %]
+        <tr>
+            <td><a href="#doc3" onclick="go_to_date('[% 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 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="#doc3" onclick="go_to_date('[% 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>
+[% MACRO jsinclude BLOCK %]
+[% Asset.js("lib/jquery/plugins/jquery-ui-timepicker-addon.min.js") %]
+[% INCLUDE 'calendar.inc' %]
+[% INCLUDE 'datatables.inc' %]
+<script type="text/javascript">
+    //<![CDATA[
+        var weekdays = new Array(_("Sundays"),_("Mondays"),_("Tuesdays"),_("Wednesdays"),_("Thursdays"),_("Fridays"),_("Saturdays"));
+        // Array containing all the information about each date in the calendar.
+        var datesInfos = new Array();
+        [% FOREACH date IN datesInfos %]
+            datesInfos["[% date.date %]"] = {
+                title : "[% date.note %]",
+                outputdate : "[% date.outputdate %]",
+                holiday_type:"[% date.holiday_type %]",
+                open_hour: "[% date.open_hour %]",
+                close_hour: "[% date.close_hour %]"
+            };
+        [% END %]
+
+        /**
+        * Displays the details of the selected date on a side panel
+        */
+        function showHoliday (date_obj, dateString, 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 ,#branch").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);
+            $('#days_of_week option[value="'+ (weekDay + 1)  +'"]').attr('selected', true);
+            $('#openHour').val(datesInfos[dateString].open_hour);
+            $('#closeHour').val(datesInfos[dateString].close_hour);
+            $('#local_today').val(getSeparetedDate(new Date()).dateString);
+
+            //This changes the label of the date type on the edit panel
+            if(holidayType == 'W') {
+                $("#holtype").attr("class","key repeatableweekly").html(_("Holiday repeating weekly"));
+            } else if(holidayType == 'R') {
+                $("#holtype").attr("class","key repeatableyearly").html(_("Holiday repeating yearly"));
+            } else if(holidayType == 'F') {
+                $("#holtype").attr("class","key float").html(_("Floating holiday"));
+            } else if(holidayType == 'N') {
+                $("#holtype").attr("class","key exception").html(_("Needs validation"));
+            } else if(holidayType == 'E') {
+                $("#holtype").attr("class","key holiday").html(_("Unique holiday"));
+            } else{
+                $("#holtype").attr("class","key normalday").html(_("Working day "));
+            }
+
+            //Select the correct holiday type on the dropdown menu
+            if (datesInfos[dateString].holiday_type !=''){
+                var type = datesInfos[dateString].holiday_type;
+                $('#holidayType option[value="'+ type +'"]').attr('selected', true)
+            }else{
+                $('#holidayType option[value="none"]').attr('selected', true)
+            }
+
+            //If it is a weekly or repeatable holiday show the option to delete the type
+            if(datesInfos[dateString].holiday_type == 'W' || datesInfos[dateString].holiday_type == 'R'){
+                $('#deleteType').show("fast");
+            }else{
+                $('#deleteType').hide("fast");
+            }
+
+            //This value is to disable and hide input when the date is in the past, because you can't edit it.
+            var value = false;
+            var today = new Date();
+            today.setHours(0,0,0,0);
+            if(date_obj < today ){
+                $("#holtype").attr("class","key past-date").html(_("Past date"));
+                $("#CopyRadioButton").attr("checked", "checked");
+                value = true;
+                $(".CopyDatePanel").toggle(value);
+            }
+            $("#title").prop('disabled', value);
+            $("#holidayType select").prop('disabled', value);
+            $("#openHour").prop('disabled', value);
+            $("#closeHour").prop('disabled', value);
+            $("#EditRadioButton").parent().toggle(!value);
+
+        }
+
+        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 dateString = date.dateString;
+            var today = new Date();
+            today.setHours(0,0,0,0);
+
+            if (datesInfos[dateString] && datesInfos[dateString].holiday_type =='W'){
+                return [true, "repeatableweekly", _("Weekly holiday: %s").format(datesInfos[dateString].title)];
+            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'R') {
+                return [true, "repeatableyearly", _("Yearly holiday: %s").format(datesInfos[dateString].title)];
+            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'N') {
+                return [true, "exception", _("Need validation: %s").format(datesInfos[dateString].title)];
+            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'F') {
+                return [true, "float", _("Floating holiday: %s").format(datesInfos[dateString].title)];
+            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'E') {
+                return [true, "holiday", _("Single holiday: %s").format(datesInfos[dateString].title)];
+            } else {
+                if(date.date_obj < today ){
+                    return [true, "past-date", _("Past day")];
+                }else{
+                    return [true, "normalday", _("Normal day")];
+                }
+            }
+        }
+
+        /* This function is in charge of showing the correct panel considering the kind of holiday */
+        function dateChanged(text, 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 dateString = date.dateString;
+            var date_obj = date.date_obj;
+            //set value of form hidden field
+            $('#from_copyFrom').val(text);
+
+            showHoliday(date_obj, dateString, dayName, day, month, year, weekDay, datesInfos[dateString].title, datesInfos[dateString].holiday_type);
+        }
+
+        /**
+        * This function separate a given date object a returns an array containing all needed information about the date.
+        */
+        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();
+            //iso date string
+            var dateString = year + '-' + month + '-' + day;
+            mydate = {
+                date_obj : date,
+                dateString : dateString,
+                weekDay: weekDay,
+                year: year,
+                month: month,
+                day: day
+            };
+
+            return mydate;
+        }
+
+        /**
+        * Valide the forms before send them to the backend
+        */
+        function validateForm(form){
+            if(form =='newHoliday' && $('#CopyRadioButton').is(':checked')){
+                if($('#to_copyFromDatePicker').val() =='' || $('#to_copyToDatePicker').val() ==''){
+                    alert("You have to pick a FROM and TO in the Copy to different dates.");
+                    return false;
+                }else if ($('#from_copyToDatePicker').val()){
+                    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 +1;
+                    var to_daysDiff = to_end - to_start + 1;
+                    if(from_daysDiff == to_daysDiff){
+                        $('#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 if(form == 'CopyCalendar'){
+                if ($('#newBranch').val() ==''){
+                    alert("Please select a copy to calendar.");
+                    return false;
+                }else{
+                    return true;
+                }
+            }else {
+                return true;
+            }
+        }
+
+        function go_to_date(isoDate){
+            //I added the time to get around the timezone
+            var date = getSeparetedDate(new Date(isoDate + " 00:00:00"));
+            var day = date.day;
+            var month = date.month;
+            var year = date.year;
+            var weekDay = date.weekDay;
+            var dayName = weekdays[weekDay];
+            var dateString = date.dateString;
+            var date_obj = date.date_obj;
+
+            $("#jcalendar-container").datepicker("setDate", date_obj);
+            showHoliday(date_obj, dateString, dayName, day, month, year, weekDay, datesInfos[dateString].title, datesInfos[dateString].holiday_type);
+        }
+
+        /**
+        *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;
+
+            $('#days_of_week option[value="everyday"]').attr('selected', true);
+            for (var i = fromDate; i <= date ; i.setDate(i.getDate() + 1)) {
+                var myDate1 = getSeparetedDate(i);
+                var date1 = myDate1.dateString;
+                var holidayType1 = datesInfos[date1].holiday_type;
+                var open_hours1 = datesInfos[date1].open_hour;
+                var close_hours1 = datesInfos[date1].close_hour;
+                for (var j = fromDate; j <= date ; j.setDate(j.getDate() + 1)) {
+                    var myDate2 = getSeparetedDate(j);
+                    var date2 = myDate2.dateString;
+                    var holidayType2 = datesInfos[date2].holiday_type;
+                    var open_hours2 = datesInfos[date2].open_hour;
+                    var close_hours2 = datesInfos[date2].close_hour;
+
+                    if (sameHoliday && holidayType1 != holidayType2){
+                        $('#holidayType option[value="empty"]').attr('selected', true);
+                        sameHoliday=false;
+                    }
+                    if(sameOpenHours && (open_hours1 != open_hours2)){
+                        $('#openHour').val('');
+                        sameOpenHours=false;
+                    }
+                    if(sameCloseHours && (close_hours1 != close_hours2)){
+                        $('#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;
+            });
+            //Set the correct coloring, default date and the date ranges for all datepickers
+            $.datepicker.setDefaults({
+                beforeShowDay: function(thedate) {
+                    return dateStatusHandler(thedate);
+                },
+                defaultDate: new Date("[% keydate %]"),
+                minDate: new Date("[% minDate %]"),
+                maxDate: new Date("[% maxDate %]"),
+                dateFormat: "yy-mm-dd"
+            });
+            //Main datepicker
+            $("#jcalendar-container").datepicker({
+                onSelect: function(dateText, inst) {
+                    dateChanged(dateText, $(this).datepicker("getDate"));
+                },
+            });
+            $('#from_copyToDatePicker').datepicker();
+            $("#from_copyToDatePicker").change(function(){
+                checkRange($(this).datepicker("getDate"));
+                $('#from_copyTo').val(($(this).val()));
+                if($('#from_copyToDatePicker').val()){
+                    $('#days_of_week').show("fast");
+                }else{
+                    $('#days_of_week').hide("fast");
+                }
+            });
+            //Datepickers for copy dates feature
+            $('#to_copyFromDatePicker').datepicker();
+            $("#to_copyFromDatePicker").change(function(){
+                $('#to_copyFrom').val(($(this).val()));
+            });
+            $('#to_copyToDatePicker').datepicker();
+            $("#to_copyToDatePicker").change(function(){
+                $('#to_copyTo').val(($(this).val()));
+            });
+            //Timepickers for open and close hours
+            $('#openHour').timepicker({
+                showOn : 'focus',
+                timeFormat: 'HH:mm:ss',
+                showSecond: false,
+                stepMinute: 5,
+            });
+            $('#closeHour').timepicker({
+                showOn : 'focus',
+                timeFormat: 'HH:mm:ss',
+                showSecond: false,
+                stepMinute: 5,
+            });
+
+            $('.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");
+                }
+            });
+
+            $("#deleteType_checkbox").on("change", function(){
+                if($("#deleteType_checkbox").is(':checked')){
+                    $('#holidayType option[value="none"]').attr('selected', true);
+                }
+            });
+            $("#holidayType select").on("change", function(){
+                if($("#holidayType select").val() == "R"){
+                    $('#days_of_week').hide("fast");
+                }else if ($('#from_copyToDatePicker').val()){
+                    $('#days_of_week').show("fast");
+                }
+            });
+        });
+    //]]>
+</script>
+[% END %]
+[% INCLUDE 'intranet-bottom.inc' %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt
index c8d7f7d567..44542edf7f 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt
@@ -91,7 +91,7 @@
 [% END %]
 <dl>
     [% IF ( CAN_user_tools_edit_calendar ) %]
-    <dt><a href="/cgi-bin/koha/tools/holidays.pl">Calendar</a></dt>
+    <dt><a href="/cgi-bin/koha/tools/discrete_calendar.pl">Calendar</a></dt>
     <dd>Define days when the library is closed</dd>
     [% END %]
 
diff --git a/misc/cronjobs/add_days_discrete_calendar.pl b/misc/cronjobs/add_days_discrete_calendar.pl
new file mode 100755
index 0000000000..2e4f3a94da
--- /dev/null
+++ b/misc/cronjobs/add_days_discrete_calendar.pl
@@ -0,0 +1,128 @@
+#!/usr/bin/perl
+
+#
+#   This script adds one day into discrete_calendar table based on the same day from the week before
+#
+use Modern::Perl;
+use DateTime;
+use DateTime::Format::Strptime;
+use Data::Dumper;
+use Getopt::Long;
+use C4::Context;
+use Koha::Database;
+use Koha::DiscreteCalendar;
+
+# Options
+my $help = 0;
+my $daysInFuture = 1;
+my $debug = 0;
+GetOptions (
+    'help|?|h'  => \$help,
+    'n=i'       => \$daysInFuture,
+    'd|?|debug' => \$debug);
+
+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
+    -d --debug: displays all added days and errors if there is any
+
+ENDUSAGE
+
+if ($help) {
+    print $usage;
+    exit;
+}
+
+my $schema = Koha::Database->new->schema;
+$schema->storage->txn_begin;
+my $dbh = C4::Context->dbh;
+
+# Predeclaring variables that will be used several times in the code
+my $query;
+my $statement;
+
+#getting the all the branches
+$query = 'SELECT branchcode FROM discrete_calendar GROUP BY branchcode';
+$statement = $dbh->prepare($query);
+$statement->execute();
+my @branches = ();
+for my $branchcode ($statement->fetchrow_array ) {
+    push @branches,$branchcode;
+}
+
+#get the latest date in the table
+$query = "SELECT MAX(date) FROM discrete_calendar";
+$statement = $dbh->prepare($query);
+$statement->execute();
+my $latestedDate = $statement->fetchrow_array;
+my $parser = DateTime::Format::Strptime->new(
+    pattern => '%Y-%m-%d %H:%M:%S',
+    on_error => 'croak',
+);
+
+$latestedDate = $parser->parse_datetime($latestedDate);
+
+my $newDay = $latestedDate->clone();
+$latestedDate->add(days => $daysInFuture);
+
+for ($newDay->add(days => 1); $newDay <= $latestedDate; $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 is_opened, holiday_type, note FROM discrete_calendar WHERE date=? AND branchcode=?';
+    my $day_last_week = "SELECT open_hour, close_hour FROM discrete_calendar WHERE DAYOFWEEK(date)=DAYOFWEEK(?) and date < ? order by date desc limit 1";
+    my $add_Day = 'INSERT INTO discrete_calendar (date,branchcode,is_opened,open_hour,close_hour) VALUES (?,?,?,?,?)';
+    my $note ='';
+    #insert into discrete_calendar for each branch
+    foreach my $branchCode(@branches){
+        $statement = $dbh->prepare($last_year);
+        $statement->execute($yearAgo,$branchCode);
+        my ($is_opened, $holiday_type, $note) = $statement->fetchrow_array;
+        #weekly and unique holidays are not replicated in the future
+        if ($holiday_type && $holiday_type ne "R"){
+            $is_opened = 1;
+            if ($holiday_type eq "W" || $holiday_type eq "E"){
+                $holiday_type='';
+                $note='';
+            }elsif ($holiday_type eq "F"){
+                $holiday_type = 'N';
+            }
+        }
+        $holiday_type = '' if $is_opened;
+        $statement = $dbh->prepare($day_last_week);
+        $statement->execute($newDay, $newDay);
+        my ($open_hour,$close_hour ) = $statement->fetchrow_array;
+        my $add_Day = 'INSERT INTO discrete_calendar (date,branchcode,is_opened,holiday_type, note,open_hour,close_hour) VALUES (?,?,?,?,?,?,?)';
+        $statement = $dbh->prepare($add_Day);
+        $statement->execute($newDay,$branchCode,$is_opened,$holiday_type,$note, $open_hour,$close_hour);
+
+        if($debug && !$@){
+            warn "Added day $newDay to $branchCode is opened : $is_opened, holiday_type : $holiday_type, note: $note, open_hour : $open_hour, close_hour : $close_hour \n";
+        }elsif($@){
+            warn "Failed to add day $newDay to $branchCode : $_\n";
+        }
+    }
+}
+# If everything went well we commit to the database
+$schema->storage->txn_commit;
\ No newline at end of file
diff --git a/misc/cronjobs/fines.pl b/misc/cronjobs/fines.pl
index 717c0d5fa2..6eceec171b 100755
--- a/misc/cronjobs/fines.pl
+++ b/misc/cronjobs/fines.pl
@@ -38,7 +38,7 @@ use Carp;
 use File::Spec;
 use Try::Tiny;
 
-use Koha::Calendar;
+use Koha::DiscreteCalendar;
 use Koha::DateUtils;
 use Koha::Patrons;
 use C4::Log;
diff --git a/misc/cronjobs/holds/cancel_unfilled_holds.pl b/misc/cronjobs/holds/cancel_unfilled_holds.pl
index ba6a40098a..a957a9a661 100755
--- a/misc/cronjobs/holds/cancel_unfilled_holds.pl
+++ b/misc/cronjobs/holds/cancel_unfilled_holds.pl
@@ -32,7 +32,7 @@ use Koha::Script -cron;
 use C4::Reserves;
 use C4::Log;
 use Koha::Holds;
-use Koha::Calendar;
+use Koha::DiscreteCalendar;
 use Koha::DateUtils;
 use Koha::Libraries;
 
diff --git a/misc/cronjobs/overdue_notices.pl b/misc/cronjobs/overdue_notices.pl
index 7b50f254c6..a0472c9d2b 100755
--- a/misc/cronjobs/overdue_notices.pl
+++ b/misc/cronjobs/overdue_notices.pl
@@ -41,7 +41,7 @@ use C4::Overdues qw(GetFine GetOverdueMessageTransportTypes parse_overdues_lette
 use C4::Log;
 use Koha::Patron::Debarments qw(AddUniqueDebarment);
 use Koha::DateUtils;
-use Koha::Calendar;
+use Koha::DiscreteCalendar;
 use Koha::Libraries;
 use Koha::Acquisition::Currencies;
 use Koha::Patrons;
@@ -460,9 +460,8 @@ elsif ( defined $text_filename ) {
 }
 
 foreach my $branchcode (@branches) {
-    my $calendar;
     if ( C4::Context->preference('OverdueNoticeCalendar') ) {
-        $calendar = Koha::Calendar->new( branchcode => $branchcode );
+        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
         if ( $calendar->is_holiday($date_to_run) ) {
             next;
         }
@@ -575,13 +574,11 @@ END_SQL
                 my $days_between;
                 if ( C4::Context->preference('OverdueNoticeCalendar') )
                 {
-                    $days_between =
-                      $calendar->days_between( dt_from_string($data->{date_due}),
-                        $date_to_run );
+                    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
+                    $days_between = $calendar->days_between( dt_from_string($data->{date_due}), $date_to_run );
                 }
                 else {
-                    $days_between =
-                      $date_to_run->delta_days( dt_from_string($data->{date_due}) );
+                    $days_between = $date_to_run->delta_days( dt_from_string($data->{date_due}) );
                 }
                 $days_between = $days_between->in_units('days');
                 if ($triggered) {
@@ -658,9 +655,8 @@ END_SQL
                 my $exceededPrintNoticesMaxLines = 0;
                 while ( my $item_info = $sth2->fetchrow_hashref() ) {
                     if ( C4::Context->preference('OverdueNoticeCalendar') ) {
-                        $days_between =
-                          $calendar->days_between(
-                            dt_from_string( $item_info->{date_due} ), $date_to_run );
+                        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
+                        $days_between = $calendar->days_between( dt_from_string( $item_info->{date_due} ), $date_to_run );
                     }
                     else {
                         $days_between =
diff --git a/misc/cronjobs/staticfines.pl b/misc/cronjobs/staticfines.pl
index 1e7d287702..cb0f031200 100755
--- a/misc/cronjobs/staticfines.pl
+++ b/misc/cronjobs/staticfines.pl
@@ -41,7 +41,7 @@ use Koha::Script -cron;
 use C4::Context;
 use C4::Circulation;
 use C4::Overdues;
-use C4::Calendar qw();    # don't need any exports from Calendar
+use Koha::DiscreteCalendar qw();    # don't need any exports from Calendar
 use C4::Biblio;
 use C4::Debug;            # supplying $debug and $cgi_debug
 use C4::Log;
@@ -177,7 +177,7 @@ for ( my $i = 0 ; $i < scalar(@$data) ; $i++ ) {
 
     my $calendar;
     unless ( defined( $calendars{$branchcode} ) ) {
-        $calendars{$branchcode} = C4::Calendar->new( branchcode => $branchcode );
+        $calendars{$branchcode} = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
     }
     $calendar = $calendars{$branchcode};
     my $isHoliday = $calendar->isHoliday( $tday, $tmonth, $tyear );
diff --git a/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl b/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl
index ae6a4f4e00..37aa429bb4 100755
--- a/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl
+++ b/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl
@@ -36,7 +36,7 @@ use C4::Context;
 use C4::Items;
 use C4::Letters;
 use C4::Overdues;
-use Koha::Calendar;
+use Koha::DiscreteCalendar;
 use Koha::DateUtils;
 use Koha::Patrons;
 use Koha::Libraries;
@@ -341,7 +341,7 @@ sub GetWaitingHolds {
             }
         );
 
-        my $calendar = Koha::Calendar->new( branchcode => $issue->{'site'}, days_mode => $daysmode );
+        my $calendar = Koha::DiscreteCalendar->new( branchcode => $issue->{'site'}, days_mode => $daysmode );
 
         my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' );
         my $pickup_date = $waiting_date->clone->add( days => $pickupdelay );
diff --git a/tools/copy-holidays.pl b/tools/copy-holidays.pl
deleted file mode 100755
index e11353a09d..0000000000
--- a/tools/copy-holidays.pl
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/perl
-
-# Copyright 2012 Catalyst IT
-#
-# 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>.
-
-use Modern::Perl;
-
-use CGI qw ( -utf8 );
-
-use C4::Auth;
-use C4::Output;
-
-
-use C4::Calendar;
-
-my $input               = CGI->new;
-my $dbh                 = C4::Context->dbh();
-
-checkauth($input, 0, {tools=> 'edit_calendar'}, 'intranet');
-
-my $branchcode          = $input->param('branchcode');
-my $from_branchcode     = $input->param('from_branchcode');
-
-C4::Calendar->new(branchcode => $from_branchcode)->copy_to_branch($branchcode) if $from_branchcode && $branchcode;
-
-print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=".($branchcode || $from_branchcode));
diff --git a/tools/discrete_calendar.pl b/tools/discrete_calendar.pl
new file mode 100755
index 0000000000..fefaa6e06d
--- /dev/null
+++ b/tools/discrete_calendar.pl
@@ -0,0 +1,155 @@
+#!/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 Modern::Perl;
+
+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 $branch = $input->param('branch') || C4::Context->userenv->{'branch'};
+my $calendar = Koha::DiscreteCalendar->new({branchcode => $branch});
+#alert the user that they are using the default calendar because they do not have a library set
+my $no_branch_selected = $calendar->{no_branch_selected};
+
+my $weekday = $input->param('day_of_week');
+
+my $holiday_type = $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'){
+    $calendar->copy_to_branch(scalar $input->param('newBranch'));
+} elsif($action eq '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->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber);
+} elsif($action eq 'edit'){
+    my $openHour = $input->param('openHour');
+    my $closeHour = $input->param('closeHour');
+    my $endDate = $input->param('toDate');
+    my $deleteType = $input->param('deleteType') || 0;
+    #Get today from javascript for a precise local time
+    my $local_today = dt_from_string( $input->param('local_today'), 'iso');
+
+    my $startDate = dt_from_string(scalar $input->param('from_copyFrom'));
+
+    if($endDate ne '' ) {
+        $endDate = dt_from_string(scalar $endDate);
+    } else{
+        $endDate = $startDate->clone();
+    }
+
+    warn $startDate;
+    warn $endDate;
+    $calendar->edit_holiday( {
+        title        => $title,
+        weekday      => $weekday,
+        holiday_type => $holiday_type,
+        open_hour    => $openHour,
+        close_hour   => $closeHour,
+        start_date   => $startDate,
+        end_date     => $endDate,
+        delete_type  => $deleteType,
+        today        => $local_today
+    });
+}
+
+# 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.
+if ( C4::Context->only_my_library ) {
+    $branch = C4::Context->userenv->{'branch'};
+}
+
+# Get all the holidays
+
+#discrete_calendar weekly holidays
+my @week_days = $calendar->get_week_days_holidays();
+
+#discrete_calendar repeatable holidays
+my @repeatable_holidays = $calendar->get_repeatable_holidays();
+
+#discrete_calendar unique holidays
+my @unique_holidays =$calendar->get_unique_holidays();
+#discrete_calendar floating holidays
+my @float_holidays =$calendar->get_float_holidays();
+#discrete_caledar need validation holidays
+my @need_validation_holidays =$calendar->get_need_validation_holidays();
+
+#Calendar maximum date
+my $minDate = $calendar->get_min_date();
+
+#Calendar minimum date
+my $maxDate = $calendar->get_max_date();
+
+my @datesInfos = $calendar->get_dates_info();
+
+$template->param(
+    UNIQUE_HOLIDAYS          => \@unique_holidays,
+    FLOAT_HOLIDAYS           => \@float_holidays,
+    NEED_VALIDATION_HOLIDAYS => \@need_validation_holidays,
+    REPEATABLE_HOLIDAYS      => \@repeatable_holidays,
+    WEEKLY_HOLIDAYS          => \@week_days,
+    calendardate             => $calendardate,
+    keydate                  => $keydate,
+    branch                   => $branch,
+    minDate                  => $minDate,
+    maxDate                  => $maxDate,
+    datesInfos               => \@datesInfos,
+    no_branch_selected       => $no_branch_selected,
+);
+
+# Shows the template with the real values replaced
+output_html_with_http_headers $input, $cookie, $template->output;
diff --git a/tools/exceptionHolidays.pl b/tools/exceptionHolidays.pl
deleted file mode 100755
index 6894074459..0000000000
--- a/tools/exceptionHolidays.pl
+++ /dev/null
@@ -1,143 +0,0 @@
-#!/usr/bin/perl
-
-use Modern::Perl;
-
-use CGI qw ( -utf8 );
-
-use C4::Auth;
-use C4::Output;
-use DateTime;
-
-use C4::Calendar;
-use Koha::DateUtils;
-
-my $input = CGI->new;
-my $dbh = C4::Context->dbh();
-
-checkauth($input, 0, {tools=> 'edit_calendar'}, 'intranet');
-
-
-our $branchcode = $input->param('showBranchName');
-my $originalbranchcode  = $branchcode;
-our $weekday = $input->param('showWeekday');
-our $day = $input->param('showDay');
-our $month = $input->param('showMonth');
-our $year = $input->param('showYear');
-our $title = $input->param('showTitle');
-our $description = $input->param('showDescription');
-our $holidaytype = $input->param('showHolidayType');
-my $datecancelrange_dt = eval { dt_from_string( scalar $input->param('datecancelrange') ) };
-my $calendardate = sprintf("%04d-%02d-%02d", $year, $month, $day);
-our $showoperation = $input->param('showOperation');
-my $allbranches = $input->param('allBranches');
-
-$title || ($title = '');
-if ($description) {
-    $description =~ s/\r/\\r/g;
-    $description =~ s/\n/\\n/g;
-} else {
-    $description = '';
-}   
-
-# We make an array with holiday's days
-our @holiday_list;
-if ($datecancelrange_dt){
-            my $first_dt = DateTime->new(year => $year, month  => $month,  day => $day);
-
-            for (my $dt = $first_dt->clone();
-                $dt <= $datecancelrange_dt;
-                $dt->add(days => 1) )
-                {
-                push @holiday_list, $dt->clone();
-                }
-}
-
-if($allbranches) {
-    my $libraries = Koha::Libraries->search;
-    while ( my $library = $libraries->next ) {
-        edit_holiday($showoperation, $library->branchcode, $weekday, $day, $month, $year, $title, $description, $holidaytype, @holiday_list);
-    }
-} else {
-    edit_holiday($showoperation, $branchcode, $weekday, $day, $month, $year, $title, $description, $holidaytype, @holiday_list);
-}
-
-print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=$originalbranchcode&calendardate=$calendardate");
-
-sub edit_holiday {
-    ($showoperation, $branchcode, $weekday, $day, $month, $year, $title, $description, $holidaytype, @holiday_list) = @_;
-    my $calendar = C4::Calendar->new(branchcode => $branchcode);
-
-    if ($showoperation eq 'exception') {
-        $calendar->insert_exception_holiday(day => $day,
-                                            month => $month,
-                                            year => $year,
-                                            title => $title,
-                                            description => $description);
-    } elsif ($showoperation eq 'exceptionrange' ) {
-            if (@holiday_list){
-                foreach my $date (@holiday_list){
-                    $calendar->insert_exception_holiday(
-                        day         => $date->{local_c}->{day},
-                        month       => $date->{local_c}->{month},
-                        year       => $date->{local_c}->{year},
-                        title       => $title,
-                        description => $description
-                        );
-                }
-            }
-    } elsif ($showoperation eq 'edit') {
-        if($holidaytype eq 'weekday') {
-        $calendar->ModWeekdayholiday(weekday => $weekday,
-                                    title => $title,
-                                    description => $description);
-        } elsif ($holidaytype eq 'daymonth') {
-        $calendar->ModDaymonthholiday(day => $day,
-                                        month => $month,
-                                        title => $title,
-                                        description => $description);
-        } elsif ($holidaytype eq 'ymd') {
-        $calendar->ModSingleholiday(day => $day,
-                                    month => $month,
-                                    year => $year,
-                                    title => $title,
-                                    description => $description);
-        } elsif ($holidaytype eq 'exception') {
-        $calendar->ModExceptionholiday(day => $day,
-                                    month => $month,
-                                    year => $year,
-                                    title => $title,
-                                    description => $description);
-        }
-    } elsif ($showoperation eq 'delete') {
-        $calendar->delete_holiday(weekday => $weekday,
-                                day => $day,
-                                month => $month,
-                                year => $year);
-    }elsif ($showoperation eq 'deleterange') {
-        if (@holiday_list){
-            foreach my $date (@holiday_list){
-                $calendar->delete_holiday_range(weekday => $weekday,
-                                                day => $date->{local_c}->{day},
-                                                month => $date->{local_c}->{month},
-                                                year => $date->{local_c}->{year});
-                }
-        }
-    }elsif ($showoperation eq 'deleterangerepeat') {
-        if (@holiday_list){
-            foreach my $date (@holiday_list){
-            $calendar->delete_holiday_range_repeatable(weekday => $weekday,
-                                            day => $date->{local_c}->{day},
-                                            month => $date->{local_c}->{month});
-            }
-        }
-    }elsif ($showoperation eq 'deleterangerepeatexcept') {
-        if (@holiday_list){
-            foreach my $date (@holiday_list){
-            $calendar->delete_exception_holiday_range(weekday => $weekday,
-                                            day => $date->{local_c}->{day},
-                                            month => $date->{local_c}->{month},
-                                            year => $date->{local_c}->{year});
-            }
-        }
-    }
-}
diff --git a/tools/holidays.pl b/tools/holidays.pl
deleted file mode 100755
index 252882dbef..0000000000
--- a/tools/holidays.pl
+++ /dev/null
@@ -1,131 +0,0 @@
-#!/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 Modern::Perl;
-
-use CGI qw ( -utf8 );
-
-use C4::Auth;
-use C4::Output;
-
-use C4::Calendar;
-use Koha::DateUtils;
-
-my $input = CGI->new;
-
-my $dbh = C4::Context->dbh();
-# Get the template to use
-my ($template, $loggedinuser, $cookie)
-    = get_template_and_user({template_name => "tools/holidays.tt",
-                             type => "intranet",
-                             query => $input,
-                             flagsrequired => {tools => 'edit_calendar'},
-                             debug => 1,
-                           });
-
-# 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 } );
-
-# 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;
-
-my $branch= $input->param('branch') || C4::Context->userenv->{'branch'};
-
-# Get all the holidays
-
-my $calendar = C4::Calendar->new(branchcode => $branch);
-my $week_days_holidays = $calendar->get_week_days_holidays();
-my @week_days;
-foreach my $weekday (keys %$week_days_holidays) {
-# warn "WEEK DAY : $weekday";
-    my %week_day;
-    %week_day = (KEY => $weekday,
-                 TITLE => $week_days_holidays->{$weekday}{title},
-                 DESCRIPTION => $week_days_holidays->{$weekday}{description});
-    push @week_days, \%week_day;
-}
-
-my $day_month_holidays = $calendar->get_day_month_holidays();
-my @day_month_holidays;
-foreach my $monthDay (keys %$day_month_holidays) {
-    # Determine date format on month and day.
-    my $day_monthdate;
-    my $day_monthdate_sort;
-    if (C4::Context->preference("dateformat") eq "metric") {
-      $day_monthdate_sort = "$day_month_holidays->{$monthDay}{month}-$day_month_holidays->{$monthDay}{day}";
-      $day_monthdate = "$day_month_holidays->{$monthDay}{day}/$day_month_holidays->{$monthDay}{month}";
-    } elsif (C4::Context->preference("dateformat") eq "dmydot") {
-      $day_monthdate_sort = "$day_month_holidays->{$monthDay}{month}.$day_month_holidays->{$monthDay}{day}";
-      $day_monthdate = "$day_month_holidays->{$monthDay}{day}.$day_month_holidays->{$monthDay}{month}";
-    }elsif (C4::Context->preference("dateformat") eq "us") {
-      $day_monthdate = "$day_month_holidays->{$monthDay}{month}/$day_month_holidays->{$monthDay}{day}";
-      $day_monthdate_sort = $day_monthdate;
-    } else {
-      $day_monthdate = "$day_month_holidays->{$monthDay}{month}-$day_month_holidays->{$monthDay}{day}";
-      $day_monthdate_sort = $day_monthdate;
-    }
-    my %day_month;
-    %day_month = (KEY => $monthDay,
-                  DATE_SORT => $day_monthdate_sort,
-                  DATE => $day_monthdate,
-                  TITLE => $day_month_holidays->{$monthDay}{title},
-                  DESCRIPTION => $day_month_holidays->{$monthDay}{description});
-    push @day_month_holidays, \%day_month;
-}
-
-my $exception_holidays = $calendar->get_exception_holidays();
-my @exception_holidays;
-foreach my $yearMonthDay (keys %$exception_holidays) {
-    my $exceptiondate = eval { dt_from_string( $exception_holidays->{$yearMonthDay}{date} ) };
-    my %exception_holiday;
-    %exception_holiday = (KEY => $yearMonthDay,
-                          DATE_SORT => $exception_holidays->{$yearMonthDay}{date},
-                          DATE => output_pref( { dt => $exceptiondate, dateonly => 1 } ),
-                          TITLE => $exception_holidays->{$yearMonthDay}{title},
-                          DESCRIPTION => $exception_holidays->{$yearMonthDay}{description});
-    push @exception_holidays, \%exception_holiday;
-}
-
-my $single_holidays = $calendar->get_single_holidays();
-my @holidays;
-foreach my $yearMonthDay (keys %$single_holidays) {
-    my $holidaydate_dt = eval { dt_from_string( $single_holidays->{$yearMonthDay}{date} ) };
-    my %holiday;
-    %holiday = (KEY => $yearMonthDay,
-                DATE_SORT => $single_holidays->{$yearMonthDay}{date},
-                DATE => output_pref( { dt => $holidaydate_dt, dateonly => 1 } ),
-                TITLE => $single_holidays->{$yearMonthDay}{title},
-                DESCRIPTION => $single_holidays->{$yearMonthDay}{description});
-    push @holidays, \%holiday;
-}
-
-$template->param(
-    WEEK_DAYS_LOOP           => \@week_days,
-    HOLIDAYS_LOOP            => \@holidays,
-    EXCEPTION_HOLIDAYS_LOOP  => \@exception_holidays,
-    DAY_MONTH_HOLIDAYS_LOOP  => \@day_month_holidays,
-    calendardate             => $calendardate,
-    keydate                  => $keydate,
-    branch                   => $branch,
-);
-
-# Shows the template with the real values replaced
-output_html_with_http_headers $input, $cookie, $template->output;
diff --git a/tools/newHolidays.pl b/tools/newHolidays.pl
deleted file mode 100755
index c402dfdf7f..0000000000
--- a/tools/newHolidays.pl
+++ /dev/null
@@ -1,144 +0,0 @@
-#!/usr/bin/perl
-#FIXME: perltidy this file
-
-# 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 Lic# along with Koha; if not, see <http://www.gnu.org/licenses>.
-# along with Koha; if not, see <http://www.gnu.org/licenses>.
-
-
-use Modern::Perl;
-
-use CGI qw ( -utf8 );
-
-use C4::Auth;
-use C4::Output;
-
-use C4::Calendar;
-use DateTime;
-use Koha::DateUtils;
-
-my $input               = CGI->new;
-my $dbh                 = C4::Context->dbh();
-
-checkauth($input, 0, {tools=> 'edit_calendar'}, 'intranet');
-
-our $branchcode          = $input->param('newBranchName');
-my $originalbranchcode  = $branchcode;
-our $weekday             = $input->param('newWeekday');
-our $day                 = $input->param('newDay');
-our $month               = $input->param('newMonth');
-our $year                = $input->param('newYear');
-my $dateofrange         = $input->param('dateofrange');
-our $title               = $input->param('newTitle');
-our $description         = $input->param('newDescription');
-our $newoperation        = $input->param('newOperation');
-my $allbranches         = $input->param('allBranches');
-
-
-my $first_dt = DateTime->new(year => $year, month  => $month,  day => $day);
-my $end_dt   = eval { dt_from_string( $dateofrange ); };
-
-my $calendardate = output_pref( { dt => $first_dt, dateonly => 1, dateformat => 'iso' } );
-
-$title || ($title = '');
-if ($description) {
-	$description =~ s/\r/\\r/g;
-	$description =~ s/\n/\\n/g;
-} else {
-	$description = '';
-}
-
-# We make an array with holiday's days
-our @holiday_list;
-if ($end_dt){
-    for (my $dt = $first_dt->clone();
-    $dt <= $end_dt;
-    $dt->add(days => 1) )
-    {
-        push @holiday_list, $dt->clone();
-    }
-}
-
-if($allbranches) {
-    my $libraries = Koha::Libraries->search;
-    while ( my $library = $libraries->next ) {
-        add_holiday($newoperation, $library->branchcode, $weekday, $day, $month, $year, $title, $description);
-    }
-} else {
-    add_holiday($newoperation, $branchcode, $weekday, $day, $month, $year, $title, $description);
-}
-
-print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=$originalbranchcode&calendardate=$calendardate");
-
-#FIXME: move add_holiday() to a better place
-sub add_holiday {
-	($newoperation, $branchcode, $weekday, $day, $month, $year, $title, $description) = @_;  
-	my $calendar = C4::Calendar->new(branchcode => $branchcode);
-
-	if ($newoperation eq 'weekday') {
-		unless ( $weekday && ($weekday ne '') ) { 
-			# was dow calculated by javascript?  original code implies it was supposed to be.
-			# if not, we need it.
-			$weekday = &Date::Calc::Day_of_Week($year, $month, $day) % 7 unless($weekday);
-		}
-		unless($calendar->isHoliday($day, $month, $year)) {
-			$calendar->insert_week_day_holiday(weekday => $weekday,
-							           title => $title,
-							           description => $description);
-		}
-	} elsif ($newoperation eq 'repeatable') {
-		unless($calendar->isHoliday($day, $month, $year)) {
-			$calendar->insert_day_month_holiday(day => $day,
-	                                    month => $month,
-							            title => $title,
-							            description => $description);
-		}
-	} elsif ($newoperation eq 'holiday') {
-		unless($calendar->isHoliday($day, $month, $year)) {
-			$calendar->insert_single_holiday(day => $day,
-	                                 month => $month,
-						             year => $year,
-						             title => $title,
-						             description => $description);
-		}
-
-	} elsif ( $newoperation eq 'holidayrange' ) {
-        if (@holiday_list){
-            foreach my $date (@holiday_list){
-                unless ( $calendar->isHoliday( $date->{local_c}->{day}, $date->{local_c}->{month}, $date->{local_c}->{year} ) ) {
-                    $calendar->insert_single_holiday(
-                        day         => $date->{local_c}->{day},
-                        month       => $date->{local_c}->{month},
-                        year        => $date->{local_c}->{year},
-                        title       => $title,
-                        description => $description
-                    );
-                }
-            }
-        }
-    } elsif ( $newoperation eq 'holidayrangerepeat' ) {
-        if (@holiday_list){
-            foreach my $date (@holiday_list){
-                unless ( $calendar->isHoliday( $date->{local_c}->{day}, $date->{local_c}->{month}, $date->{local_c}->{year} ) ) {
-                    $calendar->insert_day_month_holiday(
-                        day         => $date->{local_c}->{day},
-                        month       => $date->{local_c}->{month},
-                        title       => $title,
-                        description => $description
-                    );
-                }
-            }
-        }
-    }
-}
-- 
2.17.1