# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## """Génération calendrier (ancienne présentation) """ import calendar import html import time from app.scodoc.sco_exceptions import ScoValueError, ScoInvalidDateError from app.scodoc import sco_preferences import app.scodoc.sco_utils as scu def is_work_saturday(): "Vrai si le samedi est travaillé" return int(sco_preferences.get_preference("work_saturday")) def MonthNbDays(month, year): "returns nb of days in month" if month > 7: month = month + 1 if month % 2: return 31 elif month == 2: if calendar.isleap(year): return 29 else: return 28 else: return 30 class ddmmyyyy(object): """immutable dates""" def __init__(self, date=None, fmt="ddmmyyyy", work_saturday=False): self.work_saturday = work_saturday if date is None: return try: if fmt == "ddmmyyyy": self.day, self.month, self.year = date.split("/") elif fmt == "iso": self.year, self.month, self.day = date.split("-") else: raise ValueError("invalid format spec. (%s)" % fmt) self.year = int(self.year) self.month = int(self.month) self.day = int(self.day) except ValueError: raise ScoValueError("date invalide: %s" % date) # accept years YYYY or YY, uses 1970 as pivot if self.year < 1970: if self.year > 100: raise ScoInvalidDateError("Année invalide: %s" % self.year) if self.year < 70: self.year = self.year + 2000 else: self.year = self.year + 1900 if self.month < 1 or self.month > 12: raise ScoInvalidDateError("Mois invalide: %s" % self.month) if self.day < 1 or self.day > MonthNbDays(self.month, self.year): raise ScoInvalidDateError("Jour invalide: %s" % self.day) # weekday in 0-6, where 0 is monday self.weekday = calendar.weekday(self.year, self.month, self.day) self.time = time.mktime((self.year, self.month, self.day, 0, 0, 0, 0, 0, 0)) def iswork(self): "returns true if workable day" if self.work_saturday: nbdays = 6 else: nbdays = 5 if ( self.weekday >= 0 and self.weekday < nbdays ): # monday-friday or monday-saturday return 1 else: return 0 def __repr__(self): return "'%02d/%02d/%04d'" % (self.day, self.month, self.year) def __str__(self): return "%02d/%02d/%04d" % (self.day, self.month, self.year) def ISO(self): "iso8601 representation of the date" return "%04d-%02d-%02d" % (self.year, self.month, self.day) def next_day(self, days=1): "date for the next day (nota: may be a non workable day)" day = self.day + days month = self.month year = self.year while day > MonthNbDays(month, year): day = day - MonthNbDays(month, year) month = month + 1 if month > 12: month = 1 year = year + 1 return self.__class__( "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday ) def prev(self, days=1): "date for previous day" day = self.day - days month = self.month year = self.year while day <= 0: month = month - 1 if month == 0: month = 12 year = year - 1 day = day + MonthNbDays(month, year) return self.__class__( "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday ) def next_monday(self): "date of next monday" return self.next_day((7 - self.weekday) % 7) def prev_monday(self): "date of last monday, but on sunday, pick next monday" if self.weekday == 6: return self.next_monday() else: return self.prev(self.weekday) def __cmp__(self, other): # #py3 TODO à supprimer """return a negative integer if self < other, zero if self == other, a positive integer if self > other""" return int(self.time - other.time) def __eq__(self, other): return self.time == other.time def __ne__(self, other): return self.time != other.time def __lt__(self, other): return self.time < other.time def __le__(self, other): return self.time <= other.time def __gt__(self, other): return self.time > other.time def __ge__(self, other): return self.time >= other.time def __hash__(self): "we are immutable !" return hash(self.time) ^ hash(str(self)) # d = ddmmyyyy( '21/12/99' ) def DateRangeISO(date_beg, date_end, workable=1): """returns list of dates in [date_beg,date_end] workable = 1 => keeps only workable days""" if not date_beg: raise ScoValueError("pas de date spécifiée !") if not date_end: date_end = date_beg r = [] work_saturday = is_work_saturday() try: cur = ddmmyyyy(date_beg, work_saturday=work_saturday) end = ddmmyyyy(date_end, work_saturday=work_saturday) except (AttributeError, ValueError) as e: raise ScoValueError("date invalide !") from e while cur <= end: if (not workable) or cur.iswork(): r.append(cur) cur = cur.next_day() return [x.ISO() for x in r] def day_names(): """Returns week day names. If work_saturday property is set, include saturday """ if is_work_saturday(): return scu.DAY_NAMES[:-1] else: return scu.DAY_NAMES[:-2] def next_iso_day(date): "return date after date" d = ddmmyyyy(date, fmt="iso", work_saturday=is_work_saturday()) return d.next_day().ISO() def YearTable( year, events_by_day: dict[str, list[dict]], firstmonth=9, lastmonth=7, dayattributes="", ): # Code simplifié en 2024: utilisé seulement pour calendrier évaluations """Generate a calendar table events = list of tuples (date, text, color, href [,halfday]) where date is a string in ISO format (yyyy-mm-dd) halfday is boolean (true: morning, false: afternoon) text = text to put in calendar (must be short, 1-5 cars) (optional) """ T = [ """""" ] T.append("") month = firstmonth while True: T.append('
') T.append(_month_table_head(month)) T.append( _month_table_body( month, year, events_by_day, dayattributes, is_work_saturday(), ) ) T.append( """
""" ) if month == lastmonth: break month = month + 1 if month > 12: month = 1 year = year + 1 T.append("") return "\n".join(T) # ------ HTML Calendar functions (see YearTable function) # MONTH/DAY NAMES: MONTHNAMES = ( "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Décembre", ) MONTHNAMES_ABREV = ( "Jan.", "Fév.", "Mars", "Avr.", "Mai ", "Juin", "Juil", "Aout", "Sept", "Oct.", "Nov.", "Déc.", ) DAYNAMES = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche") DAYNAMES_ABREV = ("L", "M", "M", "J", "V", "S", "D") # COLORS: WHITE = "#FFFFFF" GRAY1 = "#EEEEEE" GREEN3 = "#99CC99" WEEKDAYCOLOR = GRAY1 WEEKENDCOLOR = GREEN3 def _month_table_head(month): color = WHITE return f"""\n""" def _month_table_body( month, year, events_by_day: dict[str, list[dict]], trattributes="", work_saturday=False, ) -> str: """ events : [event] event = [ yyyy-mm-dd, legend, href, color, descr ] XXX """ firstday, nbdays = calendar.monthrange(year, month) localtime = time.localtime() current_weeknum = time.strftime("%U", localtime) current_year = localtime[0] rows = [] # cherche date du lundi de la 1ere semaine de ce mois monday = ddmmyyyy(f"1/{month}/{year}") while monday.weekday != 0: monday = monday.prev() if work_saturday: weekend = ("D",) else: weekend = ("S", "D") for d in range(1, nbdays + 1): weeknum = time.strftime( "%U", time.strptime("%d/%d/%d" % (d, month, year), scu.DATE_FMT) ) day = DAYNAMES_ABREV[(firstday + d - 1) % 7] if day in weekend: bgcolor = WEEKENDCOLOR weekclass = "wkend" attrs = "" else: bgcolor = WEEKDAYCOLOR weekclass = "wk" + str(monday).replace("/", "_") attrs = trattributes # events this day ? events = events_by_day.get(f"{year}-{month:02}-{d:02}", []) color = None ev_txts = [] for ev in events: color = ev.get("color") href = ev.get("href", "") description = ev.get("description", "") if href: href = f'href="{href}"' if description: description = f"""title="{html.escape(description, quote=True)}" """ if href or description: ev_txts.append(f"""{ev.get("title", "")}""") else: ev_txts.append(ev.get("title", " ")) # cc = [] if color is not None: cc.append(f'") cells = "".join(cc) if day == "D": monday = monday.next_day(7) if weeknum == current_weeknum and current_year == year and weekclass != "wkend": weekclass += " currentweek" rows.append( f"""{cells}""" ) return "\n".join(rows)
{MONTHNAMES_ABREV[month - 1]}
') else: cc.append('') cc.append(f"{', '.join(ev_txts)}
{d}{day}