From 9ce4b0947c28d4cdbcab7ed53752f8e0b9ca08fe Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Mon, 26 Oct 2020 00:31:21 +0100 Subject: [PATCH 01/21] Create empty classes for rework, add basic implementation for Slot & Location --- data/les.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 data/les.py diff --git a/data/les.py b/data/les.py new file mode 100644 index 0000000..d6dd973 --- /dev/null +++ b/data/les.py @@ -0,0 +1,43 @@ +from functions.timeFormatters import timeFromInt + + +# A container class for classes +class Course: + # Initialize a course from the dict that the JSON contains + def __init__(self, courseInfo: dict): + self.name = courseInfo["course"] + + +# A slot in a course +class Slot: + def __init__(self, slot: dict): + self.day = slot["time"][0] + self.start = timeFromInt(slot["time"][1]) + self.end = timeFromInt(slot["time"][2]) + self.locations = self.getLocations(slot) + + def getLocations(self, slot: dict): + locations = [] + + # Slot has multiple locations + if "locations" in slot: + for location in slot["locations"]: + locations.append(Location(location)) + else: + # Slot has only one location + locations.append(Location(slot)) + + return locations + + +# A location where a course might take place +class Location: + def __init__(self, slot: dict): + self.campus = slot["campus"] + self.building = slot["building"] + self.room = slot["room"] + + +# A streaming platform +class Platform: + pass From bd3c3d07452ddcfd44a9ce94cdca48f63cf36eda Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Mon, 26 Oct 2020 01:28:07 +0100 Subject: [PATCH 02/21] Create enum & base implementation for Courses --- data/les.py | 90 +++++++++++++++++++++++++++++++++++++++++++--- enums/platforms.py | 13 +++++++ 2 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 enums/platforms.py diff --git a/data/les.py b/data/les.py index d6dd973..5c495ae 100644 --- a/data/les.py +++ b/data/les.py @@ -1,22 +1,86 @@ +from enums.platforms import Platforms from functions.timeFormatters import timeFromInt -# A container class for classes +# A container class for schedules +class Schedule: + pass +# TODO extra's & special classes + + +# A container class for courses class Course: # Initialize a course from the dict that the JSON contains def __init__(self, courseInfo: dict): + self.courseInfo = courseInfo self.name = courseInfo["course"] + self.slots = [] + self.initSlots() + self.platforms = {} + self.initPlatforms() + + def initSlots(self): + """ + Function that creates Slot instances & adds them to the list + """ + for slot in self.courseInfo["slots"]: + self.slots.append(Slot(self, slot)) + + def initPlatforms(self): + """ + Function that creates Platform instances & adds them into the dict + """ + for platform in Platforms: + if platform["rep"] in self.courseInfo: + self.platforms[platform["rep"]] = Platform(platform["name"], self.courseInfo[platform["rep"]]) + + def getSlotsOnDay(self, day: str, week: int): + """ + Function that returns a list of all slots of this course + on a given day of the week + + This list then has duplicate days filtered out depending on + whether or not there is a special class on this day + """ + slots = [] + specials = [] + + for slot in self.slots: + # Skip slots on other days + if slot.day != day: + continue + + # TODO check weeks & filter slots down + + if slot.special: + specials.append(slot) + else: + slots.append(slot) + + + +# TODO add an is_online field to the JSON to allow toggling +# temporary online classes easier # A slot in a course class Slot: - def __init__(self, slot: dict): + def __init__(self, course: Course, slot: dict): + self.course = course self.day = slot["time"][0] self.start = timeFromInt(slot["time"][1]) self.end = timeFromInt(slot["time"][2]) - self.locations = self.getLocations(slot) + self.canceled = "canceled" in slot # Boolean indicating whether or not this class has been canceled + self.special = "weeks" in slot or self.canceled # Boolean indicating if this class is special or generic - def getLocations(self, slot: dict): + # TODO check if on-campus, else None + self.locations = self.setLocations(slot) + self.platform = self.course.platforms[slot["online"]] + + def setLocations(self, slot: dict): + """ + Function that creates a list of Location instances + """ locations = [] # Slot has multiple locations @@ -29,6 +93,17 @@ class Slot: return locations + def getLocations(self): + """ + Function that creates a string representation for this + slot's locations + """ + if self.locations is None: + return "" + + def getOnline(self): + pass + # A location where a course might take place class Location: @@ -37,7 +112,12 @@ class Location: self.building = slot["building"] self.room = slot["room"] + def __str__(self): + return " ".join([self.campus, self.building, self.room]) + # A streaming platform class Platform: - pass + def __init__(self, name, url): + self.name = name + self.url = url diff --git a/enums/platforms.py b/enums/platforms.py new file mode 100644 index 0000000..7de21c1 --- /dev/null +++ b/enums/platforms.py @@ -0,0 +1,13 @@ +from enum import Enum + + +class Platforms(Enum): + """ + An Enum to represent online class platforms + Name: The name of the platform + Rep: A shorter, lowercased & space-less version + """ + Bongo = {"name": "Bongo Virtual Classroom", "rep": "bongo"} + MSTeams = {"name": "MS Teams", "rep": "msteams"} + Ufora = {"name": "Ufora", "rep": "ufora"} + Zoom = {"name": "Zoom", "rep": "zoom"} From cec78f0d5f2b3477488ac2055a9b086dabe988ca Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Mon, 26 Oct 2020 14:30:38 +0100 Subject: [PATCH 03/21] Schedule implementation --- data/les.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/data/les.py b/data/les.py index 5c495ae..3b067a2 100644 --- a/data/les.py +++ b/data/les.py @@ -4,8 +4,17 @@ from functions.timeFormatters import timeFromInt # A container class for schedules class Schedule: - pass -# TODO extra's & special classes + def __init__(self, schedule: dict): + self.courses = [Course(course) for course in schedule] + self.customs = [] # Courses that only the person that called the schedule has + self.extra = [] # Courses that need special attention (canceled, online, ...) + + def addCustom(self, course): + """ + Function that adds a course into the list of courses, + useful for adding a user's custom courses + """ + self.customs.append(Course(course)) # A container class for courses From ec30228929b87c077a0357591cbac1f8895aae16 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Tue, 3 Nov 2020 14:43:49 +0100 Subject: [PATCH 04/21] check weeks & filter doubles out --- data/les.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/data/les.py b/data/les.py index 3b067a2..d2b6472 100644 --- a/data/les.py +++ b/data/les.py @@ -61,13 +61,23 @@ class Course: if slot.day != day: continue - # TODO check weeks & filter slots down + # This is a special slot that should only be added + # if the week corresponds + if slot.weeks and week not in slot.weeks: + continue if slot.special: specials.append(slot) else: slots.append(slot) +# Filter doubles out (special classes, ...) + for special in specials: + for slot in slots: + if slot.start == special.start and slot.end == special.end: + slots.remove(slot) + + return slots, specials # TODO add an is_online field to the JSON to allow toggling @@ -79,6 +89,7 @@ class Slot: self.day = slot["time"][0] self.start = timeFromInt(slot["time"][1]) self.end = timeFromInt(slot["time"][2]) + self.weeks = [] if "weeks" not in slot else slot["weeks"] self.canceled = "canceled" in slot # Boolean indicating whether or not this class has been canceled self.special = "weeks" in slot or self.canceled # Boolean indicating if this class is special or generic From 49870d23eb896a08aa80f7fb8e480600aca539d7 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Fri, 23 Jul 2021 21:03:14 +0200 Subject: [PATCH 05/21] Restart rework, for real this time :tm: --- data/les.py | 143 ------------------------------------ functions/les_rework.py | 58 +++++++++++++++ functions/timeFormatters.py | 6 +- 3 files changed, 63 insertions(+), 144 deletions(-) delete mode 100644 data/les.py create mode 100644 functions/les_rework.py diff --git a/data/les.py b/data/les.py deleted file mode 100644 index d2b6472..0000000 --- a/data/les.py +++ /dev/null @@ -1,143 +0,0 @@ -from enums.platforms import Platforms -from functions.timeFormatters import timeFromInt - - -# A container class for schedules -class Schedule: - def __init__(self, schedule: dict): - self.courses = [Course(course) for course in schedule] - self.customs = [] # Courses that only the person that called the schedule has - self.extra = [] # Courses that need special attention (canceled, online, ...) - - def addCustom(self, course): - """ - Function that adds a course into the list of courses, - useful for adding a user's custom courses - """ - self.customs.append(Course(course)) - - -# A container class for courses -class Course: - # Initialize a course from the dict that the JSON contains - def __init__(self, courseInfo: dict): - self.courseInfo = courseInfo - self.name = courseInfo["course"] - - self.slots = [] - self.initSlots() - - self.platforms = {} - self.initPlatforms() - - def initSlots(self): - """ - Function that creates Slot instances & adds them to the list - """ - for slot in self.courseInfo["slots"]: - self.slots.append(Slot(self, slot)) - - def initPlatforms(self): - """ - Function that creates Platform instances & adds them into the dict - """ - for platform in Platforms: - if platform["rep"] in self.courseInfo: - self.platforms[platform["rep"]] = Platform(platform["name"], self.courseInfo[platform["rep"]]) - - def getSlotsOnDay(self, day: str, week: int): - """ - Function that returns a list of all slots of this course - on a given day of the week - - This list then has duplicate days filtered out depending on - whether or not there is a special class on this day - """ - slots = [] - specials = [] - - for slot in self.slots: - # Skip slots on other days - if slot.day != day: - continue - - # This is a special slot that should only be added - # if the week corresponds - if slot.weeks and week not in slot.weeks: - continue - - if slot.special: - specials.append(slot) - else: - slots.append(slot) - -# Filter doubles out (special classes, ...) - for special in specials: - for slot in slots: - if slot.start == special.start and slot.end == special.end: - slots.remove(slot) - - return slots, specials - - -# TODO add an is_online field to the JSON to allow toggling -# temporary online classes easier -# A slot in a course -class Slot: - def __init__(self, course: Course, slot: dict): - self.course = course - self.day = slot["time"][0] - self.start = timeFromInt(slot["time"][1]) - self.end = timeFromInt(slot["time"][2]) - self.weeks = [] if "weeks" not in slot else slot["weeks"] - self.canceled = "canceled" in slot # Boolean indicating whether or not this class has been canceled - self.special = "weeks" in slot or self.canceled # Boolean indicating if this class is special or generic - - # TODO check if on-campus, else None - self.locations = self.setLocations(slot) - self.platform = self.course.platforms[slot["online"]] - - def setLocations(self, slot: dict): - """ - Function that creates a list of Location instances - """ - locations = [] - - # Slot has multiple locations - if "locations" in slot: - for location in slot["locations"]: - locations.append(Location(location)) - else: - # Slot has only one location - locations.append(Location(slot)) - - return locations - - def getLocations(self): - """ - Function that creates a string representation for this - slot's locations - """ - if self.locations is None: - return "" - - def getOnline(self): - pass - - -# A location where a course might take place -class Location: - def __init__(self, slot: dict): - self.campus = slot["campus"] - self.building = slot["building"] - self.room = slot["room"] - - def __str__(self): - return " ".join([self.campus, self.building, self.room]) - - -# A streaming platform -class Platform: - def __init__(self, name, url): - self.name = name - self.url = url diff --git a/functions/les_rework.py b/functions/les_rework.py new file mode 100644 index 0000000..6646a4a --- /dev/null +++ b/functions/les_rework.py @@ -0,0 +1,58 @@ +from datetime import datetime, timedelta +from timeFormatters import dateTimeNow, weekdayToInt +from typing import Optional + + +def find_target_date(arg: Optional[str]) -> datetime: + """ + Find the requested date out of the user's arguments + """ + # Start at current date + day: datetime = dateTimeNow() + + # If no offset was provided, check the time + # otherwise the argument overrides it + if arg is None: + # When the command is used after 6 pm, show schedule + # for the next day instead + if day.hour > 18: + day += timedelta(days=1) + elif 0 <= (weekday := weekdayToInt(arg)) <= 4: # Weekday provided + day = forward_to_weekday(day, weekday) + elif arg.lower() == "morgen": # Tomorrow's schedule + day += timedelta(days=1) + elif arg.lower() == "overmorgen": # Day after tomorrow's schedule + day += timedelta(days=2) + + # Don't land on a weekend + day = skip_weekends(day) + + return day + + +def skip_weekends(day: datetime) -> datetime: + """ + Increment the current date if it's not a weekday + """ + weekday = day.weekday() + + # Friday is weekday 4 + if weekday > 4: + return day + timedelta(days=(7 - weekday)) + + return day + + +def forward_to_weekday(day: datetime, weekday: int) -> datetime: + """ + Increment a date until the weekday is the same as the one provided + Finds the "next" [weekday] + """ + current = day.weekday() + + # This avoids negative numbers below, and shows + # next week in case the days are the same + if weekday >= current: + weekday += 7 + + return day + timedelta(days=(weekday - current)) diff --git a/functions/timeFormatters.py b/functions/timeFormatters.py index b79dd74..e4f715b 100644 --- a/functions/timeFormatters.py +++ b/functions/timeFormatters.py @@ -134,8 +134,12 @@ def getPlural(amount, unit): return dic[unit.lower()]["s" if amount == 1 else "p"] -def weekdayToInt(day): +def weekdayToInt(day) -> int: days = {"maandag": 0, "dinsdag": 1, "woensdag": 2, "donderdag": 3, "vrijdag": 4, "zaterdag": 5, "zondag": 6} + + if day.lower() not in days: + return -1 + return days[day.lower()] From ee3ee5284dd3610cc99dc1ad15bdd55aec59a656 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Fri, 23 Jul 2021 23:43:19 +0200 Subject: [PATCH 06/21] Work on main Schedule, support for holidays --- data/schedule.py | 128 ++++++++++++++++++++++++++++++++++++ functions/timeFormatters.py | 16 ++++- requirements.txt | 4 +- 3 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 data/schedule.py diff --git a/data/schedule.py b/data/schedule.py new file mode 100644 index 0000000..45cdcd2 --- /dev/null +++ b/data/schedule.py @@ -0,0 +1,128 @@ +import json +from dacite import from_dict +from dataclasses import dataclass, field +from datetime import datetime, timedelta +from enums.platforms import Platforms +from functions.config import get +from typing import Dict, Optional, List + +from functions.timeFormatters import fromArray + + +@dataclass +class Holiday: + start_list: List[int] + end_list: List[int] + start_date: datetime = field(init=False) + end_date: datetime = field(init=False) + duration: timedelta = field(init=False) + + def __post_init__(self): + self.start_date = fromArray(self.start_list) + self.end_date = fromArray(self.end_list) + self.duration = self.end_date - self.start_date + + def has_passed(self, current_day: datetime) -> bool: + """ + Check if a holiday has passed already + """ + return current_day > self.end_date + + +@dataclass +class Course: + day: str + week: int + course_dict: Dict + + +@dataclass +class Location: + campus: str + building: str + room: str + + +@dataclass +class Timeslot: + course: Course + start_time: int + end_time: int + canceled: bool = False + is_special: bool = False + location: Optional[Location] = None + online_link: Optional[str] = None + online_platform: Optional[Platforms] = None + + +@dataclass +class Schedule: + day: datetime + schedule_dict: Dict = field(init=False) + start_date: datetime + end_date: datetime + semester_over: bool = False + holiday_offset: int = 0 + current_holiday: Optional[Holiday] = None + + def __post_init__(self): + self.schedule_dict: Dict = self.load_schedule_file() + self.start_date = fromArray(self.schedule_dict["semester_start"]) + self.end_date = fromArray(self.schedule_dict["semester_end"]) + + # Semester is over + if self.end_date <= self.day: + self.semester_over = True + return + + self.check_holidays() + + # Show schedule for after holidays + if self.current_holiday is not None: + self.day = self.current_holiday.end_date + timedelta(days=1) + + def check_holidays(self): + """ + Do all holiday-related stuff here to avoid multiple loops + """ + for hol_entry in self.schedule_dict.get("holidays", []): + holiday: Holiday = from_dict(Holiday, hol_entry) + + # Hasn't happened yet, don't care + if holiday.start_date > self.day: + continue + + # In the past: add the offset + if holiday.has_passed(self.day): + self.holiday_offset += (self.day - holiday.end_date) // 7 + elif holiday.start_date <= self.day <= holiday.end_date: + self.current_holiday = holiday + + def load_schedule_file(self) -> Dict: + """ + Load the schedule from the JSON file + """ + semester = get("semester") + year = get("year") + + with open(f"files/{year}{semester}.json", "r") as fp: + return json.load(fp) + + def get_week(self) -> int: + """ + Get the current week of the semester + """ + diff: timedelta = self.day - self.start_date + + # Hasn't started yet, show week 1 + if diff.days < 0: + return 1 + + # Add +1 at the end because week 1 would be 0 as it's not over yet + return (diff.days // 7) + self.holiday_offset + 1 + + def create_schedule(self): + """ + Create the schedule for the current week + """ + week: int = self.get_week() diff --git a/functions/timeFormatters.py b/functions/timeFormatters.py index e4f715b..58e6712 100644 --- a/functions/timeFormatters.py +++ b/functions/timeFormatters.py @@ -1,8 +1,12 @@ import datetime +from typing import List + import dateutil.relativedelta import pytz import time +from functions import stringFormatters + def epochToDate(epochTimeStamp, strFormat="%d/%m/%Y om %H:%M:%S"): now = dateTimeNow() @@ -147,8 +151,16 @@ def intToWeekday(day): return ["Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag", "Zondag"][day] -def fromString(timeString: str, formatString="%d/%m/%Y"): +def fromString(timeString: str, formatString="%d/%m/%Y", tzinfo=pytz.timezone("Europe/Brussels")): """ Constructs a datetime object from an input string """ - return datetime.datetime.strptime(timeString, formatString) + return datetime.datetime.strptime(timeString, formatString).replace(tzinfo=tzinfo) + + +def fromArray(data: List[int]) -> datetime: + day = stringFormatters.leadingZero(str(data[0])) + month = stringFormatters.leadingZero(str(data[1])) + year = str(data[2]) + + return fromString(f"{day}/{month}/{year}") diff --git a/requirements.txt b/requirements.txt index 77d3ae8..083a9f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,6 @@ yarl==1.4.2 feedparser==6.0.2 googletrans==4.0.0rc1 quart==0.15.1 -Quart-CORS==0.5.0 \ No newline at end of file +Quart-CORS==0.5.0 +attrs~=21.2.0 +dacite~=1.6.0 \ No newline at end of file From 54d31c943a99599bf678f3dd9f95e4977097d1f2 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Fri, 6 Aug 2021 20:52:56 +0200 Subject: [PATCH 07/21] Support to show the schedule for a requested day AFTER holidays --- cogs/school.py | 8 ++- data/schedule.py | 54 ++++++++++----- files/config.json | 2 +- files/schedules/31.json | 134 ++++++++++++++++++++++++++++++++++++ functions/les_rework.py | 30 +------- functions/timeFormatters.py | 38 ++++++++-- 6 files changed, 211 insertions(+), 55 deletions(-) create mode 100644 files/schedules/31.json diff --git a/cogs/school.py b/cogs/school.py index 89e68fe..72fd9a4 100644 --- a/cogs/school.py +++ b/cogs/school.py @@ -1,12 +1,12 @@ import random -from data import constants +from data import constants, schedule from decorators import help import discord from discord.ext import commands from enums.courses import years from enums.help_categories import Category -from functions import checks, eten, les +from functions import checks, eten, les, les_rework import json @@ -44,7 +44,9 @@ class School(commands.Cog): @commands.command(name="Les", aliases=["Class", "Classes", "Sched", "Schedule"], usage="[Jaargang]* [Dag]*") # @commands.check(checks.allowedChannels) @help.Category(category=Category.School) - async def les(self, ctx, *day): + async def les(self, ctx, day=None): + date = les_rework.find_target_date(day) + s = schedule.Schedule(date, day is not None) return # parsed = les.parseArgs(day) # diff --git a/data/schedule.py b/data/schedule.py index 45cdcd2..d489539 100644 --- a/data/schedule.py +++ b/data/schedule.py @@ -1,32 +1,31 @@ -import json from dacite import from_dict from dataclasses import dataclass, field from datetime import datetime, timedelta from enums.platforms import Platforms from functions.config import get +from functions.timeFormatters import fromArray, forward_to_weekday +import json from typing import Dict, Optional, List -from functions.timeFormatters import fromArray - @dataclass class Holiday: - start_list: List[int] - end_list: List[int] - start_date: datetime = field(init=False) - end_date: datetime = field(init=False) + start_date: List[int] + end_date: List[int] + start_date_parsed: datetime = field(init=False) + end_date_parsed: datetime = field(init=False) duration: timedelta = field(init=False) def __post_init__(self): - self.start_date = fromArray(self.start_list) - self.end_date = fromArray(self.end_list) - self.duration = self.end_date - self.start_date + self.start_date_parsed = fromArray(self.start_date) + self.end_date_parsed = fromArray(self.end_date) + self.duration = self.end_date_parsed - self.start_date_parsed def has_passed(self, current_day: datetime) -> bool: """ Check if a holiday has passed already """ - return current_day > self.end_date + return current_day > self.end_date_parsed @dataclass @@ -58,12 +57,14 @@ class Timeslot: @dataclass class Schedule: day: datetime + targetted_weekday: bool = False schedule_dict: Dict = field(init=False) - start_date: datetime - end_date: datetime + start_date: datetime = field(init=False) + end_date: datetime = field(init=False) semester_over: bool = False holiday_offset: int = 0 current_holiday: Optional[Holiday] = None + _weekday_str: str = field(init=False) def __post_init__(self): self.schedule_dict: Dict = self.load_schedule_file() @@ -77,9 +78,20 @@ class Schedule: self.check_holidays() + # Store the target weekday (in case it exists) so we can ask for the next + # friday after the holiday, for example + target_weekday = -1 if not self.targetted_weekday else self.day.weekday() + # Show schedule for after holidays if self.current_holiday is not None: - self.day = self.current_holiday.end_date + timedelta(days=1) + # Set day to day after holiday + self.day = self.current_holiday.end_date_parsed + timedelta(days=1) + + # Find the next [DAY] after the holidays + if target_weekday != -1: + self.day = forward_to_weekday(self.day, target_weekday) + + print(self.day) def check_holidays(self): """ @@ -89,13 +101,13 @@ class Schedule: holiday: Holiday = from_dict(Holiday, hol_entry) # Hasn't happened yet, don't care - if holiday.start_date > self.day: + if holiday.start_date_parsed > self.day: continue # In the past: add the offset if holiday.has_passed(self.day): - self.holiday_offset += (self.day - holiday.end_date) // 7 - elif holiday.start_date <= self.day <= holiday.end_date: + self.holiday_offset += (self.day - holiday.end_date_parsed) // 7 + elif holiday.start_date_parsed <= self.day <= holiday.end_date_parsed: self.current_holiday = holiday def load_schedule_file(self) -> Dict: @@ -105,7 +117,7 @@ class Schedule: semester = get("semester") year = get("year") - with open(f"files/{year}{semester}.json", "r") as fp: + with open(f"files/schedules/{year}{semester}.json", "r") as fp: return json.load(fp) def get_week(self) -> int: @@ -121,6 +133,12 @@ class Schedule: # Add +1 at the end because week 1 would be 0 as it's not over yet return (diff.days // 7) + self.holiday_offset + 1 + def find_slot_for_course(self, course_dict: Dict) -> List[Timeslot]: + """ + Create time timeslots for a course + """ + pass + def create_schedule(self): """ Create the schedule for the current week diff --git a/files/config.json b/files/config.json index dd9aec4..3d513ac 100644 --- a/files/config.json +++ b/files/config.json @@ -1 +1 @@ -{"semester": "2", "year": "2", "years": 2, "jpl": 161733, "jpl_day": 24} \ No newline at end of file +{"semester": "1", "year": "3", "years": 3, "jpl": 161733, "jpl_day": 24} \ No newline at end of file diff --git a/files/schedules/31.json b/files/schedules/31.json new file mode 100644 index 0000000..105e311 --- /dev/null +++ b/files/schedules/31.json @@ -0,0 +1,134 @@ +{ + "semester_start": [1, 7, 2021], + "semester_end": [16, 8, 2021], + "holidays": [ + { + "start_date": [2, 7, 2021], + "end_date": [10, 8, 2021] + } + ], + "schedule": [ + { + "course": "Computerarchitectuur", + "zoom": "https://ufora.ugent.be/d2l/ext/rp/228912/lti/framedlaunch/556e197e-e87b-4c27-be5d-53adc7a41826", + "msteams": "https://teams.microsoft.com/l/team/19%3ad7295f0bc4634a61b461504d4a7134b3%40thread.tacv2/conversations?groupId=8755cb96-1ef5-4ea3-b806-eeebf8a85ae8&tenantId=d7811cde-ecef-496c-8f91-a1786241b99c", + "slots": [ + ] + }, + { + "course": "Multimedia", + "zoom": "https://ugent-be.zoom.us/j/94248831947?pwd=ZCt4UnBLSzViZnFEQmkzWE5SYnF2QT09", + "slots": [ + { + "campus": "Sterre", + "building": "S9", + "room": "A3", + "time": [ + "woensdag", + 1130, + 1330 + ] + }, + { + "online": "ZOOM", + "time": [ + "vrijdag", + 1300, + 1530 + ] + } + ] + }, + { + "course": "Wetenschappelijk Rekenen", + "zoom": "https://ufora.ugent.be/d2l/ext/rp/236404/lti/framedlaunch/556e197e-e87b-4c27-be5d-53adc7a41826", + "slots": [ + { + "online": "ZOOM", + "time": [ + "dinsdag", + 1130, + 1300 + ] + }, + { + "online": "ZOOM", + "time": [ + "woensdag", + 1500, + 1800 + ] + }, + { + "online": "ZOOM", + "time": [ + "donderdag", + 830, + 1000 + ] + } + ] + }, + { + "course": "Software Engineering Lab 1", + "zoom": "https://ufora.ugent.be/d2l/ext/rp/235800/lti/framedlaunch/556e197e-e87b-4c27-be5d-53adc7a41826", + "msteams": "https://teams.microsoft.com/l/team/19%3a4dfd5b2fb1ae4aa9b72706aa3a0d6867%40thread.tacv2/conversations?groupId=256d5c58-5d53-43f5-9436-497b0c852c75&tenantId=d7811cde-ecef-496c-8f91-a1786241b99c", + "slots": [ + { + "online": "MS Teams", + "time": [ + "dinsdag", + 1430, + 1700 + ] + }, + { + "online": "MS Teams", + "time": [ + "vrijdag", + 830, + 1130 + ] + } + ] + }, + { + "course": "Webdevelopment", + "zoom": "https://ugent-be.zoom.us/j/93166767783?pwd=MWdvb1BnNnlPSnAyNk52QmRzdjcwdz09", + "slots": [ + { + "campus": "Sterre", + "building": "S9", + "room": "A3", + "time": [ + "woensdag", + 900, + 1100 + ] + }, + { + "weeks": [ + 1 + ], + "canceled": true, + "campus": "Sterre", + "building": "S9", + "room": "A3", + "time": [ + "woensdag", + 900, + 1100 + ] + }, + { + "online": "ZOOM", + "time": [ + "donderdag", + 1000, + 1300 + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/functions/les_rework.py b/functions/les_rework.py index 6646a4a..1682150 100644 --- a/functions/les_rework.py +++ b/functions/les_rework.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from timeFormatters import dateTimeNow, weekdayToInt +from functions.timeFormatters import dateTimeNow, weekdayToInt, forward_to_weekday, skip_weekends from typing import Optional @@ -28,31 +28,3 @@ def find_target_date(arg: Optional[str]) -> datetime: day = skip_weekends(day) return day - - -def skip_weekends(day: datetime) -> datetime: - """ - Increment the current date if it's not a weekday - """ - weekday = day.weekday() - - # Friday is weekday 4 - if weekday > 4: - return day + timedelta(days=(7 - weekday)) - - return day - - -def forward_to_weekday(day: datetime, weekday: int) -> datetime: - """ - Increment a date until the weekday is the same as the one provided - Finds the "next" [weekday] - """ - current = day.weekday() - - # This avoids negative numbers below, and shows - # next week in case the days are the same - if weekday >= current: - weekday += 7 - - return day + timedelta(days=(weekday - current)) diff --git a/functions/timeFormatters.py b/functions/timeFormatters.py index 58e6712..3d49476 100644 --- a/functions/timeFormatters.py +++ b/functions/timeFormatters.py @@ -138,13 +138,15 @@ def getPlural(amount, unit): return dic[unit.lower()]["s" if amount == 1 else "p"] -def weekdayToInt(day) -> int: +def weekdayToInt(day: str) -> int: days = {"maandag": 0, "dinsdag": 1, "woensdag": 2, "donderdag": 3, "vrijdag": 4, "zaterdag": 5, "zondag": 6} - if day.lower() not in days: - return -1 + # Allow abbreviations + for d, i in days.items(): + if d.startswith(day): + return i - return days[day.lower()] + return -1 def intToWeekday(day): @@ -164,3 +166,31 @@ def fromArray(data: List[int]) -> datetime: year = str(data[2]) return fromString(f"{day}/{month}/{year}") + + +def skip_weekends(day: datetime) -> datetime: + """ + Increment the current date if it's not a weekday + """ + weekday = day.weekday() + + # Friday is weekday 4 + if weekday > 4: + return day + datetime.timedelta(days=(7 - weekday)) + + return day + + +def forward_to_weekday(day: datetime, weekday: int) -> datetime: + """ + Increment a date until the weekday is the same as the one provided + Finds the "next" [weekday] + """ + current = day.weekday() + + # This avoids negative numbers below, and shows + # next week in case the days are the same + if weekday <= current: + weekday += 7 + + return day + datetime.timedelta(days=(weekday - current)) From 1857bdefe97709f02ce496d2155e2b2c138af139 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Fri, 6 Aug 2021 23:47:50 +0200 Subject: [PATCH 08/21] Rework schedule a bit to make it more maintainable & easier to code --- data/schedule.py | 88 ++++++++++++---- enums/{platforms.py => platform.py} | 17 +++- files/schedules/31.json | 149 ++++++++++++++-------------- functions/les_rework.py | 1 + 4 files changed, 161 insertions(+), 94 deletions(-) rename enums/{platforms.py => platform.py} (52%) diff --git a/data/schedule.py b/data/schedule.py index d489539..b661ff3 100644 --- a/data/schedule.py +++ b/data/schedule.py @@ -1,9 +1,10 @@ +import dacite from dacite import from_dict from dataclasses import dataclass, field from datetime import datetime, timedelta -from enums.platforms import Platforms +from enums.platform import Platform, get_platform from functions.config import get -from functions.timeFormatters import fromArray, forward_to_weekday +from functions.timeFormatters import fromArray, forward_to_weekday, intToWeekday import json from typing import Dict, Optional, List @@ -30,9 +31,7 @@ class Holiday: @dataclass class Course: - day: str - week: int - course_dict: Dict + name: str @dataclass @@ -51,8 +50,37 @@ class Timeslot: is_special: bool = False location: Optional[Location] = None online_link: Optional[str] = None - online_platform: Optional[Platforms] = None + online_platform: Optional[Platform] = None + @staticmethod + def from_slot_dict(slot_dict: Dict, course_dict: Dict, current_week: int): + """ + Construct a Timeslot from a dict of data + """ + if "weeks" in slot_dict and str(current_week) in slot_dict["weeks"]: + return Timeslot.special_from_dict(slot_dict, course_dict, str(current_week)) + + course = Course(course_dict["course"]) + start_time = slot_dict["time"]["start"] + end_time = slot_dict["time"]["end"] + + # Location can be none if a class is online-only + location = dacite.from_dict(Location, slot_dict["location"]) if "location" in slot_dict else None + + # Find platform & link if this class is online + online_platform: Platform = get_platform(slot_dict.get("online", None)) + online_link = course_dict["online_links"][Platform.value["rep"]] if online_platform is not None else None + + return Timeslot(course=course, start_time=start_time, end_time=end_time, canceled=False, is_special=False, + location=location, online_platform=online_platform, online_link=online_link) + + @staticmethod + def special_from_dict(slot_dict: Dict, course_dict: Dict, current_week: str): + """ + Create a SPECIAL Timeslot from a dict and data + """ + course = Course(course_dict["course"]) + # TODO @dataclass class Schedule: @@ -78,18 +106,21 @@ class Schedule: self.check_holidays() - # Store the target weekday (in case it exists) so we can ask for the next - # friday after the holiday, for example - target_weekday = -1 if not self.targetted_weekday else self.day.weekday() + # TODO show a custom embed when no class instead of fast-forwarding + # # Store the target weekday (in case it exists) so we can ask for the next + # # friday after the holiday, for example + # target_weekday = -1 if not self.targetted_weekday else self.day.weekday() + # + # # Show schedule for after holidays + # if self.current_holiday is not None: + # # Set day to day after holiday + # self.day = self.current_holiday.end_date_parsed + timedelta(days=1) + # + # # Find the next [DAY] after the holidays + # if target_weekday != -1: + # self.day = forward_to_weekday(self.day, target_weekday) - # Show schedule for after holidays - if self.current_holiday is not None: - # Set day to day after holiday - self.day = self.current_holiday.end_date_parsed + timedelta(days=1) - - # Find the next [DAY] after the holidays - if target_weekday != -1: - self.day = forward_to_weekday(self.day, target_weekday) + self._weekday_str = intToWeekday(self.day.weekday()) print(self.day) @@ -114,8 +145,8 @@ class Schedule: """ Load the schedule from the JSON file """ - semester = get("semester") - year = get("year") + semester = get_platform("semester") + year = get_platform("year") with open(f"files/schedules/{year}{semester}.json", "r") as fp: return json.load(fp) @@ -133,14 +164,29 @@ class Schedule: # Add +1 at the end because week 1 would be 0 as it's not over yet return (diff.days // 7) + self.holiday_offset + 1 - def find_slot_for_course(self, course_dict: Dict) -> List[Timeslot]: + def find_slots_for_course(self, course_dict: Dict, current_week: int) -> List[Timeslot]: """ Create time timeslots for a course """ - pass + slots_today = [] + + # First create a list of all slots of today + for slot in course_dict["slots"]: + # This slot is for a different day + if slot["time"]["day"] != self._weekday_str.lower(): + continue + + slots_today.append(slot) + + # Create Timeslots + slots_today = list(map(lambda x: Timeslot.from_slot_dict(x, course_dict, current_week), slots_today)) + + return slots_today def create_schedule(self): """ Create the schedule for the current week """ week: int = self.get_week() + slots: List[List[Timeslot]] = [self.find_slots_for_course(course, week) for course in self.schedule_dict["schedule"]] + slots_flattened = [item for sublist in slots for item in sublist] diff --git a/enums/platforms.py b/enums/platform.py similarity index 52% rename from enums/platforms.py rename to enums/platform.py index 7de21c1..b9a9520 100644 --- a/enums/platforms.py +++ b/enums/platform.py @@ -1,7 +1,8 @@ from enum import Enum +from typing import Optional -class Platforms(Enum): +class Platform(Enum): """ An Enum to represent online class platforms Name: The name of the platform @@ -11,3 +12,17 @@ class Platforms(Enum): MSTeams = {"name": "MS Teams", "rep": "msteams"} Ufora = {"name": "Ufora", "rep": "ufora"} Zoom = {"name": "Zoom", "rep": "zoom"} + + +def get_platform(rep: Optional[str]) -> Optional[Platform]: + """ + Find the platform that corresponds to the given name + """ + if rep is None: + return None + + for platform in Platform: + if platform.value["rep"] == rep: + return platform + + return None diff --git a/files/schedules/31.json b/files/schedules/31.json index 105e311..605459c 100644 --- a/files/schedules/31.json +++ b/files/schedules/31.json @@ -10,123 +10,128 @@ "schedule": [ { "course": "Computerarchitectuur", - "zoom": "https://ufora.ugent.be/d2l/ext/rp/228912/lti/framedlaunch/556e197e-e87b-4c27-be5d-53adc7a41826", - "msteams": "https://teams.microsoft.com/l/team/19%3ad7295f0bc4634a61b461504d4a7134b3%40thread.tacv2/conversations?groupId=8755cb96-1ef5-4ea3-b806-eeebf8a85ae8&tenantId=d7811cde-ecef-496c-8f91-a1786241b99c", + "online_links": { + "zoom": "https://ufora.ugent.be/d2l/ext/rp/228912/lti/framedlaunch/556e197e-e87b-4c27-be5d-53adc7a41826", + "msteams": "https://teams.microsoft.com/l/team/19%3ad7295f0bc4634a61b461504d4a7134b3%40thread.tacv2/conversations?groupId=8755cb96-1ef5-4ea3-b806-eeebf8a85ae8&tenantId=d7811cde-ecef-496c-8f91-a1786241b99c" + }, "slots": [ ] }, { "course": "Multimedia", - "zoom": "https://ugent-be.zoom.us/j/94248831947?pwd=ZCt4UnBLSzViZnFEQmkzWE5SYnF2QT09", + "online_links": { + "zoom": "https://ugent-be.zoom.us/j/94248831947?pwd=ZCt4UnBLSzViZnFEQmkzWE5SYnF2QT09" + }, "slots": [ { - "campus": "Sterre", - "building": "S9", - "room": "A3", - "time": [ - "woensdag", - 1130, - 1330 - ] + "location": { + "campus": "Sterre", + "building": "S9", + "room": "A3" + }, + "time": { + "day": "woensdag", + "start": 1130, + "end": 1330 + } }, { "online": "ZOOM", - "time": [ - "vrijdag", - 1300, - 1530 - ] + "time": { + "day": "vrijdag", + "start": 1300, + "end": 1530 + } } ] }, { "course": "Wetenschappelijk Rekenen", - "zoom": "https://ufora.ugent.be/d2l/ext/rp/236404/lti/framedlaunch/556e197e-e87b-4c27-be5d-53adc7a41826", + "online_links": { + "zoom": "https://ufora.ugent.be/d2l/ext/rp/236404/lti/framedlaunch/556e197e-e87b-4c27-be5d-53adc7a41826" + }, "slots": [ { "online": "ZOOM", - "time": [ - "dinsdag", - 1130, - 1300 - ] + "time": { + "day": "dinsdag", + "start": 1130, + "end": 1300 + } }, { "online": "ZOOM", - "time": [ - "woensdag", - 1500, - 1800 - ] + "time": { + "day": "woensdag", + "start": 1500, + "end": 1800 + } }, { "online": "ZOOM", - "time": [ - "donderdag", - 830, - 1000 - ] + "time": { + "day": "donderdag", + "start": 830, + "end": 1000 + } } ] }, { "course": "Software Engineering Lab 1", - "zoom": "https://ufora.ugent.be/d2l/ext/rp/235800/lti/framedlaunch/556e197e-e87b-4c27-be5d-53adc7a41826", - "msteams": "https://teams.microsoft.com/l/team/19%3a4dfd5b2fb1ae4aa9b72706aa3a0d6867%40thread.tacv2/conversations?groupId=256d5c58-5d53-43f5-9436-497b0c852c75&tenantId=d7811cde-ecef-496c-8f91-a1786241b99c", + "online_links": { + "zoom": "https://ufora.ugent.be/d2l/ext/rp/235800/lti/framedlaunch/556e197e-e87b-4c27-be5d-53adc7a41826", + "msteams": "https://teams.microsoft.com/l/team/19%3a4dfd5b2fb1ae4aa9b72706aa3a0d6867%40thread.tacv2/conversations?groupId=256d5c58-5d53-43f5-9436-497b0c852c75&tenantId=d7811cde-ecef-496c-8f91-a1786241b99c" + }, "slots": [ { "online": "MS Teams", - "time": [ - "dinsdag", - 1430, - 1700 - ] + "time": { + "day": "dinsdag", + "start": 1430, + "end": 1700 + } }, { "online": "MS Teams", - "time": [ - "vrijdag", - 830, - 1130 - ] + "time": { + "day": "vrijdag", + "start": 830, + "end": 1130 + } } ] }, { "course": "Webdevelopment", - "zoom": "https://ugent-be.zoom.us/j/93166767783?pwd=MWdvb1BnNnlPSnAyNk52QmRzdjcwdz09", + "online_links": { + "zoom": "https://ugent-be.zoom.us/j/93166767783?pwd=MWdvb1BnNnlPSnAyNk52QmRzdjcwdz09" + }, "slots": [ { - "campus": "Sterre", - "building": "S9", - "room": "A3", - "time": [ - "woensdag", - 900, - 1100 - ] - }, - { - "weeks": [ - 1 - ], - "canceled": true, - "campus": "Sterre", - "building": "S9", - "room": "A3", - "time": [ - "woensdag", - 900, - 1100 - ] + "weeks": { + "1": { + "canceled": true + } + }, + "location": { + "campus": "Sterre", + "building": "S9", + "room": "A3" + }, + "time": { + "day": "woensdag", + "start": 900, + "end": 1100 + } }, { "online": "ZOOM", - "time": [ - "donderdag", - 1000, - 1300 - ] + "time": { + "day": "donderdag", + "start": 1000, + "end": 1300 + } } ] } diff --git a/functions/les_rework.py b/functions/les_rework.py index 1682150..3c96154 100644 --- a/functions/les_rework.py +++ b/functions/les_rework.py @@ -24,6 +24,7 @@ def find_target_date(arg: Optional[str]) -> datetime: elif arg.lower() == "overmorgen": # Day after tomorrow's schedule day += timedelta(days=2) + # TODO show a different embed when "(over)morgen" is requested & it lands on a weekend # Don't land on a weekend day = skip_weekends(day) From b3854324d42d71422dae28c10bfc6193ae0c6ee9 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 15:18:45 +0200 Subject: [PATCH 09/21] Special weeks --- data/schedule.py | 27 +++++++++++++++------------ files/schedules/31.json | 14 +++++++------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/data/schedule.py b/data/schedule.py index b661ff3..e494855 100644 --- a/data/schedule.py +++ b/data/schedule.py @@ -57,8 +57,18 @@ class Timeslot: """ Construct a Timeslot from a dict of data """ + special = False + if "weeks" in slot_dict and str(current_week) in slot_dict["weeks"]: - return Timeslot.special_from_dict(slot_dict, course_dict, str(current_week)) + # If at least one thing was changed, this slot requires extra attention + special = True + # Overwrite the normal data with the customized entries + slot_dict.update(slot_dict["weeks"][str(current_week)]) + + # Only happens online, not on-campus + online_only = slot_dict["weeks"][str(current_week)].get("online_only", False) + if online_only: + slot_dict.pop("location") course = Course(course_dict["course"]) start_time = slot_dict["time"]["start"] @@ -71,16 +81,9 @@ class Timeslot: online_platform: Platform = get_platform(slot_dict.get("online", None)) online_link = course_dict["online_links"][Platform.value["rep"]] if online_platform is not None else None - return Timeslot(course=course, start_time=start_time, end_time=end_time, canceled=False, is_special=False, - location=location, online_platform=online_platform, online_link=online_link) + return Timeslot(course=course, start_time=start_time, end_time=end_time, canceled="canceled" in slot_dict, + is_special=special, location=location, online_platform=online_platform, online_link=online_link) - @staticmethod - def special_from_dict(slot_dict: Dict, course_dict: Dict, current_week: str): - """ - Create a SPECIAL Timeslot from a dict and data - """ - course = Course(course_dict["course"]) - # TODO @dataclass class Schedule: @@ -145,8 +148,8 @@ class Schedule: """ Load the schedule from the JSON file """ - semester = get_platform("semester") - year = get_platform("year") + semester = get("semester") + year = get("year") with open(f"files/schedules/{year}{semester}.json", "r") as fp: return json.load(fp) diff --git a/files/schedules/31.json b/files/schedules/31.json index 605459c..a11e805 100644 --- a/files/schedules/31.json +++ b/files/schedules/31.json @@ -36,7 +36,7 @@ } }, { - "online": "ZOOM", + "online": "zoom", "time": { "day": "vrijdag", "start": 1300, @@ -52,7 +52,7 @@ }, "slots": [ { - "online": "ZOOM", + "online": "zoom", "time": { "day": "dinsdag", "start": 1130, @@ -60,7 +60,7 @@ } }, { - "online": "ZOOM", + "online": "zoom", "time": { "day": "woensdag", "start": 1500, @@ -68,7 +68,7 @@ } }, { - "online": "ZOOM", + "online": "zoom", "time": { "day": "donderdag", "start": 830, @@ -85,7 +85,7 @@ }, "slots": [ { - "online": "MS Teams", + "online": "msteams", "time": { "day": "dinsdag", "start": 1430, @@ -93,7 +93,7 @@ } }, { - "online": "MS Teams", + "online": "msteams", "time": { "day": "vrijdag", "start": 830, @@ -126,7 +126,7 @@ } }, { - "online": "ZOOM", + "online": "zoom", "time": { "day": "donderdag", "start": 1000, From e8301ce8a26b0e22af6ba84aeb0a776c7dcc89f1 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 18:12:16 +0200 Subject: [PATCH 10/21] Creating embeds --- cogs/school.py | 6 +- data/schedule.py | 189 +++++++++++++++++++++++++++---- enums/platform.py | 4 +- files/schedules/31.json | 6 +- functions/timeFormatters.py | 7 ++ tests/test_data/test_schedule.py | 5 + 6 files changed, 191 insertions(+), 26 deletions(-) create mode 100644 tests/test_data/test_schedule.py diff --git a/cogs/school.py b/cogs/school.py index 72fd9a4..7d7ce9c 100644 --- a/cogs/school.py +++ b/cogs/school.py @@ -6,7 +6,7 @@ import discord from discord.ext import commands from enums.courses import years from enums.help_categories import Category -from functions import checks, eten, les, les_rework +from functions import checks, config, eten, les, les_rework import json @@ -46,8 +46,8 @@ class School(commands.Cog): @help.Category(category=Category.School) async def les(self, ctx, day=None): date = les_rework.find_target_date(day) - s = schedule.Schedule(date, day is not None) - return + s = schedule.Schedule(date, int(config.get("year")), int(config.get("semester")), day is not None) + return await ctx.send(embed=s.create_schedule().to_embed()) # parsed = les.parseArgs(day) # # # Invalid arguments diff --git a/data/schedule.py b/data/schedule.py index e494855..c298b27 100644 --- a/data/schedule.py +++ b/data/schedule.py @@ -1,10 +1,11 @@ +from abc import ABC, abstractmethod import dacite +from discord import Colour, Embed from dacite import from_dict from dataclasses import dataclass, field from datetime import datetime, timedelta from enums.platform import Platform, get_platform -from functions.config import get -from functions.timeFormatters import fromArray, forward_to_weekday, intToWeekday +from functions.timeFormatters import fromArray, intToWeekday, timeFromInt import json from typing import Dict, Optional, List @@ -33,6 +34,9 @@ class Holiday: class Course: name: str + def __str__(self): + return self.name + @dataclass class Location: @@ -40,6 +44,9 @@ class Location: building: str room: str + def __str__(self): + return f"{self.campus} {self.building} {self.room}" + @dataclass class Timeslot: @@ -52,6 +59,33 @@ class Timeslot: online_link: Optional[str] = None online_platform: Optional[Platform] = None + def __str__(self): + time_str = f"{timeFromInt(self.start_time)} - {timeFromInt(self.end_time)}" + + return f"{time_str}: {self.course} {self._get_location_str()}" + + def get_link_str(self) -> Optional[str]: + if self.online_link is None or self.online_platform is None: + return None + + return f"[{self.online_platform.value.get('name')}]({self.online_link})" + + def _get_location_str(self, offline_prefix="in", online_prefix="**online** @") -> str: + return f"{offline_prefix} {self.location}" if self.location is not None \ + else f"{online_prefix} **{self.get_link_str()}**" + + def get_special_fmt_str(self) -> Optional[str]: + if not self.canceled and not self.is_special: + return None + + # This class was canceled + if self.canceled: + return f"{self.course} van {timeFromInt(self.start_time)} gaat vandaag **niet** door." + + # Something else is wrong + return f"{self.course} gaat vandaag door van **{timeFromInt(self.start_time)}** tot " \ + f"**{timeFromInt(self.end_time)}** {self._get_location_str(online_prefix='op')}" + @staticmethod def from_slot_dict(slot_dict: Dict, course_dict: Dict, current_week: int): """ @@ -79,7 +113,7 @@ class Timeslot: # Find platform & link if this class is online online_platform: Platform = get_platform(slot_dict.get("online", None)) - online_link = course_dict["online_links"][Platform.value["rep"]] if online_platform is not None else None + online_link = course_dict["online_links"][online_platform.value["rep"]] if online_platform is not None else None return Timeslot(course=course, start_time=start_time, end_time=end_time, canceled="canceled" in slot_dict, is_special=special, location=location, online_platform=online_platform, online_link=online_link) @@ -88,14 +122,17 @@ class Timeslot: @dataclass class Schedule: day: datetime - targetted_weekday: bool = False + year: int + semester: int + targeted_weekday: bool = False + week: int = field(init=False) schedule_dict: Dict = field(init=False) start_date: datetime = field(init=False) end_date: datetime = field(init=False) semester_over: bool = False holiday_offset: int = 0 current_holiday: Optional[Holiday] = None - _weekday_str: str = field(init=False) + weekday_str: str = field(init=False) def __post_init__(self): self.schedule_dict: Dict = self.load_schedule_file() @@ -108,11 +145,12 @@ class Schedule: return self.check_holidays() + self.week = self.get_week() # TODO show a custom embed when no class instead of fast-forwarding # # Store the target weekday (in case it exists) so we can ask for the next # # friday after the holiday, for example - # target_weekday = -1 if not self.targetted_weekday else self.day.weekday() + # target_weekday = -1 if not self.targeted_weekday else self.day.weekday() # # # Show schedule for after holidays # if self.current_holiday is not None: @@ -123,9 +161,7 @@ class Schedule: # if target_weekday != -1: # self.day = forward_to_weekday(self.day, target_weekday) - self._weekday_str = intToWeekday(self.day.weekday()) - - print(self.day) + self.weekday_str = intToWeekday(self.day.weekday()) def check_holidays(self): """ @@ -140,7 +176,7 @@ class Schedule: # In the past: add the offset if holiday.has_passed(self.day): - self.holiday_offset += (self.day - holiday.end_date_parsed) // 7 + self.holiday_offset += (self.day - holiday.end_date_parsed).days // 7 elif holiday.start_date_parsed <= self.day <= holiday.end_date_parsed: self.current_holiday = holiday @@ -148,10 +184,7 @@ class Schedule: """ Load the schedule from the JSON file """ - semester = get("semester") - year = get("year") - - with open(f"files/schedules/{year}{semester}.json", "r") as fp: + with open(f"files/schedules/{self.year}{self.semester}.json", "r") as fp: return json.load(fp) def get_week(self) -> int: @@ -167,7 +200,7 @@ class Schedule: # Add +1 at the end because week 1 would be 0 as it's not over yet return (diff.days // 7) + self.holiday_offset + 1 - def find_slots_for_course(self, course_dict: Dict, current_week: int) -> List[Timeslot]: + def find_slots_for_course(self, course_dict: Dict) -> List[Timeslot]: """ Create time timeslots for a course """ @@ -176,13 +209,13 @@ class Schedule: # First create a list of all slots of today for slot in course_dict["slots"]: # This slot is for a different day - if slot["time"]["day"] != self._weekday_str.lower(): + if slot["time"]["day"] != self.weekday_str.lower(): continue slots_today.append(slot) # Create Timeslots - slots_today = list(map(lambda x: Timeslot.from_slot_dict(x, course_dict, current_week), slots_today)) + slots_today = list(map(lambda x: Timeslot.from_slot_dict(x, course_dict, self.week), slots_today)) return slots_today @@ -190,6 +223,124 @@ class Schedule: """ Create the schedule for the current week """ - week: int = self.get_week() - slots: List[List[Timeslot]] = [self.find_slots_for_course(course, week) for course in self.schedule_dict["schedule"]] + if self.current_holiday is not None: + return HolidayEmbed(self) + + slots: List[List[Timeslot]] = [self.find_slots_for_course(course) for course in self.schedule_dict["schedule"]] slots_flattened = [item for sublist in slots for item in sublist] + + # Sort by timestamp + slots_flattened.sort(key=lambda x: x.start_time) + not_canceled = list(filter(lambda x: not x.canceled, slots_flattened)) + + # All classes are canceled + if not not_canceled: + return NoClassEmbed(self, slots_flattened) + + return ScheduleEmbed(self, slots_flattened, not_canceled) + + +@dataclass +class LesEmbed(ABC): + """ + Abstract base class for Les embeds + """ + schedule: Schedule + + def get_author(self) -> str: + level = "Bachelor" if self.schedule.year < 4 else "Master" + year = self.schedule.year if self.schedule.year < 4 else self.schedule.year - 3 + suffix = "ste" if self.schedule.year == 1 else "de" + return f"Lessenrooster voor {year}{suffix} {level}" + + def get_title(self) -> str: + date = self.schedule.day.strftime("%d/%m/%Y") + return f"{self.schedule.weekday_str} {date}" + + def get_footer(self) -> str: + return f"Semester {self.schedule.semester} | Lesweek {self.schedule.week}" + + def get_extras(self) -> str: + return "" + + def get_online_links(self) -> str: + return "" + + @abstractmethod + def get_description(self) -> str: + pass + + def to_embed(self) -> Embed: + embed = Embed(title=self.get_title(), colour=Colour.blue()) + embed.set_author(name=self.get_author()) + embed.set_footer(text=self.get_footer()) + + embed.description = self.get_description() + + # Add links if there are any + links = self.get_online_links() + if links: + embed.add_field(name="Online links", value=links, inline=False) + + # Add extras if there are any + extras = self.get_extras() + if extras: + embed.add_field(name="Extra", value=extras, inline=False) + + return embed + + +@dataclass +class HolidayEmbed(LesEmbed): + """ + Class for a Les embed sent during holidays + """ + def get_description(self) -> str: + date = self.schedule.current_holiday.end_date_parsed.strftime("%d/%m/%Y") + return f"Het is momenteel **vakantie** tot en met **{date}**." + + +@dataclass +class NoClassEmbed(LesEmbed): + """ + Class for a Les embed when all classes are canceled or there are none at all + """ + slots: List[Timeslot] + + def get_description(self) -> str: + return "Geen les" + + def get_extras(self) -> str: + canceled = list(filter(lambda x: x.canceled, self.slots)) + if not canceled: + return "" + + return "\n".join(list(entry.get_special_fmt_str() for entry in canceled)) + + +@dataclass +class ScheduleEmbed(LesEmbed): + """ + Class for a successful schedule + """ + slots: List[Timeslot] + slots_not_canceled: List[Timeslot] + + def get_description(self) -> str: + return "\n".join(list(f"{entry}" for entry in self.slots_not_canceled)) + + def get_extras(self) -> str: + special = list(filter(lambda x: x.is_special or x.canceled, self.slots)) + + if not special: + return "" + + return "\n".join(list(entry.get_special_fmt_str() for entry in special)) + + def get_online_links(self) -> str: + has_link = list(filter(lambda x: x.online_link is not None, self.slots)) + + if not has_link: + return "" + + return "\n".join(list(f"{entry.course}: **{entry.get_link_str()}**" for entry in has_link)) diff --git a/enums/platform.py b/enums/platform.py index b9a9520..436309f 100644 --- a/enums/platform.py +++ b/enums/platform.py @@ -8,8 +8,10 @@ class Platform(Enum): Name: The name of the platform Rep: A shorter, lowercased & space-less version """ - Bongo = {"name": "Bongo Virtual Classroom", "rep": "bongo"} + BongoVC = {"name": "Bongo Virtual Classroom", "rep": "bongo"} + GatherTown = {"name": "Gather Town", "rep": "gather"} MSTeams = {"name": "MS Teams", "rep": "msteams"} + OpenCast = {"name": "OpenCast", "rep": "opencast"} Ufora = {"name": "Ufora", "rep": "ufora"} Zoom = {"name": "Zoom", "rep": "zoom"} diff --git a/files/schedules/31.json b/files/schedules/31.json index a11e805..f40f33d 100644 --- a/files/schedules/31.json +++ b/files/schedules/31.json @@ -3,8 +3,8 @@ "semester_end": [16, 8, 2021], "holidays": [ { - "start_date": [2, 7, 2021], - "end_date": [10, 8, 2021] + "start_date": [2, 7, 2021, 23, 59, 59], + "end_date": [10, 8, 2021, 23, 59, 59] } ], "schedule": [ @@ -110,7 +110,7 @@ "slots": [ { "weeks": { - "1": { + "6": { "canceled": true } }, diff --git a/functions/timeFormatters.py b/functions/timeFormatters.py index 3d49476..4460156 100644 --- a/functions/timeFormatters.py +++ b/functions/timeFormatters.py @@ -165,6 +165,13 @@ def fromArray(data: List[int]) -> datetime: month = stringFormatters.leadingZero(str(data[1])) year = str(data[2]) + if len(data) == 6: + hour = stringFormatters.leadingZero(str(data[3])) + minute = stringFormatters.leadingZero(str(data[4])) + second = stringFormatters.leadingZero(str(data[5])) + + return fromString(f"{day}/{month}/{year} {hour}:{minute}:{second}", formatString="%d/%m/%Y %H:%M:%S") + return fromString(f"{day}/{month}/{year}") diff --git a/tests/test_data/test_schedule.py b/tests/test_data/test_schedule.py new file mode 100644 index 0000000..5d7d235 --- /dev/null +++ b/tests/test_data/test_schedule.py @@ -0,0 +1,5 @@ +import unittest + + +class TestSchedule(unittest.TestCase): + pass From 9cebb8280ee27115b48cb8f339a662b939bc109b Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 18:14:56 +0200 Subject: [PATCH 11/21] Special online links --- data/schedule.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/data/schedule.py b/data/schedule.py index c298b27..1ff0a38 100644 --- a/data/schedule.py +++ b/data/schedule.py @@ -113,7 +113,13 @@ class Timeslot: # Find platform & link if this class is online online_platform: Platform = get_platform(slot_dict.get("online", None)) - online_link = course_dict["online_links"][online_platform.value["rep"]] if online_platform is not None else None + + # Custom online link for this day if it exists, else the general link for this platform + online_link = \ + slot_dict["online_link"] if "online_link" in slot_dict else \ + course_dict["online_links"][online_platform.value["rep"]] \ + if online_platform is not None \ + else None return Timeslot(course=course, start_time=start_time, end_time=end_time, canceled="canceled" in slot_dict, is_special=special, location=location, online_platform=online_platform, online_link=online_link) From 8c6d3682b45e4d45bb0fc47ad4944574865762a3 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 18:24:11 +0200 Subject: [PATCH 12/21] Remove old les code, fix food --- cogs/school.py | 50 +----- functions/eten.py | 2 +- functions/les.py | 357 +++------------------------------------- functions/les_rework.py | 31 ---- 4 files changed, 30 insertions(+), 410 deletions(-) delete mode 100644 functions/les_rework.py diff --git a/cogs/school.py b/cogs/school.py index 7d7ce9c..dca3555 100644 --- a/cogs/school.py +++ b/cogs/school.py @@ -1,12 +1,11 @@ -import random - from data import constants, schedule from decorators import help import discord from discord.ext import commands from enums.courses import years from enums.help_categories import Category -from functions import checks, config, eten, les, les_rework +from functions import checks, config, eten, les +from functions.timeFormatters import intToWeekday import json @@ -23,7 +22,8 @@ class School(commands.Cog): # @commands.check(checks.allowedChannels) @help.Category(category=Category.School) async def eten(self, ctx, *day): - day = les.getWeekDay(None if len(day) == 0 else day)[1] + day_dt = les.find_target_date(day if day else None) + day = intToWeekday(day_dt.weekday()) # Create embed menu = eten.etenScript(day) @@ -45,49 +45,9 @@ class School(commands.Cog): # @commands.check(checks.allowedChannels) @help.Category(category=Category.School) async def les(self, ctx, day=None): - date = les_rework.find_target_date(day) + date = les.find_target_date(day) s = schedule.Schedule(date, int(config.get("year")), int(config.get("semester")), day is not None) return await ctx.send(embed=s.create_schedule().to_embed()) - # parsed = les.parseArgs(day) - # - # # Invalid arguments - # if not parsed[0]: - # return await ctx.send(parsed[1]) - # - # day, dayDatetime, semester, year = parsed[1:] - # - # # Customize the user's schedule - # schedule = self.customizeSchedule(ctx, year, semester) - # - # # Create the embed - # embed = les.createEmbed(day, dayDatetime, semester, year, schedule) - # - # await ctx.send(embed=embed) - - # Add all the user's courses - def customizeSchedule(self, ctx, year, semester): - schedule = les.getSchedule(semester, year) - - COC = self.client.get_guild(int(constants.CallOfCode)) - - if COC is None: - return schedule - - member = COC.get_member(ctx.author.id) - - for role in member.roles: - for univYear in years: - for course in univYear: - if course.value["year"] < year and course.value["id"] == role.id and course.value["semester"] == semester: - with open("files/schedules/{}{}.json".format(course.value["year"], course.value["semester"]), - "r") as fp: - sched2 = json.load(fp) - - for val in sched2: - if val["course"] == course.value["name"]: - val["custom"] = course.value["year"] - schedule.append(val) - return schedule @commands.command(name="Pin", usage="[Message]") @help.Category(category=Category.School) diff --git a/functions/eten.py b/functions/eten.py index efba819..e95d2d2 100644 --- a/functions/eten.py +++ b/functions/eten.py @@ -26,7 +26,7 @@ def etenScript(weekDag): # Fetch from API try: - menu = requests.get("https://zeus.ugent.be/hydra/api/2.0/resto/menu/nl/{}/{}/{}.json".format(d.year, d.month, d.day)).json() + menu = requests.get("https://zeus.ugent.be/hydra/api/2.0/resto/menu/nl-sterre/{}/{}/{}.json".format(d.year, d.month, d.day)).json() # Print menu for s in menu["meals"]: diff --git a/functions/les.py b/functions/les.py index 822491e..3c96154 100644 --- a/functions/les.py +++ b/functions/les.py @@ -1,340 +1,31 @@ -import datetime -import discord -from functions import config, timeFormatters, stringFormatters -from functions.numbers import clamp -import json +from datetime import datetime, timedelta +from functions.timeFormatters import dateTimeNow, weekdayToInt, forward_to_weekday, skip_weekends +from typing import Optional -# TODO use constants & enums instead of hardcoding platform names -# also make the naming in the jsons more consistent - -def createCourseString(courses): - courseString = "" - for course in sorted(courses, key=lambda item: item["slot"]["time"][1]): - # Add a ":" to the hour + add a leading "0" if needed - start = timeFormatters.timeFromInt(course["slot"]["time"][1]) - end = timeFormatters.timeFromInt(course["slot"]["time"][2]) - courseString += "{} - {}: {} {}\n".format(start, end, - str(course["course"]), getLocation(course["slot"])) - return courseString - - -def createEmbed(day, dayDatetime, semester, year, schedule): - # Create a date object to check the current week - startDate = 1612224000 - currentTime = dayDatetime.timestamp() - - # TODO don't clamp because week 1 is calculated as week 0!! - week = clamp(timeFormatters.timeIn(currentTime - startDate, "weeks")[0], 1, 13) - - # Compensate for easter holidays - # Sorry but I don't have time to make a clean solution for this rn - # this will have to do - # Does -1 instead of -2 because weeks were 0-indexed all along - week -= 1 - - title, week = getTitle(day, dayDatetime, week) - - # Add all courses & their corresponding times + locations of today - courses, extras, prev, online = getCourses(schedule, day, week) - - embed = discord.Embed(colour=discord.Colour.blue(), title=title) - embed.set_author(name="Lessenrooster voor {}{} Bachelor".format(year, "ste" if year == 1 else "de")) - - if len(courses) == 0: - embed.add_field(name="Geen Les", value="Geen Les", inline=False) - else: - courseString = createCourseString(courses) - # TODO uncomment this when covid rules slow down - # courseString += "\nGroep {} heeft vandaag online les.".format(1 if week % 2 == 0 else 2) - embed.description = courseString - - if prev: - embed.add_field(name="Vakken uit vorige jaren", value=createCourseString(prev), inline=False) - - if extras: - embed.add_field(name="Extra", value="\n".join(getExtras(extra) for extra in extras), inline=False) - - # Add online links - temporarily removed because everything is online right now - if online: - uniqueLinks: dict = getUniqueLinks(online) - embed.add_field(name="Online Links", value="\n".join( - sorted(getLinks(onlineClass, links) for onlineClass, links in uniqueLinks.items()))) - - embed.set_footer(text="Semester {} | Lesweek {}".format(semester, round(week))) - return embed - - -def findDate(targetWeekday): +def find_target_date(arg: Optional[str]) -> datetime: """ - Function that finds the datetime object that corresponds to - the next occurence of [targetWeekday]. - :param targetWeekday: The weekday to find + Find the requested date out of the user's arguments """ - now = timeFormatters.dateTimeNow() - while now.weekday() != targetWeekday: - now = now + datetime.timedelta(days=1) - return now + # Start at current date + day: datetime = dateTimeNow() + # If no offset was provided, check the time + # otherwise the argument overrides it + if arg is None: + # When the command is used after 6 pm, show schedule + # for the next day instead + if day.hour > 18: + day += timedelta(days=1) + elif 0 <= (weekday := weekdayToInt(arg)) <= 4: # Weekday provided + day = forward_to_weekday(day, weekday) + elif arg.lower() == "morgen": # Tomorrow's schedule + day += timedelta(days=1) + elif arg.lower() == "overmorgen": # Day after tomorrow's schedule + day += timedelta(days=2) -def getCourses(schedule, day, week): - """ - Function that creates a list of all courses of this day, - a list of all online links, and extra information for these courses. - :param schedule: A user's (customized) schedule - :param day: The current weekday - :param week: The current week - """ - # Add all courses & their corresponding times + locations of today - courses = [] - extras = [] - prev = [] - onlineLinks = [] + # TODO show a different embed when "(over)morgen" is requested & it lands on a weekend + # Don't land on a weekend + day = skip_weekends(day) - for course in schedule: - for slot in course["slots"]: - if day in slot["time"]: - # Basic dict containing the course name & the class' time slot - classDic = {"course": course["course"], "slot": slot} - - # Class was canceled - if "canceled" in slot and "weeks" in slot and week in slot["weeks"]: - extras.append(classDic) - continue - - # Add online links for those at home - # Check if link hasn't been added yet - if "online" in slot and not any(el["course"] == course["course"] and - # Avoid KeyErrors: if either of these don't have an online link yet, - # add it as well - ("online" not in el or el["online"] == slot["online"]) - for el in onlineLinks): - # Some courses have multiple links on the same day, - # add all of them - if "bongo" in slot["online"].lower(): - onlineDic = {"course": course["course"], "online": "Bongo Virtual Classroom", - "link": course["bongo"]} - onlineLinks.append(onlineDic) - - if "zoom" in slot["online"].lower(): - onlineDic = {"course": course["course"], "online": "ZOOM", "link": course["zoom"]} - onlineLinks.append(onlineDic) - - if "teams" in slot["online"].lower(): - onlineDic = {"course": course["course"], "online": "MS Teams", "link": course["msteams"]} - onlineLinks.append(onlineDic) - - # Add this class' bongo, msteams & zoom links - if "bongo" in course: - classDic["slot"]["bongo"] = course["bongo"] - - if "msteams" in course: - classDic["slot"]["msteams"] = course["msteams"] - - if "zoom" in course: - classDic["slot"]["zoom"] = course["zoom"] - - if "custom" in course: - prev.append(classDic) - - # Check for special classes - if "weeks" in slot and "online" not in slot: - if week in slot["weeks"]: - if "custom" not in course: - courses.append(classDic) - extras.append(classDic) - elif "weeks" in slot and "online" in slot and "group" not in slot: - # This class is only online for this week - if week in slot["weeks"]: - if "custom" not in course: - courses.append(classDic) - extras.append(classDic) - else: - # Nothing special happening, just add it to the list of courses - # in case this is a course for everyone in this year - if "custom" not in course: - courses.append(classDic) - - # Filter out normal courses that are replaced with special courses - for extra in extras: - for course in courses: - if course["slot"]["time"] == extra["slot"]["time"] and course != extra: - courses.remove(course) - break - - # Sort online links alphabetically - onlineLinks.sort(key=lambda x: x["course"]) - - # Remove links of canceled classes - for element in onlineLinks: - if not any(c["course"] == element["course"] for c in courses): - onlineLinks.remove(element) - - return courses, extras, prev, onlineLinks - - -def getExtras(extra): - """ - Function that returns a formatted string giving clear info - when a course is happening somewhere else (or canceled). - """ - start = timeFormatters.timeFromInt(extra["slot"]["time"][1]) - end = timeFormatters.timeFromInt(extra["slot"]["time"][2]) - - location = getLocation(extra["slot"]) - - if "canceled" in extra["slot"]: - return "De les **{}** van **{}** tot **{}** gaat vandaag uitzonderlijk **niet** door.".format( - extra["course"], start, end - ) - - if "group" in extra["slot"]: - return "**Groep {}** heeft vandaag uitzonderlijk **{}** **{}** van **{} tot {}**.".format( - extra["slot"]["group"], extra["course"], location, - start, end - ) - elif "online" in extra["slot"]: - return "**{}** gaat vandaag uitzonderlijk **online** door {} van **{} tot {}**.".format( - extra["course"], location[7:], - start, end - ) - else: - return "**{}** vindt vandaag uitzonderlijk plaats **{}** van **{} tot {}**.".format( - extra["course"], location, - start, end - ) - - -def getUniqueLinks(onlineClasses): - """ - Function that returns a dict of all online unique online links for every class - in case some classes have multiple links on the same day. - """ - # Create a list of all unique course names - courseNames = list(set(oc["course"] for oc in onlineClasses)) - uniqueLinks: dict = {} - - # Add every link of every class into the dict - for name in courseNames: - uniqueLinks[name] = {} - for oc in onlineClasses: - if oc["course"] == name: - # Add the link for this platform - uniqueLinks[name][oc["online"]] = oc["link"] - - return uniqueLinks - - -def getLinks(onlineClass, links): - """ - Function that returns a formatted string giving a hyperlink - to every online link for this class today. - """ - return "{}: {}".format(onlineClass, - " | ".join( - ["**[{}]({})**".format(platform, url) for platform, url in - links.items()]) - ) - - -def getLocation(slot): - """ - Function that returns a formatted string indicating where this course - is happening. - """ - if "canceled" in slot: - return None - - # TODO fix this because it's ugly - if "online" in slot: - return "online @ **[{}]({})**".format(slot["online"], - slot["zoom"] if slot["online"] == "ZOOM" else slot["msteams"] if slot[ - "online"] == "MS Teams" else - slot["bongo"]) - - # Check for courses in multiple locations - if "locations" in slot: - # Language - 'en' for the last one - return ", ".join(getLocation(location) for location in slot["locations"][:-1]) \ - + " en " + getLocation(slot["locations"][-1]) - return "in {} {} {}".format(slot["campus"], slot["building"], slot["room"]) - - -def getSchedule(semester, year): - with open("files/schedules/{}{}.json".format(year, semester), "r") as fp: - schedule = json.load(fp) - - return schedule - - -def getTitle(day, dayDT, week): - # now = timeFormatters.dateTimeNow() - # if timeFormatters.weekdayToInt(day) < now.weekday(): - # week += 1 - - day = day[0].upper() + day[1:].lower() - - titleString = "{} {}/{}/{}".format(day, stringFormatters.leadingZero(dayDT.day), - stringFormatters.leadingZero(dayDT.month), dayDT.year) - return titleString, week - - -# Returns the day of the week, while keeping track of weekends -def getWeekDay(day=None): - weekDays = ["maandag", "dinsdag", "woensdag", "donderdag", "vrijdag"] - - # Get current day of the week - dayNumber = datetime.datetime.today().weekday() - # If a day or a modifier was passed, show that day instead - if day is not None: - if day[0] == "morgen": - dayNumber += 1 - elif day[0] == "overmorgen": - dayNumber += 2 - else: - for i in range(5): - if weekDays[i].startswith(day): - dayNumber = i - # Weekends should be skipped - dayNumber = dayNumber % 7 - if dayNumber > 4: - dayNumber = 0 - - # Get daystring - return dayNumber, weekDays[dayNumber] - - -def parseArgs(day): - semester = int(config.get("semester")) - year = int(config.get("year")) - years_counter = int(config.get("years")) - # Check if a schedule or a day was called - if len(day) == 0: - day = [] - else: - # Only either of them was passed - if len(day) == 1: - # Called a schedule - if day[0].isdigit(): - if 0 < int(day[0]) < years_counter + 1: - year = int(day[0]) - day = [] - else: - return [False, "Dit is geen geldige jaargang."] - # elif: calling a weekday is automatically handled below, - # so checking is obsolete - else: - # TODO check other direction (di 1) in else - # Both were passed - if day[0].isdigit(): - if 0 < int(day[0]) < years_counter + 1: - year = int(day[0]) - # day = [] - else: - return [False, "Dit is geen geldige jaargang."] - # Cut the schedule from the string - day = day[1:] - day = getWeekDay(None if len(day) == 0 else day)[1] - dayDatetime = findDate(timeFormatters.weekdayToInt(day)) - - return [True, day, dayDatetime, semester, year] + return day diff --git a/functions/les_rework.py b/functions/les_rework.py deleted file mode 100644 index 3c96154..0000000 --- a/functions/les_rework.py +++ /dev/null @@ -1,31 +0,0 @@ -from datetime import datetime, timedelta -from functions.timeFormatters import dateTimeNow, weekdayToInt, forward_to_weekday, skip_weekends -from typing import Optional - - -def find_target_date(arg: Optional[str]) -> datetime: - """ - Find the requested date out of the user's arguments - """ - # Start at current date - day: datetime = dateTimeNow() - - # If no offset was provided, check the time - # otherwise the argument overrides it - if arg is None: - # When the command is used after 6 pm, show schedule - # for the next day instead - if day.hour > 18: - day += timedelta(days=1) - elif 0 <= (weekday := weekdayToInt(arg)) <= 4: # Weekday provided - day = forward_to_weekday(day, weekday) - elif arg.lower() == "morgen": # Tomorrow's schedule - day += timedelta(days=1) - elif arg.lower() == "overmorgen": # Day after tomorrow's schedule - day += timedelta(days=2) - - # TODO show a different embed when "(over)morgen" is requested & it lands on a weekend - # Don't land on a weekend - day = skip_weekends(day) - - return day From a198a831531aba367bc2c292dc208daa7ae16fd4 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 18:24:53 +0200 Subject: [PATCH 13/21] Clean up imports --- cogs/school.py | 6 ++---- functions/les.py | 31 ------------------------------- 2 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 functions/les.py diff --git a/cogs/school.py b/cogs/school.py index dca3555..3768a74 100644 --- a/cogs/school.py +++ b/cogs/school.py @@ -1,12 +1,10 @@ -from data import constants, schedule +from data import schedule from decorators import help import discord from discord.ext import commands -from enums.courses import years from enums.help_categories import Category -from functions import checks, config, eten, les +from functions import config, eten, les from functions.timeFormatters import intToWeekday -import json class School(commands.Cog): diff --git a/functions/les.py b/functions/les.py deleted file mode 100644 index 3c96154..0000000 --- a/functions/les.py +++ /dev/null @@ -1,31 +0,0 @@ -from datetime import datetime, timedelta -from functions.timeFormatters import dateTimeNow, weekdayToInt, forward_to_weekday, skip_weekends -from typing import Optional - - -def find_target_date(arg: Optional[str]) -> datetime: - """ - Find the requested date out of the user's arguments - """ - # Start at current date - day: datetime = dateTimeNow() - - # If no offset was provided, check the time - # otherwise the argument overrides it - if arg is None: - # When the command is used after 6 pm, show schedule - # for the next day instead - if day.hour > 18: - day += timedelta(days=1) - elif 0 <= (weekday := weekdayToInt(arg)) <= 4: # Weekday provided - day = forward_to_weekday(day, weekday) - elif arg.lower() == "morgen": # Tomorrow's schedule - day += timedelta(days=1) - elif arg.lower() == "overmorgen": # Day after tomorrow's schedule - day += timedelta(days=2) - - # TODO show a different embed when "(over)morgen" is requested & it lands on a weekend - # Don't land on a weekend - day = skip_weekends(day) - - return day From e9ea063876fc3734939af4c1f365110e61fa18e2 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 18:33:32 +0200 Subject: [PATCH 14/21] Show custom embed for weekends --- cogs/school.py | 11 ++++++++++- functions/les.py | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 functions/les.py diff --git a/cogs/school.py b/cogs/school.py index 3768a74..b22482d 100644 --- a/cogs/school.py +++ b/cogs/school.py @@ -4,7 +4,8 @@ import discord from discord.ext import commands from enums.help_categories import Category from functions import config, eten, les -from functions.timeFormatters import intToWeekday +from functions.stringFormatters import capitalize +from functions.timeFormatters import intToWeekday, skip_weekends class School(commands.Cog): @@ -21,6 +22,7 @@ class School(commands.Cog): @help.Category(category=Category.School) async def eten(self, ctx, *day): day_dt = les.find_target_date(day if day else None) + day_dt = skip_weekends(day_dt) day = intToWeekday(day_dt.weekday()) # Create embed @@ -44,6 +46,13 @@ class School(commands.Cog): @help.Category(category=Category.School) async def les(self, ctx, day=None): date = les.find_target_date(day) + + # Person explicitly requested a weekend-day + if day is not None and day.lower() in ("morgen", "overmorgen") and date.weekday() > 4: + return await ctx.send(f"{capitalize(day)} is het weekend.") + + date = skip_weekends(date) + s = schedule.Schedule(date, int(config.get("year")), int(config.get("semester")), day is not None) return await ctx.send(embed=s.create_schedule().to_embed()) diff --git a/functions/les.py b/functions/les.py new file mode 100644 index 0000000..c3a7cfb --- /dev/null +++ b/functions/les.py @@ -0,0 +1,27 @@ +from datetime import datetime, timedelta +from functions.timeFormatters import dateTimeNow, weekdayToInt, forward_to_weekday +from typing import Optional + + +def find_target_date(arg: Optional[str]) -> datetime: + """ + Find the requested date out of the user's arguments + """ + # Start at current date + day: datetime = dateTimeNow() + + # If no offset was provided, check the time + # otherwise the argument overrides it + if arg is None: + # When the command is used after 6 pm, show schedule + # for the next day instead + if day.hour > 18: + day += timedelta(days=1) + elif 0 <= (weekday := weekdayToInt(arg)) <= 4: # Weekday provided + day = forward_to_weekday(day, weekday) + elif arg.lower() == "morgen": # Tomorrow's schedule + day += timedelta(days=1) + elif arg.lower() == "overmorgen": # Day after tomorrow's schedule + day += timedelta(days=2) + + return day From 85f29e7afad56f477650fcdee16a0c9e431e292f Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 19:32:41 +0200 Subject: [PATCH 15/21] Check for semester end --- cogs/school.py | 4 ++++ files/schedules/31.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cogs/school.py b/cogs/school.py index b22482d..8f52d6e 100644 --- a/cogs/school.py +++ b/cogs/school.py @@ -54,6 +54,10 @@ class School(commands.Cog): date = skip_weekends(date) s = schedule.Schedule(date, int(config.get("year")), int(config.get("semester")), day is not None) + + if s.semester_over: + return await ctx.send("Het semester is afgelopen.") + return await ctx.send(embed=s.create_schedule().to_embed()) @commands.command(name="Pin", usage="[Message]") diff --git a/files/schedules/31.json b/files/schedules/31.json index f40f33d..8edbf90 100644 --- a/files/schedules/31.json +++ b/files/schedules/31.json @@ -1,6 +1,6 @@ { "semester_start": [1, 7, 2021], - "semester_end": [16, 8, 2021], + "semester_end": [1, 8, 2021], "holidays": [ { "start_date": [2, 7, 2021, 23, 59, 59], From 445ca84834e4f31e937c2411acb6e2b4a629b25f Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 20:26:53 +0200 Subject: [PATCH 16/21] Write some tests, fix imports --- data/schedule.py | 5 ++--- tests/test_data/test_schedule.py | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/data/schedule.py b/data/schedule.py index 1ff0a38..68f6fca 100644 --- a/data/schedule.py +++ b/data/schedule.py @@ -1,9 +1,8 @@ from abc import ABC, abstractmethod -import dacite -from discord import Colour, Embed from dacite import from_dict from dataclasses import dataclass, field from datetime import datetime, timedelta +from discord import Colour, Embed from enums.platform import Platform, get_platform from functions.timeFormatters import fromArray, intToWeekday, timeFromInt import json @@ -109,7 +108,7 @@ class Timeslot: end_time = slot_dict["time"]["end"] # Location can be none if a class is online-only - location = dacite.from_dict(Location, slot_dict["location"]) if "location" in slot_dict else None + location = from_dict(Location, slot_dict["location"]) if "location" in slot_dict else None # Find platform & link if this class is online online_platform: Platform = get_platform(slot_dict.get("online", None)) diff --git a/tests/test_data/test_schedule.py b/tests/test_data/test_schedule.py index 5d7d235..22916cc 100644 --- a/tests/test_data/test_schedule.py +++ b/tests/test_data/test_schedule.py @@ -1,5 +1,27 @@ +import pytz + +from data import schedule +from datetime import datetime import unittest class TestSchedule(unittest.TestCase): - pass + def test_holiday_has_passed(self): + tz = pytz.timezone("Europe/Brussels") + before = datetime(2020, 8, 8, tzinfo=tz) + during = datetime(2021, 6, 2, tzinfo=tz) + after = datetime(2021, 8, 8, tzinfo=tz) + + holiday = schedule.Holiday([1, 6, 2021], [2, 7, 2021]) + + self.assertFalse(holiday.has_passed(before)) + self.assertFalse(holiday.has_passed(during)) + self.assertTrue(holiday.has_passed(after)) + + def test_course_str(self): + course = schedule.Course("Test") + self.assertEqual(str(course), "Test") + + def test_location_str(self): + location = schedule.Location("C", "B", "R") + self.assertEqual(str(location), "C B R") From 76df6561281fdfe89e790a04aced49c8f728ef18 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 20:40:50 +0200 Subject: [PATCH 17/21] run tests on commit --- .github/workflows/test.yml | 15 +++++++++++++++ requirements.txt | 3 ++- tests/{test_data => }/__init__.py | 0 tests/data/__init__.py | 0 tests/{test_data => data}/test_regexes.py | 0 tests/{test_data => data}/test_schedule.py | 11 +---------- tests/{test_data => data}/test_snipe.py | 0 tests/run_tests.py | 6 ------ 8 files changed, 18 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/test.yml rename tests/{test_data => }/__init__.py (100%) create mode 100644 tests/data/__init__.py rename tests/{test_data => data}/test_regexes.py (100%) rename tests/{test_data => data}/test_schedule.py (69%) rename tests/{test_data => data}/test_snipe.py (100%) delete mode 100644 tests/run_tests.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..147dfbc --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,15 @@ +name: Deploy + +on: + push: + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.9.5' + - run: pip3 install -r requirements.txt + - run: pytest tests \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 083a9f5..dc9dcba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,4 +16,5 @@ googletrans==4.0.0rc1 quart==0.15.1 Quart-CORS==0.5.0 attrs~=21.2.0 -dacite~=1.6.0 \ No newline at end of file +dacite~=1.6.0 +pytest==6.2.4 \ No newline at end of file diff --git a/tests/test_data/__init__.py b/tests/__init__.py similarity index 100% rename from tests/test_data/__init__.py rename to tests/__init__.py diff --git a/tests/data/__init__.py b/tests/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_data/test_regexes.py b/tests/data/test_regexes.py similarity index 100% rename from tests/test_data/test_regexes.py rename to tests/data/test_regexes.py diff --git a/tests/test_data/test_schedule.py b/tests/data/test_schedule.py similarity index 69% rename from tests/test_data/test_schedule.py rename to tests/data/test_schedule.py index 22916cc..34fbb3e 100644 --- a/tests/test_data/test_schedule.py +++ b/tests/data/test_schedule.py @@ -1,7 +1,6 @@ -import pytz - from data import schedule from datetime import datetime +import pytz import unittest @@ -17,11 +16,3 @@ class TestSchedule(unittest.TestCase): self.assertFalse(holiday.has_passed(before)) self.assertFalse(holiday.has_passed(during)) self.assertTrue(holiday.has_passed(after)) - - def test_course_str(self): - course = schedule.Course("Test") - self.assertEqual(str(course), "Test") - - def test_location_str(self): - location = schedule.Location("C", "B", "R") - self.assertEqual(str(location), "C B R") diff --git a/tests/test_data/test_snipe.py b/tests/data/test_snipe.py similarity index 100% rename from tests/test_data/test_snipe.py rename to tests/data/test_snipe.py diff --git a/tests/run_tests.py b/tests/run_tests.py deleted file mode 100644 index 4b64cea..0000000 --- a/tests/run_tests.py +++ /dev/null @@ -1,6 +0,0 @@ -import unittest - - -if __name__ == "__main__": - suite = unittest.TestLoader().discover('.', pattern="test_*.py") - unittest.TextTestRunner(verbosity=3).run(suite) From a97a35a13b7bed1a94eb0fc2da9b7c3a9e12875a Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 20:43:05 +0200 Subject: [PATCH 18/21] Fix job name --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 147dfbc..eaaa321 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,10 +1,10 @@ -name: Deploy +name: Run Tests on: push: jobs: - deploy: + python: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From aa1e28937c0a85985910fec5649d561def7d9c52 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 22:22:11 +0200 Subject: [PATCH 19/21] Add extra tests for schedules & timeformatters --- data/schedule.py | 10 ++-- tests/data/test_schedule.py | 79 +++++++++++++++++++++++++- tests/functions/__init__.py | 0 tests/functions/test_timeFormatters.py | 64 +++++++++++++++++++++ 4 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 tests/functions/__init__.py create mode 100644 tests/functions/test_timeFormatters.py diff --git a/data/schedule.py b/data/schedule.py index 68f6fca..f286b60 100644 --- a/data/schedule.py +++ b/data/schedule.py @@ -145,14 +145,13 @@ class Schedule: self.end_date = fromArray(self.schedule_dict["semester_end"]) # Semester is over - if self.end_date <= self.day: + if self.end_date < self.day: self.semester_over = True return self.check_holidays() self.week = self.get_week() - # TODO show a custom embed when no class instead of fast-forwarding # # Store the target weekday (in case it exists) so we can ask for the next # # friday after the holiday, for example # target_weekday = -1 if not self.targeted_weekday else self.day.weekday() @@ -181,7 +180,8 @@ class Schedule: # In the past: add the offset if holiday.has_passed(self.day): - self.holiday_offset += (self.day - holiday.end_date_parsed).days // 7 + # Add 1 because Monday-Sunday is only 6 days, but should be counted as a week + self.holiday_offset += (holiday.duration.days + 1) // 7 elif holiday.start_date_parsed <= self.day <= holiday.end_date_parsed: self.current_holiday = holiday @@ -203,7 +203,9 @@ class Schedule: return 1 # Add +1 at the end because week 1 would be 0 as it's not over yet - return (diff.days // 7) + self.holiday_offset + 1 + # Every week would be one behind + # Also subtract all passed holidays + return (diff.days // 7) - self.holiday_offset + 1 def find_slots_for_course(self, course_dict: Dict) -> List[Timeslot]: """ diff --git a/tests/data/test_schedule.py b/tests/data/test_schedule.py index 34fbb3e..5c9da8c 100644 --- a/tests/data/test_schedule.py +++ b/tests/data/test_schedule.py @@ -1,10 +1,12 @@ from data import schedule from datetime import datetime +from enums.platform import Platform import pytz -import unittest +from unittest import TestCase +from unittest.mock import patch -class TestSchedule(unittest.TestCase): +class TestSchedule(TestCase): def test_holiday_has_passed(self): tz = pytz.timezone("Europe/Brussels") before = datetime(2020, 8, 8, tzinfo=tz) @@ -16,3 +18,76 @@ class TestSchedule(unittest.TestCase): self.assertFalse(holiday.has_passed(before)) self.assertFalse(holiday.has_passed(during)) self.assertTrue(holiday.has_passed(after)) + + def test_timeslot_link(self): + slot = schedule.Timeslot(schedule.Course("a"), 1234, 5678) + self.assertEqual(None, slot.get_link_str()) + + slot = schedule.Timeslot(schedule.Course("a"), 1234, 5678, online_link="link", online_platform=Platform.Zoom) + self.assertEqual("[Zoom](link)", slot.get_link_str()) + + @patch("data.schedule.Schedule.check_holidays") + @patch("data.schedule.Schedule.load_schedule_file") + def test_schedule_semester_over(self, mock_load, mock_check_holidays): + mock_load.return_value = {"semester_start": [1, 2, 2020], "semester_end": [4, 5, 2021]} + dt = datetime(2021, 8, 8, tzinfo=pytz.timezone("Europe/Brussels")) + + s = schedule.Schedule(dt, 3, 1) + self.assertTrue(s.semester_over) + + # Check that the code stopped running in case the semester is over + mock_check_holidays.assert_not_called() + + @patch("data.schedule.Schedule.load_schedule_file") + def test_schedule_holidays(self, mock_load): + mock_load.return_value = { + "semester_start": [6, 7, 2021], "semester_end": [20, 8, 2021], + "holidays": [ + {"start_date": [1, 8, 2021], "end_date": [10, 8, 2021]} + ] + } + + # During holiday + dt = datetime(2021, 8, 8, tzinfo=pytz.timezone("Europe/Brussels")) + s = schedule.Schedule(dt, 3, 1) + self.assertNotEqual(None, s.current_holiday) + + # Not during holiday + dt = datetime(2021, 8, 15, tzinfo=pytz.timezone("Europe/Brussels")) + s = schedule.Schedule(dt, 3, 1) + self.assertEqual(None, s.current_holiday) + + @patch("data.schedule.Schedule.load_schedule_file") + def test_schedule_holiday_offset(self, mock_load): + # Week 1, no holidays + mock_load.return_value = { + "semester_start": [2, 8, 2021], "semester_end": [20, 8, 2021] + } + + dt = datetime(2021, 8, 6, tzinfo=pytz.timezone("Europe/Brussels")) + s = schedule.Schedule(dt, 3, 1) + self.assertEqual(1, s.get_week()) + + # Week 1, one off-day doesn't change the week + mock_load.return_value = { + "semester_start": [2, 8, 2021], "semester_end": [20, 8, 2021], + "holidays": [ + {"start_date": [5, 8, 2021], "end_date": [5, 8, 2021]} + ] + } + + s = schedule.Schedule(dt, 3, 1) + self.assertEqual(1, s.get_week()) + + # Week 3, with a one-week holiday in between + mock_load.return_value = { + "semester_start": [2, 8, 2021], "semester_end": [20, 8, 2021], + "holidays": [ + {"start_date": [5, 8, 2021], "end_date": [5, 8, 2021]}, + {"start_date": [9, 8, 2021], "end_date": [15, 8, 2021]} + ] + } + + dt = datetime(2021, 8, 19, tzinfo=pytz.timezone("Europe/Brussels")) + s = schedule.Schedule(dt, 3, 1) + self.assertEqual(2, s.get_week()) diff --git a/tests/functions/__init__.py b/tests/functions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/functions/test_timeFormatters.py b/tests/functions/test_timeFormatters.py new file mode 100644 index 0000000..8b9f5fb --- /dev/null +++ b/tests/functions/test_timeFormatters.py @@ -0,0 +1,64 @@ +from datetime import datetime +from functions import timeFormatters +import unittest + + +class TestTimeFormatters(unittest.TestCase): + def test_leadingZero(self): + self.assertEqual("0123", timeFormatters.leadingZero("123")) + self.assertEqual("0123", timeFormatters.leadingZero("0123")) + + def test_delimiter(self): + self.assertEqual("0123", timeFormatters.delimiter("0123")) + self.assertEqual("01.23", timeFormatters.delimiter("0123", delim=".")) + + def test_timeFromInt(self): + self.assertEqual("01:23", timeFormatters.timeFromInt(123)) + self.assertEqual("12:34", timeFormatters.timeFromInt(1234)) + + def test_fromArray(self): + # Only day/month/year + d, m, y = 1, 2, 2021 + inp = [d, m, y] + + dt = timeFormatters.fromArray(inp) + self.assertEqual(d, dt.day) + self.assertEqual(m, dt.month) + self.assertEqual(y, dt.year) + + # Also hours/minutes/seconds + d, m, y, hh, mm, ss = 1, 2, 2021, 1, 2, 3 + inp = [d, m, y, hh, mm, ss] + + dt = timeFormatters.fromArray(inp) + self.assertEqual(d, dt.day) + self.assertEqual(m, dt.month) + self.assertEqual(y, dt.year) + self.assertEqual(hh, dt.hour) + self.assertEqual(mm, dt.minute) + self.assertEqual(ss, dt.second) + + def test_skipWeekends(self): + # Already a weekday + weekday = datetime(2021, 8, 11) + skipped = timeFormatters.skip_weekends(weekday) + self.assertEqual(weekday, skipped) + + # Weekend + weekend = datetime(2021, 8, 7) + skipped = timeFormatters.skip_weekends(weekend) + self.assertEqual(0, skipped.weekday()) + + def test_forwardToWeekday(self): + mo = datetime(2021, 8, 10) + # Before + forwarded = timeFormatters.forward_to_weekday(mo, 2) + self.assertEqual(1, (forwarded - mo).days) + + # Same day + forwarded = timeFormatters.forward_to_weekday(mo, 1) + self.assertEqual(7, (forwarded - mo).days) + + # After + forwarded = timeFormatters.forward_to_weekday(mo, 0) + self.assertEqual(6, (forwarded - mo).days) From 07e48e404646e2d465e1a37650a4af4763e144c9 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 22:23:23 +0200 Subject: [PATCH 20/21] Rename job --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eaaa321..0c33832 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: push: jobs: - python: + python-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From 41a1527e722a9200a06cb7918c589243d4d89003 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 8 Aug 2021 22:24:05 +0200 Subject: [PATCH 21/21] Fix typo in test --- tests/functions/test_timeFormatters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functions/test_timeFormatters.py b/tests/functions/test_timeFormatters.py index 8b9f5fb..790c013 100644 --- a/tests/functions/test_timeFormatters.py +++ b/tests/functions/test_timeFormatters.py @@ -9,7 +9,7 @@ class TestTimeFormatters(unittest.TestCase): self.assertEqual("0123", timeFormatters.leadingZero("0123")) def test_delimiter(self): - self.assertEqual("0123", timeFormatters.delimiter("0123")) + self.assertEqual("01:23", timeFormatters.delimiter("0123")) self.assertEqual("01.23", timeFormatters.delimiter("0123", delim=".")) def test_timeFromInt(self):