[Bps-public-commit] rtx-calendar branch multiple-days-events created. 1.05-43-g10ada4e

BPS Git Server git at git.bestpractical.com
Mon Oct 16 12:46:00 UTC 2023


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rtx-calendar".

The branch, multiple-days-events has been created
        at  10ada4eac29a1d1611b06a04e9785f55a9f4d2bd (commit)

- Log -----------------------------------------------------------------
commit 10ada4eac29a1d1611b06a04e9785f55a9f4d2bd
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Mon Oct 16 09:40:50 2023 -0300

    Change date loading from unknown to ISO for better performance
    
    As described on https://docs.bestpractical.com/rt/5.0.4/RT/Date.html#Set
    loading a date from an unknown format is a "heavyweight" operation as
    for now.
    
    This patch updates the code to load dates from ISO format.

diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index e5f3bfb..a9b665e 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -133,14 +133,14 @@ sub FindTickets {
             # to it
             my $current_date = RT::Date->new($CurrentUser);
             $current_date->Set(
-                Format => 'unknown',
-                Value => $starts_date,
+                Format => 'ISO',
+                Value => $starts_date . " 00:00:00",
                 Timezone => 'user'
             );
             my $end_date_unix = RT::Date->new($CurrentUser);
             $end_date_unix->Set(
-                Format => 'unknown',
-                Value => $ends_date,
+                Format => 'ISO',
+                Value => $ends_date . " 00:00:00",
                 Timezone => 'user'
             );
             $end_date_unix = $end_date_unix->Unix;
@@ -197,8 +197,8 @@ sub GetDate {
         my $DateObj            = RT::Date->new($CurrentUser);
         if ( $CustomFieldObjType eq 'Date' ) {
             $DateObj->Set(
-                Format   => 'unknown',
-                Value    => $CFDateValue,
+                Format   => 'ISO',
+                Value    => $CFDateValue . " 00:00:00",
             );
         } else {
             $DateObj->Set( Format => 'ISO', Value => $CFDateValue );
@@ -300,12 +300,13 @@ CALENDAR_ICON:
                 $cf =~ s/^CF\.\{(.*)\}/$1/;
                 my $DateValue = $Object->FirstCustomFieldValue($cf);
                 next CALENDAR_ICON unless $DateValue;
-                my $DateObj = RT::Date->new( $CurrentUser );
                 my $CustomFieldObj = RT::CustomField->new( $CurrentUser );
                 $CustomFieldObj->LoadByName( Name => $cf );
-                my $date_format = $CustomFieldObj->Type eq 'Date' ? 'unknown' : 'ISO';
-                $DateObj->Set( Format => $date_format, Value => $DateValue );
-                $DateValue = $DateObj->ISO( Time => 0, Timezone => 'user' );
+                unless ( $CustomFieldObj->Type eq 'Date' ) {
+                    my $DateObj = RT::Date->new( $CurrentUser );
+                    $DateObj->Set( Format => 'ISO', Value => $DateValue );
+                    $DateValue = $DateObj->ISO( Time => 0, Timezone => 'user' );
+                }
                 next CALENDAR_ICON unless $DateValue eq $CurrentDate;
             } else {
                 my $DateObj = $ComparedDate . "Obj";

commit 0b8436bfbd5d0f99f95b867e832a818416c193c7
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Oct 12 17:51:09 2023 -0300

    Change icon to be rendered only when required
    
    Icon was being rendered even when there was no need for it. This commit
    changes the code to render the icon only when it is the first or the
    last day of the event.
    
    Update the identation of the code to match the rest of the file due to
    the change of some if statements position.

diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index df500b9..515e1e0 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -9,9 +9,6 @@ $CurrentPostion => undef
 </%args>
 
 <%perl>
-my $icon
-= RTx::Calendar::GetEventImg( $Object, $today, $DateTypes, $IsReminder,
-   $session{'CurrentUser'} );
 my $spanning_tickets_for_today    = $TicketsSpanningDays->{$today}    || [];
 my $spanning_tickets_for_tomorrow = $TicketsSpanningDays->{$tomorrow} || [];
 my $first_day_of_the_event = 0;
@@ -58,7 +55,10 @@ if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
             float: right;
 % }
         ">
+% if ( $first_day_of_the_event || $last_day_of_the_event ) {
+% my $icon = RTx::Calendar::GetEventImg( $Object, $today, $DateTypes, $IsReminder, $session{'CurrentUser'} );
             <% $icon|n %>
+% }
         </div>
         <div class="event-info">
 % if ( $first_day_of_the_event || $DayOfWeek eq 1 ) {
@@ -67,14 +67,11 @@ if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
            <% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
            <% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %>
         </a>
-% }
-
-
-<span class="tip">
-	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
-           <% $Object->QueueObj->Name %> #<% $TicketId %>
-        </a>
-	:</strong> <% $subject%><br />
+        <span class="tip">
+            <a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
+               <% $Object->QueueObj->Name %> #<% $TicketId %>
+            </a>
+	        :</strong> <% $subject%><br />
 	<br />
 
 <%perl>
@@ -110,6 +107,7 @@ if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
 
     <br />
 </span>
+% }
     </div>
 </small>
 

commit 11c8a53bdbef7483f06e7587b1de6696464f6d22
Author: Jason Crome <jcrome at bestpractical.com>
Date:   Wed Oct 4 16:47:40 2023 -0400

    RTx-Calendar 1.10

diff --git a/CHANGES b/CHANGES
index ef176b3..fb3422a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,7 @@
+1.10 2023-10-04
+ - Sort color legend and filter by status values the same way, using
+   a case-insensitive sort.
+
 1.09 2023-09-29
  - Add dark mode support
  - Fix bug that reset filter settings when changing months
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index c5dc443..e5f3bfb 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -4,7 +4,7 @@ use strict;
 use DateTime;
 use DateTime::Set;
 
-our $VERSION = "1.09";
+our $VERSION = "1.10";
 
 RT->AddStyleSheets('calendar.css');
 RT->AddJavaScript('calendar.js');

commit 1df07ea16042ba5861d4aca63467a4d4b29a6d5e
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Wed Oct 4 16:18:28 2023 -0300

    Filter status values same as color legend
    
    The sort order of filter on status values was different from the sort
    order of the color legend, causing confusion amongst users. Both use the
    same sorting mechanism now, by converting status values to lower-case
    before sorting.

diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
index db84e96..29e68dc 100644
--- a/html/Elements/CalendarSidebar
+++ b/html/Elements/CalendarSidebar
@@ -14,7 +14,7 @@
      method="post">
       <select name="NewFilterOnStatus" id="NewFilterOnStatus"
         class="selectpicker form-control filteronstatus mt-3 mb-3" multiple="multiple" size="6">
-% for my $Status (sort {loc($a) cmp loc($b)} @{RT->Config->Get('CalendarFilterStatuses')}) {
+% for my $Status (sort {lc(loc($a)) cmp lc(loc($b))} @{RT->Config->Get('CalendarFilterStatuses')}) {
         <option value="<% $Status %>"
 % if (@FilterOnStatus && $FilterOnStatus[0]) {
       <% (grep { $Status eq $_ } @FilterOnStatus) ? 'selected="selected"':''%>

commit 22c5c0bbf06a55141f82b13d6079bab6e36f21ae
Author: Jason Crome <jcrome at bestpractical.com>
Date:   Fri Sep 29 16:26:07 2023 -0400

    RTx-Calendar 1.09

diff --git a/CHANGES b/CHANGES
index bcb6591..ef176b3 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,7 @@
+1.09 2023-09-29
+ - Add dark mode support
+ - Fix bug that reset filter settings when changing months
+
 1.08 2023-09-27
  - Add multi-day event support
 
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index b6b1d42..c5dc443 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -4,7 +4,7 @@ use strict;
 use DateTime;
 use DateTime::Set;
 
-our $VERSION = "1.08";
+our $VERSION = "1.09";
 
 RT->AddStyleSheets('calendar.css');
 RT->AddJavaScript('calendar.js');

commit a1f147d4982a2590479ee98e24b5387d3af0613d
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Sep 29 14:33:22 2023 -0300

    Fix losing Filter on Status when changin the month
    
    There was a bug where the generated querystring that is used to carry
    the status filter from month to month was not being generated. This
    patch fixes it.

diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index 8de3932..b6bf784 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -284,8 +284,7 @@ my $QueryString =
         OrderBy => $OrderBy,
         Rows    => $RowsPerPage,
         NotFirstAccess => $NotFirstAccess,
-      )
-      if ($Query);
+      );
 
 $QueryString ||= 'NewQuery=1&NotFirstAccess=1';
 

commit b5c9d29eef73606975dbcd732deedae75eacbe07
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Sep 29 12:14:00 2023 -0300

    Add Dark Mode to the Calendar

diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index b87cd78..8de3932 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -165,13 +165,13 @@ while ($date <= $end) {
 <td valign="top" align="center">
 <form method="post" class="changeCalendarMonth" onsubmit="changeCalendarMonth()">
 
-<select name="Month" class="selectpicker">
+<select name="Month" class="selectpicker form-control col-3">
 % for (0..11) {
 <option value="<%$_%>" <% $_ == $Month ? 'selected' : ''%> ><%$rtdate->GetMonth($_)%></option>
 % }
 </select>
 % my $year = (localtime)[5] + 1900;
-<select name="Year" class="selectpicker">
+<select name="Year" class="selectpicker form-control col-3">
 % for ( ($year-5) .. ($year+5)) {
 <option value="<%$_%>" <% $_ == $Year ? 'selected' : ''%>><%$_%></option>
 % }
diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index 5f018b3..df500b9 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -35,9 +35,12 @@ if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
 % if ( $first_day_of_the_event || $DayOfWeek eq 1 ) {
     first-day
 % }
+% my $status_class = 'event-status-'.$status;
+% $status_class =~ s/\s+/-/g;
+<% $status_class %>
 " style="
 % if ( $CalendarStatusColorMap{$status} ) {
-    background-color: <%$CalendarStatusColorMap{$status}%>;
+    background-color: <%$CalendarStatusColorMap{$status}%> !important;
 % }
 % # We need to decrease the z-index of the spanning days of an event
 % # so the event title (which is placed on the div of the first day of the
diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
index b1f96ce..db84e96 100644
--- a/html/Elements/CalendarSidebar
+++ b/html/Elements/CalendarSidebar
@@ -13,7 +13,7 @@
     <form id="FilterOnStatusForm"
      method="post">
       <select name="NewFilterOnStatus" id="NewFilterOnStatus"
-        class="selectpicker filteronstatus mt-3 mb-3" multiple="multiple" size="6">
+        class="selectpicker form-control filteronstatus mt-3 mb-3" multiple="multiple" size="6">
 % for my $Status (sort {loc($a) cmp loc($b)} @{RT->Config->Get('CalendarFilterStatuses')}) {
         <option value="<% $Status %>"
 % if (@FilterOnStatus && $FilterOnStatus[0]) {
@@ -25,7 +25,7 @@
       <div class="text-center">
         <input type="submit" value="<% loc('Filter') %>" class="mr-2 button btn btn-primary" />
         <button type="submit" id="FilterOnStatusClear" name="FilterOnStatusClear"
-          value="1" class="button btn btn-primary"><% loc('Clear Filter') %></button>
+          value="1" class="button btn btn-primary form-control"><% loc('Clear Filter') %></button>
       </div>
     </form>
     </&>
@@ -52,7 +52,7 @@
      &>
 % my %ColorStatusMap = RT->Config->Get('CalendarStatusColorMap');
 % foreach my $Status (sort { lc($a) cmp lc($b) } keys %ColorStatusMap) {
-    <span style="color: <% $ColorStatusMap{$Status} %>"><% $Status %><span><br />
+    <span style="color: <% $ColorStatusMap{$Status} %> !important;"><% $Status %></span><br />
 % }
 </&>
 
diff --git a/static/css/calendar.css b/static/css/calendar.css
index 915a0b0..83b740c 100644
--- a/static/css/calendar.css
+++ b/static/css/calendar.css
@@ -180,3 +180,17 @@ table.rtxcalendar .day.first-day.last-day {
 .calendar-container {
     display: flow-root;
 }
+
+.darkmode table.rtxcalendar * {
+    background: unset !important;
+}
+
+.darkmode table.rtxcalendar div.day div.event-info:hover span.tip{
+    border: 1px solid #555;
+    background-color: #2C3539 !important;
+    color: #fff !important;
+}
+
+.darkmode table.rtxcalendar div.day a {
+    color: #fff !important;
+}

commit ebbe1024d1635e3c2b894ac36ae050c88b583bfb
Author: Jason Crome <jcrome at bestpractical.com>
Date:   Wed Sep 27 13:17:49 2023 -0400

    RTx-Calendar 1.08

diff --git a/CHANGES b/CHANGES
index 7c4a6a2..bcb6591 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,6 @@
+1.08 2023-09-27
+ - Add multi-day event support
+
 1.07 2023-09-06
  - Clicking Reset Filter no longer resets month display
 
diff --git a/MANIFEST b/MANIFEST
index c9e48ba..0b407dd 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -1,8 +1,11 @@
 CHANGES
 etc/RTxCalendar_Config.pm
-etc/tabs_privileged_callback.patch
 html/Callbacks/RTx-Calendar/Elements/Tabs/Privileged
+html/Elements/Calendar
 html/Elements/CalendarEvent
+html/Elements/CalendarFooter
+html/Elements/CalendarSidebar
+html/Elements/CalendarWithSidebar
 html/Elements/MyCalendar
 html/Search/Calendar.html
 inc/Module/Install.pm
@@ -33,4 +36,5 @@ static/images/started.png
 static/images/starts.png
 static/images/starts_due.png
 static/images/updated.png
+static/js/calendar.js
 TODO
diff --git a/META.yml b/META.yml
index 9215247..fe5224e 100644
--- a/META.yml
+++ b/META.yml
@@ -26,6 +26,6 @@ requires:
   perl: 5.10.1
 resources:
   license: http://opensource.org/licenses/gpl-license.php
-version: '1.07'
+version: '1.08'
 x_module_install_rtx_version: '0.43'
 x_requires_rt: 5.0.0
diff --git a/README b/README
index dc900c5..83a757d 100644
--- a/README
+++ b/README
@@ -28,10 +28,6 @@ INSTALLATION
         May need root permissions
 
     patch RT
-        Apply for versions prior to 4.4.2:
-
-            patch -p1 -d /path/to/rt < etc/tabs_privileged_callback.patch
-
     Edit your /opt/rt5/etc/RT_SiteConfig.pm
         Add this line:
 
@@ -43,11 +39,19 @@ INSTALLATION
     Restart your webserver
 
 CONFIGURATION
-  Base configuration
-    To use the MyCalendar portlet, you must add MyCalendar to
+  Use the calendar on Dashboard
+    The calendar comes with 3 different portlets that can be added to your
+    RT dashboards:
+
+    MyCalendar, a summary of the events for the current week.
+    Calendar, a full month of the calendar view, without sidebar.
+    CalendarWithSidebar, a full month of the calendar view, with sidebar
+    which includes an extra status filter and legends of the calendars.
+
     $HomepageComponents in etc/RT_SiteConfig.pm:
 
-      Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar
+      Set($HomepageComponents, [qw(QuickCreate Quicksearch
+         MyCalendar Calendar CalendarWithSidebar
          MyAdminQueues MySupportQueues MyReminders RefreshHomepage)]);
 
   Display configuration
@@ -67,21 +71,6 @@ CONFIGURATION
              'DueObj->ISO',
              'CustomField.{Maintenance Estimated Start Date/Time - ET}'));
 
-   Event sorting
-    You can set the order that the events will presented in the day cell
-    with the $CalendarSortEvents setting.
-
-    This setting takes a subroutine reference that will receive an array of
-    RT::Ticket objects and should return a sorted array of RT::Ticket.
-
-    The following example sorts the events by status:
-
-        Set($CalendarSortEvents, sub {
-            my @Tickets = @_;
-            my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
-            return @SortedTickets;
-        });
-
    Event colors
     It's also possible to change the color of the events in the calendar by
     adding the $CalendarStatusColorMap setting to your etc/RT_SiteConfig.pm:
@@ -103,6 +92,12 @@ CONFIGURATION
 
         Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
 
+   Default selected status on Filtering on Status field
+    You can change the default selected statuses by adding them to the
+    @CalendarFilterDefaultStatuses setting to your etc/RT_SiteConfig.pm:
+
+        Set(@CalendarFilterDefaultStatuses, qw(new open));
+
    Custom icons
     Custom Icons can be defined for the events in the calendar by adding the
     $CalendarIcons setting to your etc/RT_SiteConfig.pm:
@@ -114,6 +109,21 @@ CONFIGURATION
 
     The images should be placed on local/static/images.
 
+   Multiple days events
+    You can define multiple days events by adding the
+    %CalendarMultipleDaysEvents setting to your etc/RT_SiteConfig.pm:
+
+        Set( %CalendarMultipleDaysEvents, (
+                'Maintenance' => {
+                    'Starts' => 'Starts',
+                    'Ends'   => 'Due',
+                },
+            )
+        );
+
+    Note that the Starts and Ends fields must be included in the search
+    result Format in order the event to be displayed on the calendar.
+
 USAGE
     A small help section is available in /Search/Calendar.html
 
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 0ac7da8..b6b1d42 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -4,7 +4,7 @@ use strict;
 use DateTime;
 use DateTime::Set;
 
-our $VERSION = "1.07";
+our $VERSION = "1.08";
 
 RT->AddStyleSheets('calendar.css');
 RT->AddJavaScript('calendar.js');

commit 8be04f9d7031b48c62ffca2ae9aaecd99accbae1
Author: Jason Crome <jcrome at bestpractical.com>
Date:   Wed Sep 27 12:58:54 2023 -0400

    Remove remaining RT4 support

diff --git a/etc/tabs_privileged_callback.patch b/etc/tabs_privileged_callback.patch
deleted file mode 100644
index 493c0f8..0000000
--- a/etc/tabs_privileged_callback.patch
+++ /dev/null
@@ -1,32 +0,0 @@
-diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
-index c6c6505..d4b2e59 100644
---- a/share/html/Elements/Tabs
-+++ b/share/html/Elements/Tabs
-@@ -757,6 +757,9 @@ my $build_main_nav = sub {
-         }
-     }
- 
-+    # Scope here so we can share in the Privileged callback
-+    my $args      = '';
-+    my $has_query = '';
-     if (
-         (
-                $request_path =~ m{^/(?:Ticket|Search)/}
-@@ -767,8 +770,6 @@ my $build_main_nav = sub {
-       )
-     {
-         my $search = Menu()->child('search')->child('tickets');
--        my $args      = '';
--        my $has_query = '';
-         my $current_search = $session{"CurrentSearchHash"} || {};
-         my $search_id = $DECODED_ARGS->{'SavedSearchLoad'} || $DECODED_ARGS->{'SavedSearchId'} || $current_search->{'SearchId'} || '';
-         my $chart_id = $DECODED_ARGS->{'SavedChartSearchId'} || $current_search->{SavedChartSearchId};
-@@ -936,7 +937,7 @@ my $build_main_nav = sub {
-         PageMenu()->child( edit => title => loc('Edit'), path => '/Prefs/MyRT.html' );
-     }
- 
--    $m->callback( CallbackName => 'Privileged', Path => $request_path );
-+    $m->callback( CallbackName => 'Privileged', Path => $request_path, Search_Args => $args, Has_Query => $has_query );
- };
- 
- my $build_selfservice_nav = sub {
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 79fc444..0ac7da8 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -369,10 +369,6 @@ May need root permissions
 
 =item patch RT
 
-Apply for versions prior to 4.4.2:
-
-    patch -p1 -d /path/to/rt < etc/tabs_privileged_callback.patch
-
 =item Edit your F</opt/rt5/etc/RT_SiteConfig.pm>
 
 Add this line:

commit b587db7493eb30295a60c1a1ab5c5ac49f66cb6f
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Sep 26 21:38:59 2023 -0300

    Fix filter on status bad behavior when changing months
    
    When users changed the month on the calendar with the filter on status
    enabled, and on the next month they changed the status filter, the
    filter pre selecting the statuses from the args on the URI, and not from
    the posted form.

diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index b2bafe8..b87cd78 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -205,7 +205,16 @@ my $NewQuery = $DECODED_ARGS->{NewQuery};
 my $BaseQuery = $DECODED_ARGS->{BaseQuery};
 my $FilterOnStatusClear = $DECODED_ARGS->{FilterOnStatusClear};
 my @FilterOnStatus;
-if ( $DECODED_ARGS->{FilterOnStatus} ) {
+if ( $DECODED_ARGS->{NewFilterOnStatus} ) {
+  if ( ref $DECODED_ARGS->{NewFilterOnStatus} eq 'ARRAY' ) {
+    @FilterOnStatus = @{$DECODED_ARGS->{NewFilterOnStatus}};
+  }
+  else {
+    push @FilterOnStatus, $DECODED_ARGS->{NewFilterOnStatus};
+  }
+}
+# This comes from the month changing form and link
+elsif ( $DECODED_ARGS->{FilterOnStatus} ) {
   if ( ref $DECODED_ARGS->{FilterOnStatus} eq 'ARRAY' ) {
     @FilterOnStatus = @{$DECODED_ARGS->{FilterOnStatus}};
   }
diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
index 34ceaf0..b1f96ce 100644
--- a/html/Elements/CalendarSidebar
+++ b/html/Elements/CalendarSidebar
@@ -12,7 +12,7 @@
 
     <form id="FilterOnStatusForm"
      method="post">
-      <select name="FilterOnStatus" id="FilterOnStatus"
+      <select name="NewFilterOnStatus" id="NewFilterOnStatus"
         class="selectpicker filteronstatus mt-3 mb-3" multiple="multiple" size="6">
 % for my $Status (sort {loc($a) cmp loc($b)} @{RT->Config->Get('CalendarFilterStatuses')}) {
         <option value="<% $Status %>"

commit 7f227f40213daaf375d5ad15437a67bafb99d0d0
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Sep 26 21:21:31 2023 -0300

    Fix filter on status to not replace the default query
    
    Filter on Status was replacing the default query of the Calendar
    element, causing the calendar to show wrong events when selecting a
    status that was not part of the default query.

diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index 0042415..b2bafe8 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -249,12 +249,6 @@ my $set = DateTime::Set->from_recurrence(
     next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
 );
 
-if (@FilterOnStatus) {
-  my $StatusClause = join " OR ", map { "Status = '$_'" } @FilterOnStatus;
-  $Query .= " AND " if $Query;
-  $Query .= "($StatusClause)";
-}
-
 # Default Query and Format
 my $TempFormat = "__Starts__ __Due__";
 my $TempQuery = "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
@@ -310,6 +304,12 @@ my %DateTypes = map { $_ => 1 } @Dates;
 
 $TempQuery .= RTx::Calendar::DatesClauses(\@Dates, $date->strftime("%F"), $end->strftime("%F"));
 
+if (@FilterOnStatus) {
+  my $StatusClause = join " OR ", map { "Status = '$_'" } @FilterOnStatus;
+  $TempQuery .= " AND " if $TempQuery;
+  $TempQuery .= "($StatusClause)";
+}
+
 $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$TempQuery, FormatRef => \$TempFormat );
 
 my ($Tickets, $TicketsSpanningDays) = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));

commit a2e43afe42190b9cdef7f54b8b6d6dc41c836719
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Sep 26 11:47:07 2023 -0400

    Handle Date custom fields correctly
    
    Unlike DateTime, Date values don't have time part and thus no timezone.
    E.g. for 2023-09-26, it should always be 2023-09-26, no matter what
    timezone current user is in.

diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index db29d65..5f018b3 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -92,8 +92,8 @@ if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
         if (grep { $_ eq $cf_obj->Type} qw(DateTime Date)) {
             my $date_value = RT::Date->new($session{'CurrentUser'});
             my $date_format = $cf_obj->Type eq 'DateTime' ? 'ISO' : 'unknown';
-            $date_value->Set(Format => $date_format, Value => $value, Timezone => 'UTC');
-            $value = $date_value->AsString( Timezone => 'user' );
+            $date_value->Set(Format => $date_format, Value => $value);
+            $value = $date_value->AsString( Timezone => 'user', Time => $cf_obj->Type eq 'DateTime' ? 1 : 0 );
         }
     } else {
         my $method = '$Object->'.$attr.'()';
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 6af5495..79fc444 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -199,7 +199,6 @@ sub GetDate {
             $DateObj->Set(
                 Format   => 'unknown',
                 Value    => $CFDateValue,
-                Timezone => 'utc'
             );
         } else {
             $DateObj->Set( Format => 'ISO', Value => $CFDateValue );

commit 733bdbfcead93b4ca4158cd5af35d834882e647f
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Sep 22 16:42:24 2023 -0300

    Fix event popup to appear correctly on the right side of the screen
    
    The event popup was disappearing off the right side of the screen when
    the event was near the right edge of the calendar.  This commit fixes
    that by making the popup appear on the left side of the event when the
    event is near the right edge of the calendar.

diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index f6568f3..0042415 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -85,6 +85,7 @@ while ($date <= $end) {
   push @classes, "today"     if (DateTime->compare($today,     $date) == 0);
   push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
   push @classes, "aweekago"  if (DateTime->compare($aweekago,  $date) == 0);
+  push @classes, "weekday-$day_of_week";
 
   for my $t ( RTx::Calendar::SortCalendarEvents(
                 \@{ $Tickets->{ $date->strftime("%F") } || [] },
@@ -117,7 +118,7 @@ while ($date <= $end) {
   }
 </%perl>
 
-    <td class="<% @classes %>"><div class="inside-day">
+    <td class="<% join(' ', @classes) %>"><div class="inside-day">
       <div class="calendardate"><%$date->day%></div>
 %     for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
 %       if ( grep { $_->id eq $week_ticket_position{$index}{id} }
diff --git a/html/Elements/MyCalendar b/html/Elements/MyCalendar
index 5bff533..da45a4e 100644
--- a/html/Elements/MyCalendar
+++ b/html/Elements/MyCalendar
@@ -23,6 +23,7 @@ while ($date <= $end) {
   my @classes = ();
   push @classes, "today"     if (DateTime->compare($today,     $date) == 0);
   push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
+  push @classes, "weekday-$day_of_week";
   for my $t ( RTx::Calendar::SortCalendarEvents(
                 \@{ $Tickets->{ $date->strftime("%F") } || [] },
                 $sorting_field,
@@ -54,7 +55,7 @@ while ($date <= $end) {
   }
 </%perl>
 
-    <td class="<% @classes %>"><div class="inside-day">
+    <td class="<% join(' ', @classes) %>"><div class="inside-day">
       <div class="calendardate"><%$date->day%></div>
 %      for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
 %       if ( grep { $_->id eq $week_ticket_position{$index}{id} }
diff --git a/static/css/calendar.css b/static/css/calendar.css
index c6cb8c8..915a0b0 100644
--- a/static/css/calendar.css
+++ b/static/css/calendar.css
@@ -33,6 +33,12 @@ table.rtxcalendar div.day div.event-info:hover span.tip{
     color: #505050;
 }
 
+table.rtxcalendar td.weekday-7 div.day div.event-info:hover span.tip,
+table.rtxcalendar td.weekday-6 div.day div.event-info:hover span.tip,
+table.rtxcalendar td.weekday-5 div.day div.event-info:hover span.tip {
+    left: unset;
+    right: 24px;
+}
 
 /* For the full calendar */
 table.rtxcalendar {

commit 165c2735c5bc5375e73217c0e890be405035385b
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Sep 22 16:10:07 2023 -0300

    Remove the daily events sorting sub routine from the settings
    
    The CalendarSortingEvents setting was introduced to allow the user to
    sort the daily events in the calendar. That was first implemented for
    giving a possibility sort events by status, grouping the events that are
    in the same status together. Although that could be useful in a few
    cases, it was a blocker to implement the multiple days events feature.
    
    Since this feature was never introduced in a final version of the
    extension, it is safe to remove it and sort the events by date.
    
    Now the events are sorted by the first date field of the search format.
    
    In order to make this sorting, we are using the GetDate method that was
    previously a private method of the RTx::Calendar package. So we renamed
    it from _GetDate to GetDate and made it public.

diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
index 6340a1f..5241e22 100644
--- a/etc/RTxCalendar_Config.pm
+++ b/etc/RTxCalendar_Config.pm
@@ -18,12 +18,6 @@ Set(%CalendarStatusColorMap, (
     'stalled'                               => '#FF0000',
 ));
 
-Set($CalendarSortEvents, sub {
-    my @Tickets = @_;
-    my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
-    return @SortedTickets;
-});
-
 Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
 
 Set(@CalendarFilterDefaultStatuses, qw(new open));
diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index c621d40..f6568f3 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -86,14 +86,17 @@ while ($date <= $end) {
   push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
   push @classes, "aweekago"  if (DateTime->compare($aweekago,  $date) == 0);
 
-  for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
+  for my $t ( RTx::Calendar::SortCalendarEvents(
+                \@{ $Tickets->{ $date->strftime("%F") } || [] },
+                $sorting_field,
+                $session{CurrentUser} ) ) {
     # check if ticket was already displayed this week, if not, we need to find a
     # position for it
     unless ( grep { $week_ticket_position{$_}{id} eq $t->id } keys %week_ticket_position ) {
       # new tickets should assume the first empty spot.
       my $i = 1;
       my $free_index = 0;
-      for my $index ( sort keys %week_ticket_position ) {
+      for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
         if ( $week_ticket_position{$index}{id} eq "" ) {
           $free_index = $i;
           last;
@@ -116,7 +119,7 @@ while ($date <= $end) {
 
     <td class="<% @classes %>"><div class="inside-day">
       <div class="calendardate"><%$date->day%></div>
-%     for my $index ( sort keys %week_ticket_position ) {
+%     for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
 %       if ( grep { $_->id eq $week_ticket_position{$index}{id} }
 %                 @{ $Tickets->{ $date->strftime("%F") } || [] } ) {
 %         my $t = $week_ticket_position{$index}{TicketObj};
@@ -319,5 +322,5 @@ my $DownloadQueryString =
         OrderBy => $OrderBy,
       );
 
-my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
+my $sorting_field = $Dates[0] || '';
 </%INIT>
diff --git a/html/Elements/MyCalendar b/html/Elements/MyCalendar
index fb2e227..5bff533 100644
--- a/html/Elements/MyCalendar
+++ b/html/Elements/MyCalendar
@@ -23,14 +23,17 @@ while ($date <= $end) {
   my @classes = ();
   push @classes, "today"     if (DateTime->compare($today,     $date) == 0);
   push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
-  for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
+  for my $t ( RTx::Calendar::SortCalendarEvents(
+                \@{ $Tickets->{ $date->strftime("%F") } || [] },
+                $sorting_field,
+                $session{CurrentUser} ) ) {
     # check if ticket was already displayed this week, if not, we need to find a
     # position for it
     unless ( grep { $week_ticket_position{$_}{id} eq $t->id } keys %week_ticket_position ) {
       # new tickets should assume the first empty spot.
       my $i = 1;
       my $free_index = 0;
-      for my $index ( sort keys %week_ticket_position ) {
+      for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
         if ( $week_ticket_position{$index}{id} eq "" ) {
           $free_index = $i;
           last;
@@ -53,7 +56,7 @@ while ($date <= $end) {
 
     <td class="<% @classes %>"><div class="inside-day">
       <div class="calendardate"><%$date->day%></div>
-%     for my $index ( sort keys %week_ticket_position ) {
+%      for my $index ( sort { $a <=> $b } keys %week_ticket_position ) {
 %       if ( grep { $_->id eq $week_ticket_position{$index}{id} }
 %                 @{ $Tickets->{ $date->strftime("%F") } || [] } ) {
 %         my $t = $week_ticket_position{$index}{TicketObj};
@@ -143,5 +146,5 @@ $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef =
 
 my ($Tickets, $TicketsSpanningDays) = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
 
-my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
+my $sorting_field = $Dates[0] || '';
 </%INIT>
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 185cc43..6af5495 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -106,7 +106,7 @@ sub FindTickets {
             # $dateindex is the date to use as key in the Tickets Hash
             # in the YYYY-MM-DD format
             # Tickets are then groupd by date in the %Tickets hash
-            my $dateindex = _GetDate( $Date, $Ticket, $CurrentUser );
+            my $dateindex = GetDate( $Date, $Ticket, $CurrentUser );
 
             push @{ $Tickets{$dateindex } },
                 $Ticket
@@ -126,8 +126,8 @@ sub FindTickets {
                 grep { $_ eq $multiple_days_events->{$event}{'Ends'} } @$Dates;
             my $starts_field = $multiple_days_events->{$event}{'Starts'};
             my $ends_field   = $multiple_days_events->{$event}{'Ends'};
-            my $starts_date  = _GetDate( $starts_field, $Ticket, $CurrentUser );
-            my $ends_date    = _GetDate( $ends_field,   $Ticket, $CurrentUser );
+            my $starts_date  = GetDate( $starts_field, $Ticket, $CurrentUser );
+            my $ends_date    = GetDate( $ends_field,   $Ticket, $CurrentUser );
 
             # Loop through all days between start and end and add the ticket
             # to it
@@ -173,11 +173,16 @@ sub FindTickets {
     }
 }
 
-sub _GetDate {
+sub GetDate {
     my $date_field = shift;
     my $Ticket = shift;
     my $CurrentUser = shift;
 
+    unless ($date_field) {
+        $RT::Logger->debug("No date field provided. Using created date.");
+        $date_field = 'Created';
+    }
+
     if ($date_field =~ /^CF\./){
         my $cf = $date_field;
         $cf =~ s/^CF\.\{(.*)\}/$1/;
@@ -206,6 +211,19 @@ sub _GetDate {
     }
 }
 
+sub SortCalendarEvents {
+    my $tickets_of_the_day = shift;
+    my $sorting_field = shift;
+    my $current_user = shift;
+    my @sorted_tickets = sort {
+        my ($a_value, $b_value);
+        $a_value = RTx::Calendar::GetDate( $sorting_field, $a, $current_user ) . " " . $a->id;
+        $b_value = RTx::Calendar::GetDate( $sorting_field, $b, $current_user ) . " " . $b->id;
+        ($a_value cmp $b_value)
+    } @$tickets_of_the_day;
+    return @sorted_tickets;
+}
+
 #
 # Take a user object and return the search with Description "calendar" if it exists
 #
@@ -414,22 +432,6 @@ mouse over a date in F<etc/RT_SiteConfig.pm>:
          'DueObj->ISO',
          'CustomField.{Maintenance Estimated Start Date/Time - ET}'));
 
-=head3 Event sorting
-
-You can set the order that the events will presented in the day cell with
-the C<$CalendarSortEvents> setting.
-
-This setting takes a subroutine reference that will receive an array of
-L<RT::Ticket> objects and should return a sorted array of L<RT::Ticket>.
-
-The following example sorts the events by status:
-
-    Set($CalendarSortEvents, sub {
-        my @Tickets = @_;
-        my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
-        return @SortedTickets;
-    });
-
 =head3 Event colors
 
 It's also possible to change the color of the events in the calendar by

commit ece6ba01afd5bbbb234fc112f771eac6156ce09c
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Wed Sep 20 08:23:39 2023 -0300

    Remove extra line break of Event Types legend
    
    There was a unnecessary line break on Types legend taking
    extra space of the legend and sidebar. This patch removes
    it.

diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
index 1619fca..34ceaf0 100644
--- a/html/Elements/CalendarSidebar
+++ b/html/Elements/CalendarSidebar
@@ -43,7 +43,7 @@
         <span class="tiplegend">
           <% $TranslatedLegend %>
         </span>
-      </span><br />
+      </span>
 % }
     </&>
 

commit 1e47535adc4fd0f2e3b02c8062bbf7209115af0a
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Sep 15 16:14:06 2023 -0300

    Update POD with CalendarFilterDefaultStatuses config information

diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 0421abe..185cc43 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -454,6 +454,12 @@ F<etc/RT_SiteConfig.pm>:
 
     Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
 
+=head3 Default selected status on Filtering on Status field
+
+You can change the default selected statuses by adding them to the
+C<@CalendarFilterDefaultStatuses> setting to your F<etc/RT_SiteConfig.pm>:
+
+    Set(@CalendarFilterDefaultStatuses, qw(new open));
 
 =head3 Custom icons
 

commit 9ef3675d0774da334e2cd0c71cc663c677abd0d1
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Sep 15 16:09:23 2023 -0300

    Add option to pre-select Filter on Status values
    
    Add CalendarFilterDefaultStatuses option which allows to pre-select
    Status values in the Filter on Status box for the first time the
    calendar is displayed.

diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
index d042d47..6340a1f 100644
--- a/etc/RTxCalendar_Config.pm
+++ b/etc/RTxCalendar_Config.pm
@@ -26,4 +26,6 @@ Set($CalendarSortEvents, sub {
 
 Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
 
+Set(@CalendarFilterDefaultStatuses, qw(new open));
+
 1;
diff --git a/html/Elements/Calendar b/html/Elements/Calendar
index c5b1fc9..c621d40 100644
--- a/html/Elements/Calendar
+++ b/html/Elements/Calendar
@@ -189,6 +189,7 @@ while ($date <= $end) {
 </&>
 </div>
 <%INIT>
+my $NotFirstAccess = $DECODED_ARGS->{NotFirstAccess};
 my $Month = $DECODED_ARGS->{Month} || (localtime)[4];
 my $Year = $DECODED_ARGS->{Year}  || (localtime)[5] + 1900;
 my $Query = $DECODED_ARGS->{Query};
@@ -207,7 +208,11 @@ if ( $DECODED_ARGS->{FilterOnStatus} ) {
   else {
     push @FilterOnStatus, $DECODED_ARGS->{FilterOnStatus};
   }
+} else {
+  @FilterOnStatus = @{RT->Config->Get('CalendarFilterDefaultStatuses')}
+    unless $NotFirstAccess;
 }
+$NotFirstAccess = 1;
 
 if ($FilterOnStatusClear) {
   $Query = $BaseQuery if $BaseQuery;
@@ -270,11 +275,12 @@ my $QueryString =
         Format  => $Format,
         Order   => $Order,
         OrderBy => $OrderBy,
-        Rows    => $RowsPerPage
+        Rows    => $RowsPerPage,
+        NotFirstAccess => $NotFirstAccess,
       )
       if ($Query);
 
-$QueryString ||= 'NewQuery=1';
+$QueryString ||= 'NewQuery=1&NotFirstAccess=1';
 
 # we search all date types in Format string
 my @CoreDates    = grep { $TempFormat =~ m/__${_}(Relative)?__/ } @DateTypes;

commit 8d327b01bc81cb30c8e76c29e2bc2fc0381f4604
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Sep 15 15:45:04 2023 -0300

    Update calendar POD mentioning the new Portlets
    
    Add Calendar and CalendarWithSidebar portlets to the POD.

diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 36d5ee4..0421abe 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -372,12 +372,26 @@ Add this line:
 
 =head1 CONFIGURATION
 
-=head2 Base configuration
+=head2 Use the calendar on Dashboard
+
+The calendar comes with 3 different portlets that can be added to your
+RT dashboards:
+
+=over
+
+=item C<MyCalendar>, a summary of the events for the current week.
+
+=item C<Calendar>, a full month of the calendar view, without sidebar.
+
+=item C<CalendarWithSidebar>, a full month of the calendar view, with
+sidebar which includes an extra status filter and legends of the calendars.
+
+=back
 
-To use the C<MyCalendar> portlet, you must add C<MyCalendar> to
 C<$HomepageComponents> in F<etc/RT_SiteConfig.pm>:
 
-  Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar
+  Set($HomepageComponents, [qw(QuickCreate Quicksearch
+     MyCalendar Calendar CalendarWithSidebar
      MyAdminQueues MySupportQueues MyReminders RefreshHomepage)]);
 
 =head2 Display configuration

commit aeb3159b2c1b54750568c341826bb8c503291d30
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Sep 15 15:30:04 2023 -0300

    Move calendar to Element file
    
    Calendar can now be used as a dashboard portlet. Created two flavors of
    the Element to be added to the dashboard. One with the sidebar and one
    without.

diff --git a/html/Search/Calendar.html b/html/Elements/Calendar
similarity index 85%
copy from html/Search/Calendar.html
copy to html/Elements/Calendar
index 01ef818..c5b1fc9 100644
--- a/html/Search/Calendar.html
+++ b/html/Elements/Calendar
@@ -1,27 +1,10 @@
 <%args>
-$Month => (localtime)[4]
-$Year => (localtime)[5] + 1900
-$Query => undef
-$Format => undef
-$Order => undef
-$OrderBy => undef
-$RowsPerPage => undef
-$NewQuery => 0
- at FilterOnStatus => undef
-$BaseQuery => undef
-$FilterOnStatusClear => undef
+$ShowSidebar => 0
 </%args>
 
-<& /Elements/Header, Title => $title &>
-
-% if ( $m->comp_exists( '/Ticket/Elements/Tabs' ) ) {
-<& /Ticket/Elements/Tabs,
-    current_tab => "Search/Calendar.html?$QueryString",
-    Title => $title &>
-% } else {
-    <& /Elements/Tabs &>
-% }
+<div class="calendar-container">
 
+% if ($ShowSidebar) {
   <& /Elements/CalendarSidebar,
     BaseQuery => $BaseQuery,
     Month => $Month,
@@ -33,10 +16,12 @@ $FilterOnStatusClear => undef
     FilterOnStatus => \@FilterOnStatus,
     Dates => \@Dates,
   &>
+% }
 
 <div class="calendar-content">
 <&| /Widgets/TitleBox,
      title => loc('Calendar for [_1] [_2]', $rtdate->GetMonth($Month), $Year),
+     title_href => "Search/Calendar.html",
      titleright => loc('Download Spreadsheet'),
      titleright_href => $RT::WebPath. "/Search/Results.tsv?". $DownloadQueryString
      &>
@@ -49,7 +34,7 @@ $FilterOnStatusClear => undef
 %    $PYear--;
 %    $PMonth = 11;
 % }
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
+<a href="?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
 </td>
 <th align="center">
   <font size="+1"><% $rtdate->GetMonth($Month). " $Year" %></font>
@@ -60,7 +45,7 @@ $FilterOnStatusClear => undef
 %    $NYear++;
 %    $NMonth = 0;
 % }
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
+<a href="?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
 </td>
 </tr>
 </table>
@@ -170,11 +155,11 @@ while ($date <= $end) {
 <table width="100%">
 <tr>
 <td align="left">
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
+<a href="?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
 </td>
 
 <td valign="top" align="center">
-<form action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post">
+<form method="post" class="changeCalendarMonth" onsubmit="changeCalendarMonth()">
 
 <select name="Month" class="selectpicker">
 % for (0..11) {
@@ -187,22 +172,43 @@ while ($date <= $end) {
 <option value="<%$_%>" <% $_ == $Year ? 'selected' : ''%>><%$_%></option>
 % }
 </select>
-
+<input type="hidden" id="querystring" value="<% $QueryString|n %>" />
 <input type="submit" value="<% loc('Submit') %>" class="button btn btn-primary" />
 
 </form>
 </td>
 
 <td align="right">
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
+<a href="?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
 </td>
 </tr>
 </table>
 
+</div>
+
 </&>
-  <& /Elements/CalendarFooter &>
 </div>
 <%INIT>
+my $Month = $DECODED_ARGS->{Month} || (localtime)[4];
+my $Year = $DECODED_ARGS->{Year}  || (localtime)[5] + 1900;
+my $Query = $DECODED_ARGS->{Query};
+my $Format = $DECODED_ARGS->{Format};
+my $Order = $DECODED_ARGS->{Order};
+my $OrderBy = $DECODED_ARGS->{OrderBy};
+my $RowsPerPage = $DECODED_ARGS->{RowsPerPage};
+my $NewQuery = $DECODED_ARGS->{NewQuery};
+my $BaseQuery = $DECODED_ARGS->{BaseQuery};
+my $FilterOnStatusClear = $DECODED_ARGS->{FilterOnStatusClear};
+my @FilterOnStatus;
+if ( $DECODED_ARGS->{FilterOnStatus} ) {
+  if ( ref $DECODED_ARGS->{FilterOnStatus} eq 'ARRAY' ) {
+    @FilterOnStatus = @{$DECODED_ARGS->{FilterOnStatus}};
+  }
+  else {
+    push @FilterOnStatus, $DECODED_ARGS->{FilterOnStatus};
+  }
+}
+
 if ($FilterOnStatusClear) {
   $Query = $BaseQuery if $BaseQuery;
   @FilterOnStatus = ();
@@ -234,7 +240,7 @@ my $set = DateTime::Set->from_recurrence(
     next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
 );
 
-if ($FilterOnStatus[0]) {
+if (@FilterOnStatus) {
   my $StatusClause = join " OR ", map { "Status = '$_'" } @FilterOnStatus;
   $Query .= " AND " if $Query;
   $Query .= "($StatusClause)";
diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
index c752ae8..1619fca 100644
--- a/html/Elements/CalendarSidebar
+++ b/html/Elements/CalendarSidebar
@@ -1,11 +1,4 @@
 <%args>
-$BaseQuery => undef
-$Month => undef
-$Year => undef
-$Format => undef
-$Order => undef
-$OrderBy => undef
-$RowsPerPage => undef
 @FilterOnStatus => undef
 @Dates => undef
 </%args>
@@ -18,14 +11,7 @@ $RowsPerPage => undef
       &>
 
     <form id="FilterOnStatusForm"
-    action="<%$RT::WebPath%>/Search/Calendar.html" method="post">
-      <input type="hidden" name="BaseQuery" value="<%$BaseQuery%>" />
-      <input type="hidden" name="Month" value="<%$Month%>" />
-      <input type="hidden" name="Year" value="<%$Year%>" />
-      <input type="hidden" name="Format" value="<%$Format%>" />
-      <input type="hidden" name="Order" value="<%$Order%>" />
-      <input type="hidden" name="OrderBy" value="<%$OrderBy%>" />
-      <input type="hidden" name="RowsPerPage" value="<%$RowsPerPage%>" />
+     method="post">
       <select name="FilterOnStatus" id="FilterOnStatus"
         class="selectpicker filteronstatus mt-3 mb-3" multiple="multiple" size="6">
 % for my $Status (sort {loc($a) cmp loc($b)} @{RT->Config->Get('CalendarFilterStatuses')}) {
diff --git a/html/Elements/CalendarWithSidebar b/html/Elements/CalendarWithSidebar
new file mode 100644
index 0000000..7cd30c5
--- /dev/null
+++ b/html/Elements/CalendarWithSidebar
@@ -0,0 +1,3 @@
+<& /Elements/Calendar,
+  ShowSidebar => 1,
+&>
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 01ef818..c12b25d 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -1,311 +1,6 @@
-<%args>
-$Month => (localtime)[4]
-$Year => (localtime)[5] + 1900
-$Query => undef
-$Format => undef
-$Order => undef
-$OrderBy => undef
-$RowsPerPage => undef
-$NewQuery => 0
- at FilterOnStatus => undef
-$BaseQuery => undef
-$FilterOnStatusClear => undef
-</%args>
-
-<& /Elements/Header, Title => $title &>
-
-% if ( $m->comp_exists( '/Ticket/Elements/Tabs' ) ) {
-<& /Ticket/Elements/Tabs,
-    current_tab => "Search/Calendar.html?$QueryString",
-    Title => $title &>
-% } else {
-    <& /Elements/Tabs &>
-% }
-
-  <& /Elements/CalendarSidebar,
-    BaseQuery => $BaseQuery,
-    Month => $Month,
-    Year => $Year,
-    Format => $Format,
-    Order => $Order,
-    OrderBy => $OrderBy,
-    RowsPerPage => $RowsPerPage,
-    FilterOnStatus => \@FilterOnStatus,
-    Dates => \@Dates,
-  &>
-
-<div class="calendar-content">
-<&| /Widgets/TitleBox,
-     title => loc('Calendar for [_1] [_2]', $rtdate->GetMonth($Month), $Year),
-     titleright => loc('Download Spreadsheet'),
-     titleright_href => $RT::WebPath. "/Search/Results.tsv?". $DownloadQueryString
-     &>
-
-<table width="100%">
-<tr>
-<td align="left">
-% my ($PMonth, $PYear) = ($Month - 1, $Year);
-% if ($PMonth < 0) {
-%    $PYear--;
-%    $PMonth = 11;
-% }
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
-</td>
-<th align="center">
-  <font size="+1"><% $rtdate->GetMonth($Month). " $Year" %></font>
-</th>
-<td align="right">
-% my ($NMonth, $NYear) = ($Month + 1, $Year);
-% if ($NMonth > 11) {
-%    $NYear++;
-%    $NMonth = 0;
-% }
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
-</td>
-</tr>
-</table>
-
-<table class="rtxcalendar">
-
-<thead>
-<tr>
-% for ( @{$week{$weekstart}} ) {
-<th width="14%"><%$rtdate->GetWeekday($_)%></th>
-% }
-</tr>
-</thead>
-
-<tbody>
-<tr>
-<%perl>
-# We use %week_ticket_position to control the display of tickets on the
-# calendar. It has the following structure:
-# {
-#   1 => { id => 123, TicketObj => $t },
-#   2 => { id => 312, TicketObj => $t },
-#   3 => { id => '', TicketObj => undef }, # empty position
-#   4 => { id => 111, TicketObj => $t },
-# }
-# where the key is the position/line of the ticket in the current week
-# when an event ends during the week, it's removed from the hash, openning
-# the position for a new ticket to be placed at the same line on the week,
-# saving some height on the calendar.
-# This variable is cleaned every time we start a new week.
-my %week_ticket_position;
-my $day_of_week = 1;
-
-while ($date <= $end) {
-  my @classes = ();
-  push @classes, "offmonth"  if $date->month != ($Month + 1);
-  push @classes, "today"     if (DateTime->compare($today,     $date) == 0);
-  push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
-  push @classes, "aweekago"  if (DateTime->compare($aweekago,  $date) == 0);
-
-  for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
-    # check if ticket was already displayed this week, if not, we need to find a
-    # position for it
-    unless ( grep { $week_ticket_position{$_}{id} eq $t->id } keys %week_ticket_position ) {
-      # new tickets should assume the first empty spot.
-      my $i = 1;
-      my $free_index = 0;
-      for my $index ( sort keys %week_ticket_position ) {
-        if ( $week_ticket_position{$index}{id} eq "" ) {
-          $free_index = $i;
-          last;
-        }
-        $i++;
-      }
-      # if we found a free spot, we place the ticket there
-      if ( $free_index != 0 ) {
-        $week_ticket_position{$free_index}{id} = $t->id;
-        $week_ticket_position{$free_index}{TicketObj} = $t;
-      }
-      # if not, we add it to the end of the hash
-      else {
-        $week_ticket_position{((scalar keys %week_ticket_position)+1)}{id} = $t->id;
-        $week_ticket_position{((scalar keys %week_ticket_position))}{TicketObj} = $t;
-      }
-    }
-  }
-</%perl>
-
-    <td class="<% @classes %>"><div class="inside-day">
-      <div class="calendardate"><%$date->day%></div>
-%     for my $index ( sort keys %week_ticket_position ) {
-%       if ( grep { $_->id eq $week_ticket_position{$index}{id} }
-%                 @{ $Tickets->{ $date->strftime("%F") } || [] } ) {
-%         my $t = $week_ticket_position{$index}{TicketObj};
-        <& /Elements/CalendarEvent,
-          Object              => $t,
-          Date                => $date,
-          DateTypes           => \%DateTypes,
-          DayOfWeek           => $day_of_week,
-          TicketsSpanningDays => $TicketsSpanningDays,
-          WeekTicketPosition  => \%week_ticket_position,
-          CurrentPostion      => $index,
-        &>
-%       }
-%       else {
-%         # if there's no ticket for this position, we add an empty space
-             <div class="day"> </div>
-%       }
-%     }
-    </div></td>
-
-%   $date = $set->next($date);
-%   if ( $date->day_of_week == $startday_of_week ) {
-% #   we start a new week with empty positions
-%     %week_ticket_position = ();
-%     $day_of_week=1;
-      </tr><tr>
-%   }
-%   else {
-%     $day_of_week = $day_of_week + 1;
-%   }
-% }
-</tr>
-</tbody>
-</table>
-
-<table width="100%">
-<tr>
-<td align="left">
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">« <%$rtdate->GetMonth($PMonth)%></a>
-</td>
-
-<td valign="top" align="center">
-<form action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post">
-
-<select name="Month" class="selectpicker">
-% for (0..11) {
-<option value="<%$_%>" <% $_ == $Month ? 'selected' : ''%> ><%$rtdate->GetMonth($_)%></option>
-% }
-</select>
-% my $year = (localtime)[5] + 1900;
-<select name="Year" class="selectpicker">
-% for ( ($year-5) .. ($year+5)) {
-<option value="<%$_%>" <% $_ == $Year ? 'selected' : ''%>><%$_%></option>
-% }
-</select>
-
-<input type="submit" value="<% loc('Submit') %>" class="button btn btn-primary" />
-
-</form>
-</td>
-
-<td align="right">
-<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%> »</a>
-</td>
-</tr>
-</table>
-
-</&>
-  <& /Elements/CalendarFooter &>
-</div>
-<%INIT>
-if ($FilterOnStatusClear) {
-  $Query = $BaseQuery if $BaseQuery;
-  @FilterOnStatus = ();
-}
-$BaseQuery ||= $Query;
-my $title = loc("Calendar");
-
-my @DateTypes = qw/Created Starts Started Due LastUpdated Resolved/;
-
-my $rtdate = RT::Date->new($session{'CurrentUser'});
-
-my $weekstart = 'Sunday'; #RT::SiteConfig?  user pref?
-my %week = (
-  'Saturday' => [6,0..5],
-  'Sunday'   => [0..6],
-  'Monday'   => [1..6,0],
-);
-my $startday_of_week = ${$week{$weekstart}}[0]  || 7;
-my $endday_of_week   = ${$week{$weekstart}}[-1] || 7;
-
-my $today = DateTime->today;
-my $yesterday = $today->clone->subtract( days=>1 );
-my $aweekago  = $today->clone->subtract( days=>7 );
-my $date = RTx::Calendar::FirstDay($Year, $Month + 1, $startday_of_week );
-my $end  = RTx::Calendar::LastDay ($Year, $Month + 1, $endday_of_week );
-
-# use this to loop over days until $end
-my $set = DateTime::Set->from_recurrence(
-    next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
-);
-
-if ($FilterOnStatus[0]) {
-  my $StatusClause = join " OR ", map { "Status = '$_'" } @FilterOnStatus;
-  $Query .= " AND " if $Query;
-  $Query .= "($StatusClause)";
-}
-
-# Default Query and Format
-my $TempFormat = "__Starts__ __Due__";
-my $TempQuery = "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
- AND ( Owner = '" . $session{CurrentUser}->Id ."' OR Owner = 'Nobody'  )
- AND ( Type = 'reminder' OR 'Type' = 'ticket' )";
-
-if ( my $Search = RTx::Calendar::SearchDefaultCalendar($session{CurrentUser}) ) {
-  $TempFormat = $Search->SubValue('Format');
-  $TempQuery = $Search->SubValue('Query');
-}
-
-# we overide them if needed
-$TempQuery  = $Query  if $Query;
-$TempFormat = $Format if $Format;
-$Format = $TempFormat unless $Format;
-
-my $QueryString =
-      $m->comp(
-        '/Elements/QueryString',
-        Query   => $BaseQuery,
-        FilterOnStatus => \@FilterOnStatus,
-        Format  => $Format,
-        Order   => $Order,
-        OrderBy => $OrderBy,
-        Rows    => $RowsPerPage
-      )
-      if ($Query);
-
-$QueryString ||= 'NewQuery=1';
-
-# we search all date types in Format string
-my @CoreDates    = grep { $TempFormat =~ m/__${_}(Relative)?__/ } @DateTypes;
-my @CustomFields = ( $TempFormat =~ /__(CustomField\.\{.*\})__/g );
-my @DateCustomFields;
-
-for my $CustomField (@CustomFields) {
-    my $LintCustomField = $CustomField;
-    $LintCustomField =~ s/CustomField\.\{(.*)\}/$1/;
-    my $CustomFieldObj = RT::CustomField->new( RT->SystemUser );
-    $CustomFieldObj->LoadByName( Name => $LintCustomField );
-    push @DateCustomFields, $CustomField
-        if $CustomFieldObj->id
-        && ( $CustomFieldObj->Type eq 'Date'
-        || $CustomFieldObj->Type eq 'DateTime' );
-}
-
-my @Dates = (@CoreDates, @DateCustomFields);
- at Dates = map { $_ =~ s/^CustomField\.(.*)$/CF.$1/; $_ } @Dates;
-
-# used to display or not a date in Element/CalendarEvent
-my %DateTypes = map { $_ => 1 } @Dates;
-
-$TempQuery .= RTx::Calendar::DatesClauses(\@Dates, $date->strftime("%F"), $end->strftime("%F"));
-
-$m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$TempQuery, FormatRef => \$TempFormat );
-
-my ($Tickets, $TicketsSpanningDays) = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
-
-my $DownloadQueryString =
-      $m->comp(
-        '/Elements/QueryString',
-        Query   => $TempQuery,
-        Format  => $Format,
-        Order   => $Order,
-        OrderBy => $OrderBy,
-      );
-
-my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
-</%INIT>
+<& /Elements/Header, Title => loc("Calendar") &>
+<& /Elements/Tabs &>
+<& /Elements/Calendar,
+  ShowSidebar => 1,
+&>
+<& /Elements/CalendarFooter &>
diff --git a/static/css/calendar.css b/static/css/calendar.css
index 37858ac..c6cb8c8 100644
--- a/static/css/calendar.css
+++ b/static/css/calendar.css
@@ -129,6 +129,7 @@ a.calendar-toggle-sidebar.sidebar-off::before {
 .calendar-sidebar-toggle-content {
     float: left;
     width: 230px;
+    margin-top: 20px;
 }
 .calendar-sidebar-toggle-content.sidebar-off {
     width:unset;
@@ -170,3 +171,6 @@ table.rtxcalendar .day.last-day {
 table.rtxcalendar .day.first-day.last-day {
     border-radius: 5px;
 }
+.calendar-container {
+    display: flow-root;
+}
diff --git a/static/js/calendar.js b/static/js/calendar.js
index a9a00ef..07a3958 100644
--- a/static/js/calendar.js
+++ b/static/js/calendar.js
@@ -1,6 +1,12 @@
 window.onresize = resizeCalendarEventTitles;
 jQuery(function() {
     resizeCalendarEventTitles();
+
+    jQuery('.changeCalendarMonth').submit(function(e){
+        e.preventDefault();
+        changeCalendarMonth();
+    });
+
 });
 
 /*
@@ -39,3 +45,10 @@ function resizeCalendarEventTitles() {
         }
     )
 }
+
+function changeCalendarMonth() {
+    var month = jQuery('.changeCalendarMonth select[name="Month"]').val();
+    var year = jQuery('.changeCalendarMonth select[name="Year"]').val();
+    var querystring = jQuery('.changeCalendarMonth #querystring').val();
+    window.location.href = "?Month=" + month + "&Year=" + year + "&" + querystring;
+}

commit 682f759318db8c61d142f52c828500b60b3fa75f
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Wed Sep 13 22:38:25 2023 -0300

    Upgrade date objects ad methods to RT::Date
    
    Many of the date calculations of the calendar were done using different
    perl date and time modules. This was causing issues to present events in
    the user timezone.

diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index c2f35cb..db29d65 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -9,8 +9,10 @@ $CurrentPostion => undef
 </%args>
 
 <%perl>
-my $icon = RTx::Calendar::GetEventImg($Object, $today, $DateTypes, $IsReminder);
-my $spanning_tickets_for_today = $TicketsSpanningDays->{$today} || [];
+my $icon
+= RTx::Calendar::GetEventImg( $Object, $today, $DateTypes, $IsReminder,
+   $session{'CurrentUser'} );
+my $spanning_tickets_for_today    = $TicketsSpanningDays->{$today}    || [];
 my $spanning_tickets_for_tomorrow = $TicketsSpanningDays->{$tomorrow} || [];
 my $first_day_of_the_event = 0;
 my $last_day_of_the_event = 0;
@@ -19,7 +21,7 @@ my $last_day_of_the_event = 0;
 if ( ( ! grep { $_ eq $TicketId } @$spanning_tickets_for_today ) ) {
     $first_day_of_the_event = 1;
 }
-if ((!grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow )) {
+if ( ( !grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow ) ) {
     $last_day_of_the_event = 1;
     # This frees up the position for the next ticket
     $WeekTicketPosition->{$CurrentPostion}->{id} = "";
@@ -72,21 +74,34 @@ if ((!grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow )) {
 	:</strong> <% $subject%><br />
 	<br />
 
-%# logic taken from Ticket/Search/Results.tsv
-% foreach my $attr (@display_fields) {
-%    my $value;
-%
-%    if ($attr =~ /(.*)->ISO$/ and $Object->$1->Unix <= 0) {
-%        $value = '-';
-%    } elsif ($attr =~ /CustomField\.\{(.*)\}$/) {
-%        my $cf = $1;
-%        $value = $Object->FirstCustomFieldValue($cf);
-%    } else {
-%        my $method = '$Object->'.$attr.'()';
-%        $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/;
-%        $value = eval $method;
-%        if ($@) {die "<b>Check your CalendarPopupFields config in etc/RT_SiteConfig.pm</b>.<br /><br />Failed to find \"$attr\" - ". $@};
-%    }
+<%perl>
+# logic taken from Ticket/Search/Results.tsv
+ foreach my $attr (@display_fields) {
+    my $value;
+
+    if ($attr =~ /(.*)->ISO$/ and $Object->$1->Unix <= 0) {
+        $value = '-';
+    } elsif ($attr =~ /CustomField\.\{(.*)\}$/) {
+        my $cf = $1;
+        my $cf_obj = $Object->LoadCustomFieldByIdentifier($cf);
+        unless ($cf_obj->id) {
+            $RT::Logger->debug("Custom field '$cf' not available for ticket #".$Object->id);
+            next;
+        }
+        $value = $Object->FirstCustomFieldValue($cf);
+        if (grep { $_ eq $cf_obj->Type} qw(DateTime Date)) {
+            my $date_value = RT::Date->new($session{'CurrentUser'});
+            my $date_format = $cf_obj->Type eq 'DateTime' ? 'ISO' : 'unknown';
+            $date_value->Set(Format => $date_format, Value => $value, Timezone => 'UTC');
+            $value = $date_value->AsString( Timezone => 'user' );
+        }
+    } else {
+        my $method = '$Object->'.$attr.'()';
+        $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/;
+        $value = eval $method;
+        if ($@) {die "<b>Check your CalendarPopupFields config in etc/RT_SiteConfig.pm</b>.<br /><br />Failed to find \"$attr\" - ". $@};
+    }
+</%perl>
 	<strong><&|/l&><% $label_of{$attr} %></&>:</strong> <% $value %><br />
 % }
 
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index c2882ab..36d5ee4 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -34,14 +34,6 @@ sub LastDay {
     $day;
 }
 
-# we can't use RT::Date::Date because it uses gmtime
-# and we need localtime
-sub LocalDate {
-    my $ts = shift;
-    my ( $d, $m, $y ) = ( localtime($ts) )[ 3 .. 5 ];
-    sprintf "%4d-%02d-%02d", ( $y + 1900 ), ++$m, $d;
-}
-
 sub DatesClauses {
     my ( $Dates, $begin, $end ) = @_;
 
@@ -143,19 +135,19 @@ sub FindTickets {
             $current_date->Set(
                 Format => 'unknown',
                 Value => $starts_date,
-                Timezone => 'utc'
+                Timezone => 'user'
             );
             my $end_date_unix = RT::Date->new($CurrentUser);
             $end_date_unix->Set(
                 Format => 'unknown',
                 Value => $ends_date,
-                Timezone => 'utc'
+                Timezone => 'user'
             );
             $end_date_unix = $end_date_unix->Unix;
             my $first_day = 1;
             while ( $current_date->Unix <= $end_date_unix )
             {
-                my $dateindex = LocalDate( $current_date->Unix );
+                my $dateindex = $current_date->ISO( Time => 0, Timezone => 'user' );
 
                 push @{ $TicketsSpanningDays{$dateindex} }, $Ticket->id
                     unless $first_day
@@ -207,10 +199,10 @@ sub _GetDate {
         } else {
             $DateObj->Set( Format => 'ISO', Value => $CFDateValue );
         }
-        return LocalDate( $DateObj->Unix );
+        return $DateObj->ISO( Time => 0, Timezone => 'user' );
     } else {
         my $DateObj = $date_field . "Obj";
-        return LocalDate( $Ticket->$DateObj->Unix );
+        return $Ticket->$DateObj->ISO( Time => 0, Timezone => 'user' );
     }
 }
 
@@ -265,6 +257,7 @@ sub GetEventImg {
     my $CurrentDate = shift;
     my $DateTypes   = shift;
     my $IsReminder  = shift;
+    my $CurrentUser = shift;
     my $EventIcon;
     my %CalendarIcons = RT->Config->Get('CalendarIcons');
 CALENDAR_ICON:
@@ -279,7 +272,7 @@ CALENDAR_ICON:
             $ComparedDate =~ s/^\s+|\s+$//g;
             if ( $DateField eq 'Reminder' ) {
                 if ( $IsReminder
-                    && RTx::Calendar::LocalDate( $Object->DueObj->Unix ) eq
+                    && $Object->DueObj->ISO( Time => 0, Timezone => 'user' ) eq
                     $CurrentDate )
                 {
                     $EventIcon = 'reminder.png';
@@ -290,12 +283,17 @@ CALENDAR_ICON:
                 $cf =~ s/^CF\.\{(.*)\}/$1/;
                 my $DateValue = $Object->FirstCustomFieldValue($cf);
                 next CALENDAR_ICON unless $DateValue;
-                $DateValue =~ s/(.*) (.*)/$1/;
+                my $DateObj = RT::Date->new( $CurrentUser );
+                my $CustomFieldObj = RT::CustomField->new( $CurrentUser );
+                $CustomFieldObj->LoadByName( Name => $cf );
+                my $date_format = $CustomFieldObj->Type eq 'Date' ? 'unknown' : 'ISO';
+                $DateObj->Set( Format => $date_format, Value => $DateValue );
+                $DateValue = $DateObj->ISO( Time => 0, Timezone => 'user' );
                 next CALENDAR_ICON unless $DateValue eq $CurrentDate;
             } else {
                 my $DateObj = $ComparedDate . "Obj";
                 my $DateValue
-                    = RTx::Calendar::LocalDate( $Object->$DateObj->Unix );
+                    = $Object->$DateObj->ISO( Time => 0, Timezone => 'user' );
                 next CALENDAR_ICON unless $DateValue eq $CurrentDate;
             }
 

commit 46a1cecb7005bf18be5b09cc4258e20640a82a4e
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Mon Sep 11 15:56:40 2023 -0300

    Make MyCalendar Multiday events compatible
    
    Add the multiday events to the MyCalendar portlet.

diff --git a/html/Elements/MyCalendar b/html/Elements/MyCalendar
index f95cc9d..fb2e227 100644
--- a/html/Elements/MyCalendar
+++ b/html/Elements/MyCalendar
@@ -14,21 +14,67 @@
 </thead>
 <tbody>
 <tr>
-% $date = $begin->clone;
-% while ($date <= $end) {
-%   my @classes = ();
-%   push @classes, "today"     if (DateTime->compare($today,     $date) == 0);
-%   push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
+<%perl>
+my %week_ticket_position;
+my $day_of_week = 1;
+$date = $begin->clone;
+
+while ($date <= $end) {
+  my @classes = ();
+  push @classes, "today"     if (DateTime->compare($today,     $date) == 0);
+  push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
+  for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
+    # check if ticket was already displayed this week, if not, we need to find a
+    # position for it
+    unless ( grep { $week_ticket_position{$_}{id} eq $t->id } keys %week_ticket_position ) {
+      # new tickets should assume the first empty spot.
+      my $i = 1;
+      my $free_index = 0;
+      for my $index ( sort keys %week_ticket_position ) {
+        if ( $week_ticket_position{$index}{id} eq "" ) {
+          $free_index = $i;
+          last;
+        }
+        $i++;
+      }
+      # if we found a free spot, we place the ticket there
+      if ( $free_index != 0 ) {
+        $week_ticket_position{$free_index}{id} = $t->id;
+        $week_ticket_position{$free_index}{TicketObj} = $t;
+      }
+      # if not, we add it to the end of the array
+      else {
+        $week_ticket_position{((scalar keys %week_ticket_position)+1)}{id} = $t->id;
+        $week_ticket_position{((scalar keys %week_ticket_position))}{TicketObj} = $t;
+      }
+    }
+  }
+</%perl>
 
     <td class="<% @classes %>"><div class="inside-day">
       <div class="calendardate"><%$date->day%></div>
-
-%     for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
-        <& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
+%     for my $index ( sort keys %week_ticket_position ) {
+%       if ( grep { $_->id eq $week_ticket_position{$index}{id} }
+%                 @{ $Tickets->{ $date->strftime("%F") } || [] } ) {
+%         my $t = $week_ticket_position{$index}{TicketObj};
+        <& /Elements/CalendarEvent,
+          Object              => $t,
+          Date                => $date,
+          DateTypes           => \%DateTypes,
+          DayOfWeek           => $day_of_week,
+          TicketsSpanningDays => $TicketsSpanningDays,
+          WeekTicketPosition  => \%week_ticket_position,
+          CurrentPostion      => $index,
+        &>
+%       }
+%       else {
+%         # if there's no ticket for this position, we add an empty space
+             <div class="day"> </div>
+%       }
 %     }
-
     </div></td>
 % $date = $set->next($date);
+% $day_of_week = $day_of_week + 1;
 % }
 </tr>
 </tbody>
@@ -95,8 +141,7 @@ $Query .= RTx::Calendar::DatesClauses(\@Dates, $begin->strftime("%F"), $end->str
 
 $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$Query, FormatRef => \$Format );
 
-my $Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
+my ($Tickets, $TicketsSpanningDays) = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
 
 my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
-
 </%INIT>

commit aa2650b47f8130a10e9b1ec03b3f746b8a903cba
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Mon Sep 11 10:50:17 2023 -0300

    Create Element for the Calendar footer
    
    Move the footer with help information of the calendar to an Element
    file to make it easier to maintain.

diff --git a/html/Elements/CalendarFooter b/html/Elements/CalendarFooter
new file mode 100644
index 0000000..86b7ccb
--- /dev/null
+++ b/html/Elements/CalendarFooter
@@ -0,0 +1,38 @@
+<&| /Widgets/TitleBox, title => loc('Help') &>
+
+<h4 class="mt-2"><&|/l&>Displaying reminders</&>:</h4>
+<p>
+<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Edit.html">} . loc("Advanced") . '</a>' &>
+If you want to see reminders on the calendar, you need to go to the [_1] tab
+of your query and explicitly add the following clause to it:
+</&>
+ <pre>
+   AND ( Type = 'ticket' OR Type = 'reminder' )
+</pre>
+</p>
+
+<h4><&|/l&>Displaying other kind of dates</&>:</h4>
+<p>
+<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
+By default, RTx::Calendar displays Due and Starts dates. You can select other
+date fields with the Display Columns section in the [_1].
+The following format will display the two additional date fields, LastUpdated and a
+custom field called Maintenance Date:
+</&>
+<pre>
+  '<small>__Due__</small>',
+  '<small>__Starts__</small>',
+  '<small>__LastUpdated__</small>',
+  '<small>__CustomField.{Maintenance Date}__</small>'
+</pre>
+</p>
+
+<h4><&|/l&>Changing the default query</&>:</h4>
+<p>
+<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
+You can change the default query used by Calendar.html and the MyCalendar
+portlet by saving a query with the name <code>calendar</code> in the [_1].
+</&>
+</p>
+
+</&>
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 5b49c6a..01ef818 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -200,45 +200,7 @@ while ($date <= $end) {
 </table>
 
 </&>
-
-<&| /Widgets/TitleBox, title => loc('Help') &>
-
-<h4 class="mt-2"><&|/l&>Displaying reminders</&>:</h4>
-<p>
-<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Edit.html">} . loc("Advanced") . '</a>' &>
-If you want to see reminders on the calendar, you need to go to the [_1] tab
-of your query and explicitly add the following clause to it:
-</&>
- <pre>
-   AND ( Type = 'ticket' OR Type = 'reminder' )
-</pre>
-</p>
-
-<h4><&|/l&>Displaying other kind of dates</&>:</h4>
-<p>
-<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
-By default, RTx::Calendar displays Due and Starts dates. You can select other
-date fields with the Display Columns section in the [_1].
-The following format will display the two additional date fields, LastUpdated and a
-custom field called Maintenance Date:
-</&>
-<pre>
-  '<small>__Due__</small>',
-  '<small>__Starts__</small>',
-  '<small>__LastUpdated__</small>',
-  '<small>__CustomField.{Maintenance Date}__</small>'
-</pre>
-</p>
-
-<h4><&|/l&>Changing the default query</&>:</h4>
-<p>
-<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
-You can change the default query used by Calendar.html and the MyCalendar
-portlet by saving a query with the name <code>calendar</code> in the [_1].
-</&>
-</p>
-
-</&>
+  <& /Elements/CalendarFooter &>
 </div>
 <%INIT>
 if ($FilterOnStatusClear) {

commit 33f8fccb079501908ef04655b6400b2c0b62a2e5
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Mon Sep 11 10:28:11 2023 -0300

    Create Element for the Sidebar
    
    Move the sidebar of the calendar to an Element file, making it easier to
    maintain and understand the code.

diff --git a/html/Elements/CalendarSidebar b/html/Elements/CalendarSidebar
new file mode 100644
index 0000000..c752ae8
--- /dev/null
+++ b/html/Elements/CalendarSidebar
@@ -0,0 +1,108 @@
+<%args>
+$BaseQuery => undef
+$Month => undef
+$Year => undef
+$Format => undef
+$Order => undef
+$OrderBy => undef
+$RowsPerPage => undef
+ at FilterOnStatus => undef
+ at Dates => undef
+</%args>
+<div class="calendar-sidebar-toggle-content">
+  <a title="Toggle Filter" href="javascript:;" class="calendar-toggle-sidebar"></a>
+  <div class="calendar-sidebar">
+    <&| /Widgets/TitleBox,
+      title => loc('Filter on Status'),
+      class => 'calendar-filter-status-box',
+      &>
+
+    <form id="FilterOnStatusForm"
+    action="<%$RT::WebPath%>/Search/Calendar.html" method="post">
+      <input type="hidden" name="BaseQuery" value="<%$BaseQuery%>" />
+      <input type="hidden" name="Month" value="<%$Month%>" />
+      <input type="hidden" name="Year" value="<%$Year%>" />
+      <input type="hidden" name="Format" value="<%$Format%>" />
+      <input type="hidden" name="Order" value="<%$Order%>" />
+      <input type="hidden" name="OrderBy" value="<%$OrderBy%>" />
+      <input type="hidden" name="RowsPerPage" value="<%$RowsPerPage%>" />
+      <select name="FilterOnStatus" id="FilterOnStatus"
+        class="selectpicker filteronstatus mt-3 mb-3" multiple="multiple" size="6">
+% for my $Status (sort {loc($a) cmp loc($b)} @{RT->Config->Get('CalendarFilterStatuses')}) {
+        <option value="<% $Status %>"
+% if (@FilterOnStatus && $FilterOnStatus[0]) {
+      <% (grep { $Status eq $_ } @FilterOnStatus) ? 'selected="selected"':''%>
+% }
+        ><% loc($Status) %></option>
+% }
+      </select>
+      <div class="text-center">
+        <input type="submit" value="<% loc('Filter') %>" class="mr-2 button btn btn-primary" />
+        <button type="submit" id="FilterOnStatusClear" name="FilterOnStatusClear"
+          value="1" class="button btn btn-primary"><% loc('Clear Filter') %></button>
+      </div>
+    </form>
+    </&>
+
+    <&| /Widgets/TitleBox,
+      title => loc('Event Types'),
+      &>
+% foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
+      <span class="tip">
+        <span class="tipimg">
+          <img
+            src="<% $RT::WebImagesURL %>/<%
+            $CalendarIcons{$CalendarIconsTranslated{$TranslatedLegend}}|n %>" />
+        </span>
+        <span class="tiplegend">
+          <% $TranslatedLegend %>
+        </span>
+      </span><br />
+% }
+    </&>
+
+<&| /Widgets/TitleBox,
+     title => loc('State Colors'),
+     &>
+% my %ColorStatusMap = RT->Config->Get('CalendarStatusColorMap');
+% foreach my $Status (sort { lc($a) cmp lc($b) } keys %ColorStatusMap) {
+    <span style="color: <% $ColorStatusMap{$Status} %>"><% $Status %><span><br />
+% }
+</&>
+
+  </div>
+</div>
+
+<script type="text/javascript">
+  jQuery(document).ready(function() {
+    jQuery('.calendar-toggle-sidebar').click(function() {
+      jQuery('.calendar-sidebar').toggle();
+      jQuery('.calendar-sidebar-toggle-content,.calendar-toggle-sidebar').toggleClass('sidebar-off');
+      jQuery('.calendar-content').toggleClass('sidebar-off');
+    });
+  });
+</script>
+
+<%init>
+my %CalendarIcons = RT->Config->Get('CalendarIcons');
+# Sort the legend after translation
+my %CalendarIconsTranslated;
+my $LegendLabel;
+LEGEND:
+foreach my $legend (sort { lc($a) cmp lc($b) } keys %CalendarIcons) {
+  # We might have multiple labels for the same icon
+  # such as "LastUpdated, CF.{Maintenance Date}"
+  # so we need to split them and translate them individually
+  my @LegendLabels = split ',', $legend;
+  $LegendLabel = join ', ',
+    map {
+      my $label = $_;
+      next LEGEND unless ( grep { $label eq $_ } @Dates );
+      $_ =~ s/^\s+|\s+$//g;
+      $_ =~ s/^CF\.\{(.*)\}/$1/;
+      $_ = 'Last Updated' if $_ eq 'LastUpdated';
+      loc($_)
+    } @LegendLabels;
+  $CalendarIconsTranslated{$LegendLabel} = $legend;
+}
+</%init>
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index fd2926c..5b49c6a 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -22,93 +22,17 @@ $FilterOnStatusClear => undef
     <& /Elements/Tabs &>
 % }
 
-<div class="calendar-sidebar-toggle-content">
-<a title="Toggle Filter" href="javascript:;" class="calendar-toggle-sidebar"></a>
-<div class="calendar-sidebar">
-<&| /Widgets/TitleBox,
-    title => loc('Filter on Status'),
-    class => 'calendar-filter-status-box',
-    &>
-
-  <form id="FilterOnStatusForm"
-  action="<%$RT::WebPath%>/Search/Calendar.html" method="post">
-  <input type="hidden" name="BaseQuery" value="<%$BaseQuery%>" />
-  <input type="hidden" name="Month" value="<%$Month%>" />
-  <input type="hidden" name="Year" value="<%$Year%>" />
-  <input type="hidden" name="Format" value="<%$Format%>" />
-  <input type="hidden" name="Order" value="<%$Order%>" />
-  <input type="hidden" name="OrderBy" value="<%$OrderBy%>" />
-  <input type="hidden" name="RowsPerPage" value="<%$RowsPerPage%>" />
-  <select name="FilterOnStatus" id="FilterOnStatus"
-    class="selectpicker filteronstatus mt-3 mb-3" multiple="multiple" size="6">
-% for my $Status (sort {loc($a) cmp loc($b)} @{RT->Config->Get('CalendarFilterStatuses')}) {
-    <option value="<% $Status %>"
-% if (@FilterOnStatus && $FilterOnStatus[0]) {
-      <% (grep { $Status eq $_ } @FilterOnStatus) ? 'selected="selected"':''%>
-% }
-      ><% loc($Status) %></option>
-% }
-  </select>
-  <div class="text-center">
-    <input type="submit" value="<% loc('Filter') %>" class="mr-2 button btn btn-primary" />
-    <button type="submit" id="FilterOnStatusClear" name="FilterOnStatusClear"
-      value="1" class="button btn btn-primary"><% loc('Clear Filter') %></button>
-  </div>
-</form>
-</&>
-
-  <&| /Widgets/TitleBox,
-     title => loc('Event Types'),
-     &>
-
-<%perl>
-my %CalendarIcons = RT->Config->Get('CalendarIcons');
-# Sort the legend after translation
-my %CalendarIconsTranslated;
-my $LegendLabel;
-LEGEND:
-foreach my $legend (sort { lc($a) cmp lc($b) } keys %CalendarIcons) {
-  # We might have multiple labels for the same icon
-  # such as "LastUpdated, CF.{Maintenance Date}"
-  # so we need to split them and translate them individually
-  my @LegendLabels = split ',', $legend;
-  $LegendLabel = join ', ',
-    map {
-      my $label = $_;
-      next LEGEND unless ( grep { $label eq $_ } @Dates );
-      $_ =~ s/^\s+|\s+$//g;
-      $_ =~ s/^CF\.\{(.*)\}/$1/;
-      $_ = 'Last Updated' if $_ eq 'LastUpdated';
-      loc($_)
-    } @LegendLabels;
-  $CalendarIconsTranslated{$LegendLabel} = $legend;
-}
-foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
-</%perl>
-  <span class="tip">
-    <span class="tipimg">
-      <img
-        src="<% $RT::WebImagesURL %>/<%
-        $CalendarIcons{$CalendarIconsTranslated{$TranslatedLegend}}|n %>" />
-    </span>
-    <span class="tiplegend">
-      <% $TranslatedLegend %>
-    </span>
-  </span>
-%     }
-</&>
-
-<&| /Widgets/TitleBox,
-     title => loc('State Colors'),
-     &>
-% my %ColorStatusMap = RT->Config->Get('CalendarStatusColorMap');
-% foreach my $Status (sort { lc($a) cmp lc($b) } keys %ColorStatusMap) {
-    <span style="color: <% $ColorStatusMap{$Status} %>"><% $Status %><span><br />
-% }
-</&>
-
-</div>
-</div>
+  <& /Elements/CalendarSidebar,
+    BaseQuery => $BaseQuery,
+    Month => $Month,
+    Year => $Year,
+    Format => $Format,
+    Order => $Order,
+    OrderBy => $OrderBy,
+    RowsPerPage => $RowsPerPage,
+    FilterOnStatus => \@FilterOnStatus,
+    Dates => \@Dates,
+  &>
 
 <div class="calendar-content">
 <&| /Widgets/TitleBox,
@@ -316,17 +240,6 @@ portlet by saving a query with the name <code>calendar</code> in the [_1].
 
 </&>
 </div>
-
-<script type="text/javascript">
-  jQuery(document).ready(function() {
-    jQuery('.calendar-toggle-sidebar').click(function() {
-      jQuery('.calendar-sidebar').toggle();
-      jQuery('.calendar-sidebar-toggle-content,.calendar-toggle-sidebar').toggleClass('sidebar-off');
-      jQuery('.calendar-content').toggleClass('sidebar-off');
-    });
-  });
-</script>
-
 <%INIT>
 if ($FilterOnStatusClear) {
   $Query = $BaseQuery if $BaseQuery;

commit bd35c81fb9116349d59fa6173c1346ad9a5b5f09
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Sep 8 16:01:52 2023 -0300

    Add multievent days position mechanism
    
    This commit adds a new mechanism to position multievent days in the
    calendar. It simulates the UI of other known calendars.

diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index b8023d9..c2f35cb 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -2,20 +2,70 @@
 $Date => undef
 $Object => undef
 $DateTypes => undef
+$DayOfWeek => undef
+$TicketsSpanningDays => undef
+$WeekTicketPosition => undef
+$CurrentPostion => undef
 </%args>
-<div class="day">
-<small>
 
-    <% RTx::Calendar::GetEventImg($Object, $today, $DateTypes, $IsReminder)|n %>
-	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>"
+<%perl>
+my $icon = RTx::Calendar::GetEventImg($Object, $today, $DateTypes, $IsReminder);
+my $spanning_tickets_for_today = $TicketsSpanningDays->{$today} || [];
+my $spanning_tickets_for_tomorrow = $TicketsSpanningDays->{$tomorrow} || [];
+my $first_day_of_the_event = 0;
+my $last_day_of_the_event = 0;
+# If the ticket is not in the spanning tickets for today array, it means
+# it's the first day of the event
+if ( ( ! grep { $_ eq $TicketId } @$spanning_tickets_for_today ) ) {
+    $first_day_of_the_event = 1;
+}
+if ((!grep { $_ eq $TicketId } @$spanning_tickets_for_tomorrow )) {
+    $last_day_of_the_event = 1;
+    # This frees up the position for the next ticket
+    $WeekTicketPosition->{$CurrentPostion}->{id} = "";
+}
+</%perl>
+
+<div class="day
+% if ( $last_day_of_the_event || $DayOfWeek eq 7 ) {
+    last-day
+% }
+% if ( $first_day_of_the_event || $DayOfWeek eq 1 ) {
+    first-day
+% }
+" style="
 % if ( $CalendarStatusColorMap{$status} ) {
-    style="color: <%$CalendarStatusColorMap{$status}%>;"
+    background-color: <%$CalendarStatusColorMap{$status}%>;
+% }
+% # We need to decrease the z-index of the spanning days of an event
+% # so the event title (which is placed on the div of the first day of the
+% # event and has a z-index of 4) is visible, since it cross multiple days.
+% if ( (grep { $_ eq $TicketId } @$spanning_tickets_for_today)
+%        && $DayOfWeek ne 1 ) {
+    z-index: 3;
 % }
-    >
+" data-object="<% $Object->Type %>-<% $Object->id %>">
+
+    <small>
+        <div class="event-icon" style="
+% if ($last_day_of_the_event
+% && !$first_day_of_the_event) {
+            float: right;
+% }
+        ">
+            <% $icon|n %>
+        </div>
+        <div class="event-info">
+% if ( $first_day_of_the_event || $DayOfWeek eq 1 ) {
+        <a class="event-title" href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
            <% $Object->QueueObj->Name %> #<% $TicketId %>
            <% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
-           <% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %></a></small><br />
-	<span class="tip">
+           <% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %>
+        </a>
+% }
+
+
+<span class="tip">
 	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
            <% $Object->QueueObj->Name %> #<% $TicketId %>
         </a>
@@ -40,14 +90,18 @@ $DateTypes => undef
 	<strong><&|/l&><% $label_of{$attr} %></&>:</strong> <% $value %><br />
 % }
 
-<br />
-	</span>
+    <br />
+</span>
+    </div>
+</small>
+
 </div>
 
 <%init>
 use RTx::Calendar;
 
 my $today = $Date->strftime("%F");
+my $tomorrow = $Date->clone()->add(days => 1)->strftime("%F");
 
 my $TicketId;
 
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index d827a68..fd2926c 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -153,27 +153,91 @@ foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
 
 <tbody>
 <tr>
-% while ($date <= $end) {
-%   my @classes = ();
-%   push @classes, "offmonth"  if $date->month != ($Month + 1);
-%   push @classes, "today"     if (DateTime->compare($today,     $date) == 0);
-%   push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
-%   push @classes, "aweekago"  if (DateTime->compare($aweekago,  $date) == 0);
+<%perl>
+# We use %week_ticket_position to control the display of tickets on the
+# calendar. It has the following structure:
+# {
+#   1 => { id => 123, TicketObj => $t },
+#   2 => { id => 312, TicketObj => $t },
+#   3 => { id => '', TicketObj => undef }, # empty position
+#   4 => { id => 111, TicketObj => $t },
+# }
+# where the key is the position/line of the ticket in the current week
+# when an event ends during the week, it's removed from the hash, openning
+# the position for a new ticket to be placed at the same line on the week,
+# saving some height on the calendar.
+# This variable is cleaned every time we start a new week.
+my %week_ticket_position;
+my $day_of_week = 1;
+
+while ($date <= $end) {
+  my @classes = ();
+  push @classes, "offmonth"  if $date->month != ($Month + 1);
+  push @classes, "today"     if (DateTime->compare($today,     $date) == 0);
+  push @classes, "yesterday" if (DateTime->compare($yesterday, $date) == 0);
+  push @classes, "aweekago"  if (DateTime->compare($aweekago,  $date) == 0);
+
+  for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
+    # check if ticket was already displayed this week, if not, we need to find a
+    # position for it
+    unless ( grep { $week_ticket_position{$_}{id} eq $t->id } keys %week_ticket_position ) {
+      # new tickets should assume the first empty spot.
+      my $i = 1;
+      my $free_index = 0;
+      for my $index ( sort keys %week_ticket_position ) {
+        if ( $week_ticket_position{$index}{id} eq "" ) {
+          $free_index = $i;
+          last;
+        }
+        $i++;
+      }
+      # if we found a free spot, we place the ticket there
+      if ( $free_index != 0 ) {
+        $week_ticket_position{$free_index}{id} = $t->id;
+        $week_ticket_position{$free_index}{TicketObj} = $t;
+      }
+      # if not, we add it to the end of the hash
+      else {
+        $week_ticket_position{((scalar keys %week_ticket_position)+1)}{id} = $t->id;
+        $week_ticket_position{((scalar keys %week_ticket_position))}{TicketObj} = $t;
+      }
+    }
+  }
+</%perl>
 
     <td class="<% @classes %>"><div class="inside-day">
       <div class="calendardate"><%$date->day%></div>
-
-%     for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
-        <& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
+%     for my $index ( sort keys %week_ticket_position ) {
+%       if ( grep { $_->id eq $week_ticket_position{$index}{id} }
+%                 @{ $Tickets->{ $date->strftime("%F") } || [] } ) {
+%         my $t = $week_ticket_position{$index}{TicketObj};
+        <& /Elements/CalendarEvent,
+          Object              => $t,
+          Date                => $date,
+          DateTypes           => \%DateTypes,
+          DayOfWeek           => $day_of_week,
+          TicketsSpanningDays => $TicketsSpanningDays,
+          WeekTicketPosition  => \%week_ticket_position,
+          CurrentPostion      => $index,
+        &>
+%       }
+%       else {
+%         # if there's no ticket for this position, we add an empty space
+             <div class="day"> </div>
+%       }
 %     }
-
     </div></td>
 
 %   $date = $set->next($date);
 %   if ( $date->day_of_week == $startday_of_week ) {
+% #   we start a new week with empty positions
+%     %week_ticket_position = ();
+%     $day_of_week=1;
       </tr><tr>
 %   }
-
+%   else {
+%     $day_of_week = $day_of_week + 1;
+%   }
 % }
 </tr>
 </tbody>
@@ -357,7 +421,7 @@ $TempQuery .= RTx::Calendar::DatesClauses(\@Dates, $date->strftime("%F"), $end->
 
 $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$TempQuery, FormatRef => \$TempFormat );
 
-my $Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
+my ($Tickets, $TicketsSpanningDays) = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
 
 my $DownloadQueryString =
       $m->comp(
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index e03f2b6..c2882ab 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -7,6 +7,7 @@ use DateTime::Set;
 our $VERSION = "1.07";
 
 RT->AddStyleSheets('calendar.css');
+RT->AddJavaScript('calendar.js');
 
 sub FirstDay {
     my ( $year, $month, $matchday ) = @_;
@@ -151,12 +152,14 @@ sub FindTickets {
                 Timezone => 'utc'
             );
             $end_date_unix = $end_date_unix->Unix;
+            my $first_day = 1;
             while ( $current_date->Unix <= $end_date_unix )
             {
                 my $dateindex = LocalDate( $current_date->Unix );
 
                 push @{ $TicketsSpanningDays{$dateindex} }, $Ticket->id
-                    unless $TicketsSpanningDaysAlreadySeen{$dateindex}
+                    unless $first_day
+                    || $TicketsSpanningDaysAlreadySeen{$dateindex}
                     {$Ticket}++;
                 push @{ $Tickets{$dateindex } },
                     $Ticket
@@ -167,6 +170,7 @@ sub FindTickets {
                     {$Ticket}++;
 
                 $current_date->AddDay();
+                $first_day = 0;
             }
         }
     }
diff --git a/static/css/calendar.css b/static/css/calendar.css
index 96e9086..37858ac 100644
--- a/static/css/calendar.css
+++ b/static/css/calendar.css
@@ -2,9 +2,17 @@
 table.rtxcalendar .day {
     position: relative;
     z-index: 1;
+    padding: 3px 3px 3px 6px;
+    margin-top: 4px;
+    margin-bottom: 4px;
+    width:120%;
+    height: 1.75rem;
+    z-index: 4;
 }
 
-
+table.rtxcalendar .day.last-day {
+    width: 100%;
+}
 
 table.rtxcalendar .day:hover {
     z-index: 5;
@@ -14,10 +22,10 @@ table.rtxcalendar .day span.tip {
     display: none;
     text-align: left;
 }
-table.rtxcalendar div.day:hover span.tip{
+table.rtxcalendar div.day div.event-info:hover span.tip{
     display: block;
     position: absolute;
-    top:12px; left:24px; width:350px;
+    top:1rem; left:24px; width:350px;
     border: 1px solid #555;
     background-color: #fff;
     padding: 4px;
@@ -137,3 +145,28 @@ a.calendar-toggle-sidebar.sidebar-off::before {
 .calendar-sidebar {
     margin-right: 10px;
 }
+
+.event-icon {
+    float: left;
+    margin-right: 5px;
+}
+
+.event-info a.event-title {
+    overflow: hidden;
+    max-width: 100%;
+    left: 0px;
+    padding-left: 21px;
+    white-space: nowrap;
+    position: absolute;
+    color: white;
+}
+
+table.rtxcalendar .day.first-day {
+    border-radius: 5px 0 0 5px;
+}
+table.rtxcalendar .day.last-day {
+    border-radius: 0 5px 5px 0;
+}
+table.rtxcalendar .day.first-day.last-day {
+    border-radius: 5px;
+}
diff --git a/static/js/calendar.js b/static/js/calendar.js
new file mode 100644
index 0000000..a9a00ef
--- /dev/null
+++ b/static/js/calendar.js
@@ -0,0 +1,41 @@
+window.onresize = resizeCalendarEventTitles;
+jQuery(function() {
+    resizeCalendarEventTitles();
+});
+
+/*
+* Adjust the max-width of the event title according to the number spanning
+* days of an event for each week of the calendar (including MyCalendar
+* portlet) so it doesn't escape the event box.
+*/
+function resizeCalendarEventTitles() {
+    if (jQuery('.rtxcalendar').length == 0){
+        return;
+    }
+    var current_width = jQuery('.rtxcalendar')
+        .find('.inside-day').first().css('width').replace('px','');
+    jQuery('.rtxcalendar').find('tr').each(
+        function(i, tr){
+            var event_repetions_on_week = {};
+            /* Each event day (first and spanning) is marked with the
+            * data-object attribute in a format like ticket-123 */
+            jQuery(tr).find('[data-object]').each(function(j, event_day){
+                if (event_repetions_on_week[jQuery(event_day).attr('data-object')] == undefined){
+                    event_repetions_on_week[jQuery(event_day).attr('data-object')] = 1;
+                } else {
+                    event_repetions_on_week[jQuery(event_day).attr('data-object')]++;
+                }
+            })
+            for (var key in event_repetions_on_week){
+                // Find the title of the first day of the event and adjust the max-width
+                // we substract 22px to display the icon of the last day of the event
+                jQuery(tr).find('.first-day[data-object="' + key + '"]')
+                    .each(function(x, first_event_day){
+                        jQuery(first_event_day).find('.event-title')
+                        .css('max-width',
+                            ((event_repetions_on_week[key] * current_width)-22) + 'px');
+                    })
+            }
+        }
+    )
+}

commit 638c33959a8fa1d7785ea3621d6aa72897d23457
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Sep 7 18:26:10 2023 -0300

    Add Multiple Days Events to Calendar
    
    Add Multiple Days Events to Calendar where an event can span multiple
    based on customizable start and end time fields.

diff --git a/html/Elements/MyCalendar b/html/Elements/MyCalendar
index 5220934..f95cc9d 100644
--- a/html/Elements/MyCalendar
+++ b/html/Elements/MyCalendar
@@ -23,7 +23,7 @@
     <td class="<% @classes %>"><div class="inside-day">
       <div class="calendardate"><%$date->day%></div>
 
-%     for my $t ( $SortCalendarEvents->( @{ $Tickets{ $date->strftime("%F") } || [] } )) {
+%     for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
         <& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
 %     }
 
@@ -95,7 +95,7 @@ $Query .= RTx::Calendar::DatesClauses(\@Dates, $begin->strftime("%F"), $end->str
 
 $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$Query, FormatRef => \$Format );
 
-my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
+my $Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
 
 my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
 
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 5ba02af..d827a68 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -163,7 +163,7 @@ foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
     <td class="<% @classes %>"><div class="inside-day">
       <div class="calendardate"><%$date->day%></div>
 
-%     for my $t ( $SortCalendarEvents->( @{ $Tickets{ $date->strftime("%F") } || [] } )) {
+%     for my $t ( $SortCalendarEvents->( @{ $Tickets->{ $date->strftime("%F") } || [] } )) {
         <& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
 %     }
 
@@ -357,7 +357,7 @@ $TempQuery .= RTx::Calendar::DatesClauses(\@Dates, $date->strftime("%F"), $end->
 
 $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef => \$TempQuery, FormatRef => \$TempFormat );
 
-my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
+my $Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
 
 my $DownloadQueryString =
       $m->comp(
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 8120ece..e03f2b6 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -49,6 +49,30 @@ sub DatesClauses {
     my @DateClauses = map {
         "($_ >= '" . $begin . " 00:00:00' AND $_ <= '" . $end . " 23:59:59')"
     } @$Dates;
+
+    # All multiple days events are already covered on the query above
+    # The following code works for covering events that start before and ends
+    # after the selected period.
+    # Start and end fields of the multiple days must also be present on the
+    # format.
+    my $multiple_days_events = RT->Config->Get('CalendarMultipleDaysEvents');
+    for my $event ( keys %$multiple_days_events ) {
+        next unless
+            grep { $_ eq $multiple_days_events->{$event}{'Starts'} } @$Dates;
+        next unless
+            grep { $_ eq $multiple_days_events->{$event}{'Ends'} } @$Dates;
+        push @DateClauses,
+            "("
+            . $multiple_days_events->{$event}{Starts}
+            . " <= '"
+            . $end
+            . " 00:00:00' AND "
+            . $multiple_days_events->{$event}{Ends}
+            . " >= '"
+            . $begin
+            . " 23:59:59')";
+    }
+
     $clauses .= " AND " . " ( " . join( " OR ", @DateClauses ) . " ) "
         if @DateClauses;
 
@@ -58,6 +82,19 @@ sub DatesClauses {
 sub FindTickets {
     my ( $CurrentUser, $Query, $Dates, $begin, $end ) = @_;
 
+    my $multiple_days_events = RT->Config->Get('CalendarMultipleDaysEvents');
+    my @multiple_days_fields;
+    for my $event ( keys %$multiple_days_events ) {
+        next unless
+            grep { $_ eq $multiple_days_events->{$event}{'Starts'} } @$Dates;
+        next unless
+            grep { $_ eq $multiple_days_events->{$event}{'Ends'} } @$Dates;
+        for my $type ( keys %{ $multiple_days_events->{$event} } ) {
+            push @multiple_days_fields,
+                $multiple_days_events->{$event}{$type};
+        }
+    }
+
     $Query .= DatesClauses( $Dates, $begin, $end )
         if $begin and $end;
 
@@ -66,40 +103,17 @@ sub FindTickets {
 
     my %Tickets;
     my %AlreadySeen;
+    my %TicketsSpanningDays;
+    my %TicketsSpanningDaysAlreadySeen;
 
     while ( my $Ticket = $Tickets->Next() ) {
-
         # How to find the LastContacted date ?
+        # Find single day events fields
         for my $Date (@$Dates) {
-
             # $dateindex is the date to use as key in the Tickets Hash
             # in the YYYY-MM-DD format
             # Tickets are then groupd by date in the %Tickets hash
-            my $dateindex;
-            if ($Date =~ /^CF\./){
-                my $cf = $Date;
-                $cf =~ s/^CF\.\{(.*)\}/$1/;
-
-                my $CFDateValue = $Ticket->FirstCustomFieldValue($cf);
-                next unless $CFDateValue;
-                my $CustomFieldObj = RT::CustomField->new($CurrentUser);
-                $CustomFieldObj->LoadByName( Name => $cf );
-                my $CustomFieldObjType = $CustomFieldObj->Type;
-                my $DateObj            = RT::Date->new($CurrentUser);
-                if ( $CustomFieldObjType eq 'Date' ) {
-                    $DateObj->Set(
-                        Format   => 'unknown',
-                        Value    => $CFDateValue,
-                        Timezone => 'utc'
-                    );
-                } else {
-                    $DateObj->Set( Format => 'ISO', Value => $CFDateValue );
-                }
-                $dateindex = LocalDate( $DateObj->Unix );
-            } else {
-                my $DateObj = $Date . "Obj";
-                $dateindex = LocalDate( $Ticket->$DateObj->Unix );
-            }
+            my $dateindex = _GetDate( $Date, $Ticket, $CurrentUser );
 
             push @{ $Tickets{$dateindex } },
                 $Ticket
@@ -110,8 +124,90 @@ sub FindTickets {
                 or $AlreadySeen{ $dateindex }
                 {$Ticket}++;
         }
+
+        # Find spanning days of multiple days events
+        for my $event (sort keys %$multiple_days_events) {
+            next unless
+                grep { $_ eq $multiple_days_events->{$event}{'Starts'} } @$Dates;
+            next unless
+                grep { $_ eq $multiple_days_events->{$event}{'Ends'} } @$Dates;
+            my $starts_field = $multiple_days_events->{$event}{'Starts'};
+            my $ends_field   = $multiple_days_events->{$event}{'Ends'};
+            my $starts_date  = _GetDate( $starts_field, $Ticket, $CurrentUser );
+            my $ends_date    = _GetDate( $ends_field,   $Ticket, $CurrentUser );
+
+            # Loop through all days between start and end and add the ticket
+            # to it
+            my $current_date = RT::Date->new($CurrentUser);
+            $current_date->Set(
+                Format => 'unknown',
+                Value => $starts_date,
+                Timezone => 'utc'
+            );
+            my $end_date_unix = RT::Date->new($CurrentUser);
+            $end_date_unix->Set(
+                Format => 'unknown',
+                Value => $ends_date,
+                Timezone => 'utc'
+            );
+            $end_date_unix = $end_date_unix->Unix;
+            while ( $current_date->Unix <= $end_date_unix )
+            {
+                my $dateindex = LocalDate( $current_date->Unix );
+
+                push @{ $TicketsSpanningDays{$dateindex} }, $Ticket->id
+                    unless $TicketsSpanningDaysAlreadySeen{$dateindex}
+                    {$Ticket}++;
+                push @{ $Tickets{$dateindex } },
+                    $Ticket
+                    # if reminder, check it's refering to a ticket
+                    unless ( $Ticket->Type eq 'reminder'
+                    and not $Ticket->RefersTo->First )
+                    or $AlreadySeen{ $dateindex }
+                    {$Ticket}++;
+
+                $current_date->AddDay();
+            }
+        }
+    }
+    if ( wantarray ) {
+        return ( \%Tickets, \%TicketsSpanningDays );
+    } else {
+        return \%Tickets;
+    }
+}
+
+sub _GetDate {
+    my $date_field = shift;
+    my $Ticket = shift;
+    my $CurrentUser = shift;
+
+    if ($date_field =~ /^CF\./){
+        my $cf = $date_field;
+        $cf =~ s/^CF\.\{(.*)\}/$1/;
+        my $CustomFieldObj = $Ticket->LoadCustomFieldByIdentifier($cf);
+        unless ($CustomFieldObj->id) {
+            RT->Logger->debug("$cf Custom Field is not available for this object.");
+            return;
+        }
+        my $CFDateValue = $Ticket->FirstCustomFieldValue($cf);
+        return unless $CFDateValue;
+        my $CustomFieldObjType = $CustomFieldObj->Type;
+        my $DateObj            = RT::Date->new($CurrentUser);
+        if ( $CustomFieldObjType eq 'Date' ) {
+            $DateObj->Set(
+                Format   => 'unknown',
+                Value    => $CFDateValue,
+                Timezone => 'utc'
+            );
+        } else {
+            $DateObj->Set( Format => 'ISO', Value => $CFDateValue );
+        }
+        return LocalDate( $DateObj->Unix );
+    } else {
+        my $DateObj = $date_field . "Obj";
+        return LocalDate( $Ticket->$DateObj->Unix );
     }
-    return %Tickets;
 }
 
 #
@@ -355,6 +451,22 @@ C<$CalendarIcons> setting to your F<etc/RT_SiteConfig.pm>:
 
 The images should be placed on F<local/static/images>.
 
+=head3 Multiple days events
+
+You can define multiple days events by adding the C<%CalendarMultipleDaysEvents>
+setting to your F<etc/RT_SiteConfig.pm>:
+
+    Set( %CalendarMultipleDaysEvents, (
+            'Maintenance' => {
+                'Starts' => 'Starts',
+                'Ends'   => 'Due',
+            },
+        )
+    );
+
+Note that the Starts and Ends fields must be included in the search result
+Format in order the event to be displayed on the calendar.
+
 =head1 USAGE
 
 A small help section is available in /Search/Calendar.html

-----------------------------------------------------------------------


hooks/post-receive
-- 
rtx-calendar


More information about the Bps-public-commit mailing list