# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2023 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 datetime 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=[], firstmonth=9, lastmonth=7, halfday=0, dayattributes="", pad_width=8, ): """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) if halfday, generate 2 cells per day (morning, afternoon) """ T = [ '' ] T.append("") month = firstmonth while 1: T.append('") if month == lastmonth: break month = month + 1 if month > 12: month = 1 year = year + 1 T.append("
') T.append(MonthTableHead(month)) T.append( MonthTableBody( month, year, events, halfday, dayattributes, is_work_saturday(), pad_width=pad_width, ) ) T.append(MonthTableTail()) 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 MonthTableHead(month): color = WHITE return """\n""" % ( color, MONTHNAMES_ABREV[month - 1], ) def MonthTableTail(): return "
%s
\n" def MonthTableBody( month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8 ): firstday, nbdays = calendar.monthrange(year, month) localtime = time.localtime() current_weeknum = time.strftime("%U", localtime) current_year = localtime[0] T = [] # cherche date du lundi de la 1ere semaine de ce mois monday = ddmmyyyy("1/%d/%d" % (month, year)) while monday.weekday != 0: monday = monday.prev() if work_saturday: weekend = ("D",) else: weekend = ("S", "D") if not halfday: for d in range(1, nbdays + 1): weeknum = time.strftime( "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y") ) 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 color = None legend = "" href = "" descr = "" # event this day ? # each event is a tuple (date, text, color, href) # where date is a string in ISO format (yyyy-mm-dd) for ev in events: ev_year = int(ev[0][:4]) ev_month = int(ev[0][5:7]) ev_day = int(ev[0][8:10]) if year == ev_year and month == ev_month and ev_day == d: if ev[1]: legend = ev[1] if ev[2]: color = ev[2] if ev[3]: href = ev[3] if len(ev) > 4 and ev[4]: descr = ev[4] # cc = [] if color is not None: cc.append('' % color) else: cc.append('') if href: href = 'href="%s"' % href if descr: descr = 'title="%s"' % html.escape(descr, quote=True) if href or descr: cc.append("" % (href, descr)) if legend or d == 1: if pad_width is not None: n = pad_width - len(legend) # pad to 8 cars if n > 0: legend = ( " " * (n // 2) + legend + " " * ((n + 1) // 2) ) else: legend = " " # empty cell cc.append(legend) if href or descr: cc.append("") cc.append("") cell = "".join(cc) if day == "D": monday = monday.next_day(7) if ( weeknum == current_weeknum and current_year == year and weekclass != "wkend" ): weekclass += " currentweek" T.append( '%d%s%s' % (bgcolor, weekclass, attrs, d, day, cell) ) else: # Calendar with 2 cells / day for d in range(1, nbdays + 1): weeknum = time.strftime( "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y") ) 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 if ( weeknum == current_weeknum and current_year == year and weekclass != "wkend" ): weeknum += " currentweek" if day == "D": monday = monday.next_day(7) T.append( '%d%s' % (bgcolor, weekclass, attrs, d, day) ) cc = [] for morning in (True, False): color = None legend = "" href = "" descr = "" for ev in events: ev_year = int(ev[0][:4]) ev_month = int(ev[0][5:7]) ev_day = int(ev[0][8:10]) if ev[4] is not None: ev_half = int(ev[4]) else: ev_half = 0 if ( year == ev_year and month == ev_month and ev_day == d and morning == ev_half ): if ev[1]: legend = ev[1] if ev[2]: color = ev[2] if ev[3]: href = ev[3] if len(ev) > 5 and ev[5]: descr = ev[5] # if color is not None: cc.append('' % (color)) else: cc.append('') if href: href = 'href="%s"' % href if descr: descr = 'title="%s"' % html.escape(descr, quote=True) if href or descr: cc.append("" % (href, descr)) if legend or d == 1: n = 3 - len(legend) # pad to 3 cars if n > 0: legend = ( " " * (n // 2) + legend + " " * ((n + 1) // 2) ) else: legend = "   " # empty cell cc.append(legend) if href or descr: cc.append("") cc.append("\n") T.append("".join(cc) + "") return "\n".join(T)