Source code for dayCalendar

"""Calendar widget showing days with events"""
# Author(s): Davide.De-Marchi@ec.europa.eu
# Copyright © European Union 2022-2023
# 
# Licensed under the EUPL, Version 1.2 or as soon they will be approved by 
# the European Commission subsequent versions of the EUPL (the "Licence");
# 
# You may not use this work except in compliance with the Licence.
# 
# You may obtain a copy of the Licence at:
# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12

# Unless required by applicable law or agreed to in writing, software
# distributed under the Licence is distributed on an "AS IS"
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied.
# 
# See the Licence for the specific language governing permissions and
# limitations under the Licence.
import ipyvuetify as v
from ipywidgets import widgets, Layout

import collections
from datetime import datetime, date, timedelta
from dateutil.relativedelta import relativedelta

try:
    from . import settings
except:
    import settings

    
    
# Given two dates as strings in YYY-MM-DD format, returns the number of weeks of covering the period between start and end day
def number_of_weeks(start, end):
    start_date = datetime.strptime(start, '%Y-%m-%d').date()
    end_date   = datetime.strptime(end,   '%Y-%m-%d').date()
    start_monday = start_date + timedelta(days=-start_date.weekday())
    end_monday   = end_date   + timedelta(days=-end_date.weekday())
    delta = end_monday - start_monday
    return 1 + delta.days // 7

#####################################################################################################################################################
# Calendar that displays days inside an interval of dates and optional events
#####################################################################################################################################################
[docs]class dayCalendar(): """ Input widget to display a daily calendar for a range of dates and allows for highlighting some of the days and manages the click on the days. Parameters ---------- start : str or datetime or date instance, optional Initial date of the calendar as a string in format 'YYYY-MM-DD' or as an instance of datetime.datetime or datetime.date (default is today date minus one month) end : str or datetime or date instance, optional Final date of the calendar as a string in format 'YYYY-MM-DD' or as an instance of datetime.datetime or datetime.date (default is today) color : str, optional Color to use for the highlighting of days in the calendar (default is settings.color_first) dark : bool, optional If True, the calendar will have a dark background (default is settings.dark_mode) days: list of str, optional List of days to be highlighted as strings in "YYYY-MM-DD" format (default is []). The list can contain repeated days (see show_count below). show_count: bool, optional If True, the event bar will show the number of events on each of the highlighted days (default is False) width : int, optional Width of the widget in pixels (default is 340) height : int, optional Height of the widget in pixels (default is None). If None is passed, the height will be calculated depending on the range of dates defined by start and end parameters. on_click : function, optional Python function to call when the user clicks on one day of the calendar. The function will receive as parameter a string in "YYYY-MM-DD" format. (default is None) on_click _event: function, optional Python function to call when the user clicks on the highlighting bar of one day of the calendar. The function will receive as parameter a string in "YYYY-MM-DD" format. (default is None) Example ------- Creation of a date picker widget:: from vois.vuetify import dayCalendar from ipywidgets import widgets from IPython.display import display output = widgets.Output() def on_click(day): with output: print('Clicked on ', day) c = dayCalendar.dayCalendar(start='2023-10-01', end='2023-10-31', days=['2023-10-10', '2023-10-20'], on_click=on_click) display(c.draw()) display(output) .. figure:: figures/dayCalendar.png :scale: 100 % :alt: dayCalendar widget Example of a dayCalendar """ # Initialization def __init__(self, start=date.today() + relativedelta(months=-1), # Dates as strings in "YYYY-MM-DD" format or datetime or date instances end=date.today(), color=settings.color_first, # Color used to highlight the events dark=settings.dark_mode, days=[], # List of strings in "YYYY-MM-DD" format to highlight some of the days show_count=False, # If True shows in each higlighted day one char '°' for each repetition inside the days list width=340, # Width on pixels height=None, # Height in pixels on_click=None, # Function called at the click on a day (will receive the day as string in "YYYY-MM-DD" format as parameter) on_click_event=None # Function called at the click on an event (will receive the day as string in "YYYY-MM-DD" format as parameter) ): if isinstance(start,datetime) or isinstance(start,date): self.start = start.strftime('%Y-%m-%d') else: self.start = start if isinstance(end,datetime) or isinstance(end,date): self.end = end.strftime('%Y-%m-%d') else: self.end = end self._color = color self._days = days self.show_count = show_count self.width = width self.height = height self.on_click = on_click self.on_click_event = on_click_event self.cal = v.Calendar(v_model='', start=self.start, end=self.end, now='1899-12-31', type='custom-weekly', event_more=False, event_height=6, events=[], event_color=self._color, short_weekdays=True, hide_header=False, show_month_on_first=True, weekdays=[1,2,3,4,5,6,0], dark=dark) # Week start on Monday: standard ISO! self.cal.on_event('input', self.__internal_on_click) self.cal.on_event('click:event', self.__internal_on_click_event) self.days2events() card_height = 53 * number_of_weeks(self.start, self.end) self.card = v.Card(flat=True, children=[self.cal], width=self.width, height=card_height, class_='pa-0 ma-0 mb-1') if self.height is None: self.height = 10 + card_height self.output = widgets.Output(layout=Layout(height='%dpx'%self.height)) with self.output: display(self.card) # Convert a list of days in events for the calendar widget def days2events(self): if self.show_count: # See https://en.wikipedia.org/wiki/List_of_Unicode_characters self.events = [{'name': '\u02DA'*count, 'start': day } for day,count in collections.Counter(self._days).items()] else: self.events = [{'name': '', 'start': d} for d in list(set(self._days))] self.cal.events = self.events # Manage click on a day def __internal_on_click(self, widget, event, data): if not self.on_click is None: self.on_click(data) # Manage click on an event def __internal_on_click_event(self, widget, event, data): if 'event' in data: day = data['event']['start'] if not self.on_click_event is None: self.on_click_event(day) # Returns the vuetify object to display (the Output widget containing the card containing the calendar)
[docs] def draw(self): """Returns the ipyvuetify object to display (the internal Output widget)""" return self.output
# color property @property def color(self): """ Get/Set the color of the highlighted days Returns -------- color : str Color of the highlighted days in the calendar Example ------- Programmatically change the color:: cal.color = 'red' print(cal.color) """ return self._color @color.setter def color(self, col): self._color = col self.cal.event_color = self._color # days property @property def days(self): """ Get/Set the highlighted days Returns -------- listfodays : list of strings in "YYYY-MM-DD" format List of days currently highlighted in the calendar Example ------- Programmatically change the highlighted days:: cal.days = ['2023-10-15', '2023-10-25'] print(cal.days) """ return self._days @days.setter def days(self, listofdays): self._days = listofdays self.days2events()