From 3e0e06b38a43af4a38368055af4e20d2a6e52c68 Mon Sep 17 00:00:00 2001 From: 29miaoetlrsdnet <29miaoet@lrsd.net> Date: Tue, 25 Aug 2026 20:05:25 -0500 Subject: [PATCH 01/13] Hardened type safety and error handling --- src/calendar.ts | 52 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/src/calendar.ts b/src/calendar.ts index 323a8e1..96d000e 100644 --- a/src/calendar.ts +++ b/src/calendar.ts @@ -20,11 +20,30 @@ interface CalendarObject { [date: string]: DayInfo; } +interface DayInfoStruct { + daystatus: string; + feature: Array; + event: Array; +} + +type FixedTime = readonly [number, number]; +type SchoolTimeAsDateStruct = [number, number, number, number, number]; + +class CalendarError extends Error { + public readonly statusCode: number; + + constructor(message: string, statusCode: number = 400) { + super(message); + this.name = "CalendarError"; + this.statusCode = statusCode; + } +} + export default class Calendar { public calendar!: CalendarObject; private dbPath: string; - private earlyDismissalTime: Array; - private regularSchoolDayTime: Array; + private earlyDismissalTime: FixedTime; + private regularSchoolDayTime: FixedTime; public now: number; constructor(dbPath: string) { @@ -40,14 +59,25 @@ export default class Calendar { try { const response = await fetch(this.dbPath); if (!response.ok) { - throw new Error("Network response error " + response.statusText); + throw new CalendarError("Network response error" + response.statusText, response.status); } this.calendar = await response.json(); } catch (error) { - console.error("There was a problem with the fetch operation:", error); + throw new CalendarError("Calendar fetch error.", 404); + } + } + + // Check whether calendar has been loaded. + private get requireData(): CalendarObject { + if (!this.calendar) { + throw new CalendarError("No calendar loaded, did you forget to call loadData()?", 404); } + return this.calendar; } + /** + * This method should only be used in debugging and not in production code. + */ getRaw() { console.log(this.calendar); } @@ -79,7 +109,7 @@ export default class Calendar { return absTime; } - getDayInfo(dateStamp: string) { + getDayInfo(dateStamp: string): DayInfoStruct { return { daystatus: this.calendar[dateStamp]["status"], feature: this.calendar[dateStamp]["holidays"], @@ -87,7 +117,7 @@ export default class Calendar { }; } - floorTimestamp(timeUnit: string, timeStamp: number) { + floorTimestamp(timeUnit: string, timeStamp: number): number { const timeObj = new Date(timeStamp); if (timeUnit === "second") { timeObj.setMilliseconds(0); @@ -109,11 +139,11 @@ export default class Calendar { return timeObj.getTime(); } - modTimestamp(timeUnit: string, timeStamp: number) { + modTimestamp(timeUnit: string, timeStamp: number): number { return timeStamp - this.floorTimestamp(timeUnit, timeStamp); } - getPercentCompletion(startingTimeStamp: number, endingTimeStamp: number) { + getPercentCompletion(startingTimeStamp: number, endingTimeStamp: number): number { const timeToElapse: number = endingTimeStamp - startingTimeStamp; // Basic safety test @@ -141,7 +171,7 @@ export default class Calendar { } } - getSchoolTimeTo(timeStamp: number) { + getSchoolTimeTo(timeStamp: number): number { const currentDate: string = this.strftime(this.now); const endingDate: string = this.strftime(timeStamp); const hoursAfterMidnight: number = this.modTimestamp("day", this.now); @@ -249,7 +279,7 @@ export default class Calendar { * only full dates, it should be updated in the * future to support more accurate timing. */ - getSchoolTimeAsDate(endingTimeStamp: number) { + getSchoolTimeAsDate(endingTimeStamp: number): SchoolTimeAsDateStruct { let endDate = new Date(endingTimeStamp); const endingDateObj = this.getDateAt(this.strftime(endingTimeStamp)); @@ -332,7 +362,7 @@ export default class Calendar { return schoolDateRemaining; } - getDateAt(dateStamp: string) { + getDateAt(dateStamp: string): DayInfo { if (!this.calendar[dateStamp]) { throw new RangeError(`Calendar does not contain ${dateStamp}.`); } From 8e1d48fad2770cbaca2dd84d78193f40b017a806 Mon Sep 17 00:00:00 2001 From: 29miaoetlrsdnet <29miaoet@lrsd.net> Date: Wed, 26 Aug 2026 20:28:30 -0500 Subject: [PATCH 02/13] Added more type hints for robustness --- src/calendar.ts | 81 +++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 46 deletions(-) diff --git a/src/calendar.ts b/src/calendar.ts index 96d000e..d7973e0 100644 --- a/src/calendar.ts +++ b/src/calendar.ts @@ -10,8 +10,8 @@ interface DayInfo { date: string; hasSchool: boolean; - timeSlot: string; - status: string; + timeSlot: "Regular" | "Early Dismissal"; + status: "Normal School Day" | "No School" | "Early Dismissal"; holidays: Array; dayInfo: Array; } @@ -28,19 +28,18 @@ interface DayInfoStruct { type FixedTime = readonly [number, number]; type SchoolTimeAsDateStruct = [number, number, number, number, number]; +type TimeUnitType = "day" | "hour" | "minute" | "second"; +type SchoolDateTuple = [number, number, number, number, number]; class CalendarError extends Error { - public readonly statusCode: number; - - constructor(message: string, statusCode: number = 400) { + constructor(message: string) { super(message); this.name = "CalendarError"; - this.statusCode = statusCode; } } export default class Calendar { - public calendar!: CalendarObject; + public _calendar!: CalendarObject; private dbPath: string; private earlyDismissalTime: FixedTime; private regularSchoolDayTime: FixedTime; @@ -59,20 +58,24 @@ export default class Calendar { try { const response = await fetch(this.dbPath); if (!response.ok) { - throw new CalendarError("Network response error" + response.statusText, response.status); + throw new CalendarError("Network response error" + response.statusText); } this.calendar = await response.json(); } catch (error) { - throw new CalendarError("Calendar fetch error.", 404); + throw new CalendarError("Calendar fetch error."); } } // Check whether calendar has been loaded. - private get requireData(): CalendarObject { - if (!this.calendar) { - throw new CalendarError("No calendar loaded, did you forget to call loadData()?", 404); + public get calendar(): CalendarObject { + if (!this._calendar) { + throw new CalendarError("No calendar loaded, did you forget to call loadData()?"); } - return this.calendar; + return this._calendar; + } + + private set calendar(data: CalendarObject) { + this._calendar = data; } /** @@ -82,9 +85,9 @@ export default class Calendar { console.log(this.calendar); } - strftime(timeStamp: number) { + strftime(timeStamp: number): string { const date = new Date(timeStamp); - const year = date.getFullYear(); + const year = String(date.getFullYear()); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; @@ -95,17 +98,17 @@ export default class Calendar { this.now = Date.now(); } - contains(schoolDate: number) { + contains(schoolDate: number): boolean { try { this.getDateAt(this.strftime(schoolDate)); return true; - } catch (RangeError) { + } catch (e) { return false; } } - getAbsoluteTimeTo(timeStamp: number) { - const absTime: number = timeStamp - this.now; + getAbsoluteTimeTo(timeStamp: number): number { + const absTime = timeStamp - this.now; return absTime; } @@ -117,7 +120,7 @@ export default class Calendar { }; } - floorTimestamp(timeUnit: string, timeStamp: number): number { + floorTimestamp(timeUnit: TimeUnitType, timeStamp: number): number { const timeObj = new Date(timeStamp); if (timeUnit === "second") { timeObj.setMilliseconds(0); @@ -134,21 +137,21 @@ export default class Calendar { timeObj.setMinutes(0); timeObj.setHours(0); } else { - throw new TypeError(`Unknown time measurement unit ${timeUnit}`); + throw new CalendarError(`Unknown time measurement unit ${timeUnit}`); } return timeObj.getTime(); } - modTimestamp(timeUnit: string, timeStamp: number): number { + modTimestamp(timeUnit: TimeUnitType, timeStamp: number): number { return timeStamp - this.floorTimestamp(timeUnit, timeStamp); } getPercentCompletion(startingTimeStamp: number, endingTimeStamp: number): number { - const timeToElapse: number = endingTimeStamp - startingTimeStamp; + const timeToElapse = endingTimeStamp - startingTimeStamp; // Basic safety test if (timeToElapse < 0) { - throw new RangeError(`Invalid range ${startingTimeStamp}-${endingTimeStamp}`); + throw new CalendarError(`Invalid range ${startingTimeStamp} - ${endingTimeStamp}`); } if (startingTimeStamp > this.now) return 0; @@ -157,7 +160,7 @@ export default class Calendar { // Borrow the this.now variable to force a full school time calculation. // Bad practice, fix later. const temp = this.now; - const schoolTimeToElapse: number = this.getSchoolTimeTo(endingTimeStamp); + const schoolTimeToElapse = this.getSchoolTimeTo(endingTimeStamp); this.now = temp; if (this.contains(this.now)) { @@ -172,10 +175,10 @@ export default class Calendar { } getSchoolTimeTo(timeStamp: number): number { - const currentDate: string = this.strftime(this.now); - const endingDate: string = this.strftime(timeStamp); - const hoursAfterMidnight: number = this.modTimestamp("day", this.now); - const hoursLastDay: number = this.modTimestamp("day", timeStamp); + const currentDate = this.strftime(this.now); + const endingDate = this.strftime(timeStamp); + const hoursAfterMidnight = this.modTimestamp("day", this.now); + const hoursLastDay = this.modTimestamp("day", timeStamp); let milliSeconds: number = 0; @@ -296,12 +299,11 @@ export default class Calendar { const endingDate = this.strftime(endDate.getTime()); - type schoolDateTuple = [number, number, number, number, number]; - let schoolDateRemaining: schoolDateTuple = [0, 0, 0, 0, 0]; + let schoolDateRemaining: SchoolDateTuple = [0, 0, 0, 0, 0]; let milliseconds: number = 0; - const currentDate: string = this.strftime(this.now); - const hoursAfterMidnight: number = this.modTimestamp("day", this.now); + const currentDate = this.strftime(this.now); + const hoursAfterMidnight = this.modTimestamp("day", this.now); for (const date in this.calendar) { // The current date should not be counted but the ending Date should be @@ -370,16 +372,3 @@ export default class Calendar { } } -/* -async function start() { - const cal = new Calendar("calendar.json", 0, 0); - await cal.loadData(); - const tempdate = new Date(2026, 8, 22, 18, 0); - const tempnow = new Date(2026, 8, 22, 9, 0); - cal.now = tempnow.getTime(); - console.log(cal.getSchoolTimeTo(tempdate.getTime())); - console.log(cal.getAbsoluteTimeTo(tempdate.getTime())); -} - -start(); -*/ From b1010f4bb1daff11c993bf9c135e94161deaec28 Mon Sep 17 00:00:00 2001 From: 29miaoetlrsdnet <29miaoet@lrsd.net> Date: Thu, 27 Aug 2026 20:35:43 -0500 Subject: [PATCH 03/13] Begin migrating on-the-go functions from dom.ts to calendar.ts --- src/calendar.ts | 58 ++++++++++++++++++++++++++++++++++++++++++++++++- src/dom.ts | 36 +++--------------------------- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/src/calendar.ts b/src/calendar.ts index d7973e0..0772a79 100644 --- a/src/calendar.ts +++ b/src/calendar.ts @@ -364,6 +364,63 @@ export default class Calendar { return schoolDateRemaining; } + findNextWeekend(): number { + const currentDate = new Date(this.now); + const currentWeekday = currentDate.getDay(); + const tempEnd = new Date(this.floorTimestamp("day", this.now)) + let endDate: Date; + + if (currentWeekday === 6 || currentWeekday === 0) { + return this.now; + } else { + const daysToAdd = 6 - currentDate.getDay(); + tempEnd.setDate(tempEnd.getDate() + daysToAdd); + } + + // Rollback to previous day + tempEnd.setDate(tempEnd.getDate() - 1); + const stamp = this.strftime(tempEnd.getTime()); + + if (this.calendar[stamp].hasSchool) { + if (this.calendar[stamp].timeSlot === "Regular") { + tempEnd.setMilliseconds(this.regularSchoolDayTime[1]); + } else if (this.calendar[stamp].timeSlot === "Early Dismissal") { + tempEnd.setMilliseconds(this.earlyDismissalTime[1]); + } + } else { + tempEnd.setHours(24); + } + return tempEnd.getTime(); + } + + getDayOfTheWeek(date: DayInfo): number { + const d = new Date(date.date); + return d.getDay(); + } + + findNextLongWeekend() { + const day = Object.values(this.calendar).find((day, index, array) => { + const first = array[index] + const second = array[index + 1] + const third = array[index + 2] + if (!second || !third) return false; + const schoolNotExists = !first.hasSchool && !second?.hasSchool && !third?.hasSchool; + const onWeekend = + ((this.getDayOfTheWeek(first) === 6) && (this.getDayOfTheWeek(second) === 0)) || + ((this.getDayOfTheWeek(second) === 6) && (this.getDayOfTheWeek(third) === 0)); + + const dateNow = new Date(this.now) + const dateCandidate = new Date(first.date) + + const inTheFuture = dateCandidate > dateNow; + + return schoolNotExists && onWeekend && inTheFuture; + }); + console.log(`Long weekend is ${day}`) + console.log(day) + throw new CalendarError("Add functionality and merge for findNextLongWeekend, `day` object contains found day, decrease by 1") + } + getDateAt(dateStamp: string): DayInfo { if (!this.calendar[dateStamp]) { throw new RangeError(`Calendar does not contain ${dateStamp}.`); @@ -371,4 +428,3 @@ export default class Calendar { return this.calendar[dateStamp]; } } - diff --git a/src/dom.ts b/src/dom.ts index 647ff54..8426625 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -154,11 +154,12 @@ function getPreferredDates(value: string) { causeOfDeath = "No School Right Now"; break; case "weekend": - findNextWeekend(); + endDate = new Date(calendar.findNextWeekend()); causeOfDeath = "Weekend"; break; case "lweekend": findNextLongWeekend(); + calendar.findNextLongWeekend(); causeOfDeath = "Long Weekend"; break; case "term": @@ -326,38 +327,6 @@ function findEndTerm() { endDate = new Date(calendar.now); } -function findNextWeekend() { - const currentDate = new Date(calendar.now); - const currentWeekday = currentDate.getDay(); - const tempEnd = new Date( - currentDate.getFullYear(), - currentDate.getMonth(), - currentDate.getDate() - ); - - if (currentWeekday === 6 || currentWeekday === 0) { - endDate = new Date(calendar.now); - } else { - const daysToAdd = 6 - currentDate.getDay(); - tempEnd.setDate(tempEnd.getDate() + daysToAdd); - } - - // Rollback to previous day - tempEnd.setDate(tempEnd.getDate() - 1); - const stamp: string = calendar.strftime(tempEnd.getTime()); - - if (calendar.calendar[stamp].hasSchool) { - if (calendar.calendar[stamp].timeSlot === "Regular") { - tempEnd.setHours(15, 40); - } else if (calendar.calendar[stamp].timeSlot === "Early Dismissal") { - tempEnd.setHours(14, 30); - } - } else { - tempEnd.setHours(24); - } - - endDate = tempEnd; -} function findNextLongWeekend() { for (const day in calendar.calendar) { @@ -388,6 +357,7 @@ function findNextLongWeekend() { (next3Weekdays[1] === 6 && next3Weekdays[2] === 0)) ) { const previousDay = new Date(day); + console.log(previousDay) previousDay.setTime(previousDay.getTime() - 24 * 60 * 60 * 1000); const previousDayStamp = calendar.strftime(previousDay.getTime()); From dad802a8d430467fc003963469165db287364770 Mon Sep 17 00:00:00 2001 From: 29miaoetlrsdnet <29miaoet@lrsd.net> Date: Fri, 28 Aug 2026 14:53:10 -0500 Subject: [PATCH 04/13] Migrated complex functions over to calendar.ts and decomposed them into smaller ones --- src/calendar.ts | 89 ++++++++++++++++++++++++++++++++++++---------- src/dom.ts | 93 ++----------------------------------------------- 2 files changed, 73 insertions(+), 109 deletions(-) diff --git a/src/calendar.ts b/src/calendar.ts index 0772a79..08916cd 100644 --- a/src/calendar.ts +++ b/src/calendar.ts @@ -377,20 +377,7 @@ export default class Calendar { tempEnd.setDate(tempEnd.getDate() + daysToAdd); } - // Rollback to previous day - tempEnd.setDate(tempEnd.getDate() - 1); - const stamp = this.strftime(tempEnd.getTime()); - - if (this.calendar[stamp].hasSchool) { - if (this.calendar[stamp].timeSlot === "Regular") { - tempEnd.setMilliseconds(this.regularSchoolDayTime[1]); - } else if (this.calendar[stamp].timeSlot === "Early Dismissal") { - tempEnd.setMilliseconds(this.earlyDismissalTime[1]); - } - } else { - tempEnd.setHours(24); - } - return tempEnd.getTime(); + return this.schoolTimeify(tempEnd).getTime(); } getDayOfTheWeek(date: DayInfo): number { @@ -398,7 +385,30 @@ export default class Calendar { return d.getDay(); } - findNextLongWeekend() { + /** + * This function must be called with an index to check first + * as a parameter, preferably -1. + */ + getLastDay(indexToCheckFirst: number): number { + const day = Object.values(this.calendar).at(indexToCheckFirst); + if (!day) { + throw new CalendarError("Could not find last school day.") + } + + if (day.hasSchool) { + const foundDate = new Date(day.date); + if (day.timeSlot === "Regular") { + foundDate.setMilliseconds(this.regularSchoolDayTime[1]) + } else if (day.timeSlot === "Early Dismissal") { + foundDate.setMilliseconds(this.earlyDismissalTime[1]) + } + return foundDate.getTime(); + } else { + return this.getLastDay(--indexToCheckFirst); + } + } + + findNextLongWeekend(): number { const day = Object.values(this.calendar).find((day, index, array) => { const first = array[index] const second = array[index + 1] @@ -416,9 +426,52 @@ export default class Calendar { return schoolNotExists && onWeekend && inTheFuture; }); - console.log(`Long weekend is ${day}`) - console.log(day) - throw new CalendarError("Add functionality and merge for findNextLongWeekend, `day` object contains found day, decrease by 1") + + if (!day) { + const previousFoundDay = new Date(this.getLastDay(-1)); + return previousFoundDay.getTime(); + } + const previousFoundDay = new Date(day.date); + return this.schoolTimeify(previousFoundDay).getTime(); + } + + /** + * Takes a date object as an input and returns a modified date object + * that has hours and minutes set to the end school time of either + * late start or early dismissal. + */ + schoolTimeify(dateObj: Date): Date { + // Rollback to previous day + dateObj.setDate(dateObj.getDate() - 1); + const stamp = this.strftime(dateObj.getTime()); + + if (this.calendar[stamp].hasSchool) { + if (this.calendar[stamp].timeSlot === "Regular") { + dateObj.setMilliseconds(this.regularSchoolDayTime[1]); + } else if (this.calendar[stamp].timeSlot === "Early Dismissal") { + dateObj.setMilliseconds(this.earlyDismissalTime[1]); + } + } else { + dateObj.setHours(24); + } + + return dateObj; + } + + findNextNoSchool(): number { + const day = Object.values(this.calendar).find(day => { + const dateNow = new Date(this.now); + const dateCandidate = new Date(day.date); + const inTheFuture = dateCandidate > dateNow; + return !day.hasSchool && inTheFuture; + }); + + if (!day) { + const previousFoundDay = new Date(this.getLastDay(-1)); + return previousFoundDay.getTime(); + } + const foundDate = new Date(day.date); + return this.schoolTimeify(foundDate).getTime(); } getDateAt(dateStamp: string): DayInfo { diff --git a/src/dom.ts b/src/dom.ts index 8426625..517d922 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -150,7 +150,7 @@ function getPreferredDates(value: string) { causeOfDeath = "Winter Break"; break; case "noschool": - findNextNoSchool(); + endDate = new Date(calendar.findNextNoSchool()); causeOfDeath = "No School Right Now"; break; case "weekend": @@ -158,8 +158,7 @@ function getPreferredDates(value: string) { causeOfDeath = "Weekend"; break; case "lweekend": - findNextLongWeekend(); - calendar.findNextLongWeekend(); + endDate = new Date(calendar.findNextLongWeekend()); causeOfDeath = "Long Weekend"; break; case "term": @@ -327,94 +326,6 @@ function findEndTerm() { endDate = new Date(calendar.now); } - -function findNextLongWeekend() { - for (const day in calendar.calendar) { - if (day < calendar.strftime(calendar.now)) { - continue; - } else { - let date = new Date(day); - const next3Days = [ - calendar.strftime(date.getTime()), - calendar.strftime(date.setTime(date.getTime() + 24 * 60 * 60 * 1000)), - calendar.strftime(date.setTime(date.getTime() + 24 * 60 * 60 * 1000)), - ]; - - date = new Date(day); - let next3Weekdays = [date.getDay()]; - - date.setTime(date.getTime() + 24 * 60 * 60 * 1000); - next3Weekdays.push(date.getDay()); - - date.setTime(date.getTime() + 24 * 60 * 60 * 1000); - next3Weekdays.push(date.getDay()); - - if ( - !calendar.calendar[next3Days[0]].hasSchool && - !calendar.calendar[next3Days[1]].hasSchool && - !calendar.calendar[next3Days[2]].hasSchool && - ((next3Weekdays[0] === 6 && next3Weekdays[1] === 0) || - (next3Weekdays[1] === 6 && next3Weekdays[2] === 0)) - ) { - const previousDay = new Date(day); - console.log(previousDay) - previousDay.setTime(previousDay.getTime() - 24 * 60 * 60 * 1000); - const previousDayStamp = calendar.strftime(previousDay.getTime()); - - if (!calendar.contains(previousDay.getTime())) { - endDate = new Date(calendar.now); - return; - } - - if (calendar.calendar[previousDayStamp].hasSchool) { - if (calendar.calendar[previousDayStamp].timeSlot === "Regular") { - previousDay.setHours(15, 40); - } else if (calendar.calendar[previousDayStamp].timeSlot === "Early Dismissal") { - previousDay.setHours(14, 30); - } - } else { - previousDay.setHours(24); - } - - endDate = new Date(previousDay.getTime()); - return; - } - } - } -} - -function findNextNoSchool() { - for (const day in calendar.calendar) { - if (day < calendar.strftime(calendar.now)) { - continue; - } else { - if (!calendar.calendar[day].hasSchool) { - const previousDay = new Date(day); - const previousDayStamp = calendar.strftime(previousDay.getTime()); - - if (!calendar.contains(previousDay.getTime())) { - endDate = new Date(calendar.now); - return; - } - - if (calendar.calendar[previousDayStamp].hasSchool) { - if (calendar.calendar[previousDayStamp].timeSlot === "Regular") { - previousDay.setHours(15, 40); - } else if (calendar.calendar[previousDayStamp].timeSlot === "Early Dismissal") { - previousDay.setHours(14, 30); - } - } else { - endDate = new Date(calendar.now); - return; - } - - endDate = new Date(previousDay.getTime()); - return; - } - } - } -} - function triggerFinish() { finish = true; if (!container || !lastMessage) return; From 719af187d118c9d406caf0050579944b3d788dd4 Mon Sep 17 00:00:00 2001 From: 29miaoetlrsdnet <29miaoet@lrsd.net> Date: Fri, 28 Aug 2026 14:53:31 -0500 Subject: [PATCH 05/13] Run format --- src/calendar.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/calendar.ts b/src/calendar.ts index 08916cd..211c286 100644 --- a/src/calendar.ts +++ b/src/calendar.ts @@ -367,7 +367,7 @@ export default class Calendar { findNextWeekend(): number { const currentDate = new Date(this.now); const currentWeekday = currentDate.getDay(); - const tempEnd = new Date(this.floorTimestamp("day", this.now)) + const tempEnd = new Date(this.floorTimestamp("day", this.now)); let endDate: Date; if (currentWeekday === 6 || currentWeekday === 0) { @@ -392,15 +392,15 @@ export default class Calendar { getLastDay(indexToCheckFirst: number): number { const day = Object.values(this.calendar).at(indexToCheckFirst); if (!day) { - throw new CalendarError("Could not find last school day.") + throw new CalendarError("Could not find last school day."); } if (day.hasSchool) { const foundDate = new Date(day.date); if (day.timeSlot === "Regular") { - foundDate.setMilliseconds(this.regularSchoolDayTime[1]) + foundDate.setMilliseconds(this.regularSchoolDayTime[1]); } else if (day.timeSlot === "Early Dismissal") { - foundDate.setMilliseconds(this.earlyDismissalTime[1]) + foundDate.setMilliseconds(this.earlyDismissalTime[1]); } return foundDate.getTime(); } else { @@ -410,17 +410,17 @@ export default class Calendar { findNextLongWeekend(): number { const day = Object.values(this.calendar).find((day, index, array) => { - const first = array[index] - const second = array[index + 1] - const third = array[index + 2] + const first = array[index]; + const second = array[index + 1]; + const third = array[index + 2]; if (!second || !third) return false; const schoolNotExists = !first.hasSchool && !second?.hasSchool && !third?.hasSchool; - const onWeekend = - ((this.getDayOfTheWeek(first) === 6) && (this.getDayOfTheWeek(second) === 0)) || - ((this.getDayOfTheWeek(second) === 6) && (this.getDayOfTheWeek(third) === 0)); + const onWeekend = + (this.getDayOfTheWeek(first) === 6 && this.getDayOfTheWeek(second) === 0) || + (this.getDayOfTheWeek(second) === 6 && this.getDayOfTheWeek(third) === 0); - const dateNow = new Date(this.now) - const dateCandidate = new Date(first.date) + const dateNow = new Date(this.now); + const dateCandidate = new Date(first.date); const inTheFuture = dateCandidate > dateNow; @@ -459,7 +459,7 @@ export default class Calendar { } findNextNoSchool(): number { - const day = Object.values(this.calendar).find(day => { + const day = Object.values(this.calendar).find((day) => { const dateNow = new Date(this.now); const dateCandidate = new Date(day.date); const inTheFuture = dateCandidate > dateNow; From ceb932b1b1bc7fa145ea01346d2bed07f2e40ede Mon Sep 17 00:00:00 2001 From: 29miaoetlrsdnet <29miaoet@lrsd.net> Date: Fri, 28 Aug 2026 19:44:53 -0500 Subject: [PATCH 06/13] Finished adding related functions to calendar.ts and updated documentation --- src/calendar.ts | 160 +++++++++++++++++++++++++++++++----------------- src/dom.ts | 38 +++--------- 2 files changed, 115 insertions(+), 83 deletions(-) diff --git a/src/calendar.ts b/src/calendar.ts index 211c286..057252d 100644 --- a/src/calendar.ts +++ b/src/calendar.ts @@ -30,6 +30,7 @@ type FixedTime = readonly [number, number]; type SchoolTimeAsDateStruct = [number, number, number, number, number]; type TimeUnitType = "day" | "hour" | "minute" | "second"; type SchoolDateTuple = [number, number, number, number, number]; +type TermEndSpecification = [number, number, number, number, number]; class CalendarError extends Error { constructor(message: string) { @@ -40,6 +41,7 @@ class CalendarError extends Error { export default class Calendar { public _calendar!: CalendarObject; + public _lastDay!: number | null; private dbPath: string; private earlyDismissalTime: FixedTime; private regularSchoolDayTime: FixedTime; @@ -47,6 +49,7 @@ export default class Calendar { constructor(dbPath: string) { this.dbPath = dbPath; + // Hardcoded for now, fix later // 8:30 to 14:30 this.earlyDismissalTime = [8.5 * 60 * 60 * 1000, 14.5 * 60 * 60 * 1000]; // 8:30 to 15:40 @@ -54,6 +57,10 @@ export default class Calendar { this.now = Date.now(); } + /** + * Loads data using fetch from an external JSON file, data + * format must match type `dayInfo`. + */ async loadData(): Promise { try { const response = await fetch(this.dbPath); @@ -61,13 +68,41 @@ export default class Calendar { throw new CalendarError("Network response error" + response.statusText); } this.calendar = await response.json(); + this.lastDay = this.getLastDay(-1); } catch (error) { throw new CalendarError("Calendar fetch error."); } } + /** + * This function must be called with an index to check first + * as a parameter, preferably -1. It should only be used once + * inside loadDate(), to prevent unnecessary calculation; to + * get the last day of school, reference the `this.lastDay` + * variable. + */ + getLastDay(indexToCheckFirst: number): number { + const day = Object.values(this.calendar).at(indexToCheckFirst); + if (!day) { + throw new CalendarError("Could not find last school day."); + } + + if (day.hasSchool) { + const foundDate = new Date(day.date); + if (day.timeSlot === "Regular") { + foundDate.setMilliseconds(this.regularSchoolDayTime[1]); + } else if (day.timeSlot === "Early Dismissal") { + foundDate.setMilliseconds(this.earlyDismissalTime[1]); + } + return foundDate.getTime(); + } else { + return this.getLastDay(--indexToCheckFirst); + } + } + + // Check whether calendar has been loaded. - public get calendar(): CalendarObject { + get calendar(): CalendarObject { if (!this._calendar) { throw new CalendarError("No calendar loaded, did you forget to call loadData()?"); } @@ -78,6 +113,17 @@ export default class Calendar { this._calendar = data; } + get lastDay(): number { + if (!this._lastDay) { + throw new CalendarError("Last day of school not found, this may be due to failing to load a valid calendar."); + } + return this._lastDay; + } + + private set lastDay(data: number) { + this._lastDay = data; + } + /** * This method should only be used in debugging and not in production code. */ @@ -157,11 +203,7 @@ export default class Calendar { if (startingTimeStamp > this.now) return 0; if (endingTimeStamp < this.now) return 1; - // Borrow the this.now variable to force a full school time calculation. - // Bad practice, fix later. - const temp = this.now; - const schoolTimeToElapse = this.getSchoolTimeTo(endingTimeStamp); - this.now = temp; + const schoolTimeToElapse = this.getSchoolTimeTo(endingTimeStamp, startingTimeStamp); if (this.contains(this.now)) { // Use schoolTime @@ -174,11 +216,18 @@ export default class Calendar { } } - getSchoolTimeTo(timeStamp: number): number { - const currentDate = this.strftime(this.now); + /** + * This method takes two arguments, the timeStamp parameter represents the target + * to which the school time to will be calculated, the second parameter, which is + * optional, represents the time where the function should take as it's starting + * time. Please take note that the FIRST parameter is the ending time, and the + * SECOND is the starting time. + */ + getSchoolTimeTo(timeStamp: number, startingTime: number = this.now): number { + const currentDate = this.strftime(startingTime); const endingDate = this.strftime(timeStamp); - const hoursAfterMidnight = this.modTimestamp("day", this.now); - const hoursLastDay = this.modTimestamp("day", timeStamp); + const millisecondsAfterMidnight = this.modTimestamp("day", startingTime); + const millisecondsLastDay = this.modTimestamp("day", timeStamp); let milliSeconds: number = 0; @@ -202,17 +251,17 @@ export default class Calendar { if (this.calendar[currentDate].timeSlot === "Regular") { // If there is school right now if ( - this.regularSchoolDayTime[0] <= hoursAfterMidnight && - hoursAfterMidnight < this.regularSchoolDayTime[1] + this.regularSchoolDayTime[0] <= millisecondsAfterMidnight && + millisecondsAfterMidnight < this.regularSchoolDayTime[1] ) { // Subtract one day since it was already calculated above milliSeconds -= this.regularSchoolDayTime[1] - this.regularSchoolDayTime[0]; // Add on the time that is still remaining today - milliSeconds += this.regularSchoolDayTime[1] - hoursAfterMidnight; - } else if (hoursAfterMidnight < this.regularSchoolDayTime[0]) { + milliSeconds += this.regularSchoolDayTime[1] - millisecondsAfterMidnight; + } else if (millisecondsAfterMidnight < this.regularSchoolDayTime[0]) { // Extra day already calculated // Do nothing - } else if (hoursAfterMidnight >= this.regularSchoolDayTime[1]) { + } else if (millisecondsAfterMidnight >= this.regularSchoolDayTime[1]) { // Subtract extra counted day milliSeconds -= this.regularSchoolDayTime[1] - this.regularSchoolDayTime[0]; } @@ -221,15 +270,15 @@ export default class Calendar { // Same thing as above except for early dismissal if (this.calendar[currentDate].timeSlot === "Early Dismissal") { if ( - this.earlyDismissalTime[0] <= hoursAfterMidnight && - hoursAfterMidnight < this.earlyDismissalTime[1] + this.earlyDismissalTime[0] <= millisecondsAfterMidnight && + millisecondsAfterMidnight < this.earlyDismissalTime[1] ) { // Subtract one day milliSeconds -= this.earlyDismissalTime[1] - this.earlyDismissalTime[0]; - milliSeconds += this.earlyDismissalTime[1] - hoursAfterMidnight; - } else if (hoursAfterMidnight < this.earlyDismissalTime[0]) { + milliSeconds += this.earlyDismissalTime[1] - millisecondsAfterMidnight; + } else if (millisecondsAfterMidnight < this.earlyDismissalTime[0]) { // Do nothing - } else if (hoursAfterMidnight >= this.earlyDismissalTime[1]) { + } else if (millisecondsAfterMidnight >= this.earlyDismissalTime[1]) { // Subtract extra counted day milliSeconds -= this.earlyDismissalTime[1] - this.earlyDismissalTime[0]; } @@ -241,17 +290,17 @@ export default class Calendar { if (this.calendar[endingDate].timeSlot === "Regular") { // If there is school right now if ( - this.regularSchoolDayTime[0] <= hoursLastDay && - hoursLastDay < this.regularSchoolDayTime[1] + this.regularSchoolDayTime[0] <= millisecondsLastDay && + millisecondsLastDay < this.regularSchoolDayTime[1] ) { // Subtract one day since it was already calculated above milliSeconds -= this.regularSchoolDayTime[1] - this.regularSchoolDayTime[0]; // Add on the time that has passed today - milliSeconds += hoursLastDay - this.regularSchoolDayTime[0]; - } else if (hoursLastDay < this.regularSchoolDayTime[0]) { + milliSeconds += millisecondsLastDay - this.regularSchoolDayTime[0]; + } else if (millisecondsLastDay < this.regularSchoolDayTime[0]) { // Subtract extra counted day milliSeconds -= this.regularSchoolDayTime[1] - this.regularSchoolDayTime[0]; - } else if (hoursLastDay >= this.regularSchoolDayTime[1]) { + } else if (millisecondsLastDay >= this.regularSchoolDayTime[1]) { // Extra day already calculated // Do nothing } @@ -260,16 +309,16 @@ export default class Calendar { // Same thing as above except for early dismissal if (this.calendar[endingDate].timeSlot === "Early Dismissal") { if ( - this.earlyDismissalTime[0] <= hoursLastDay && - hoursLastDay < this.earlyDismissalTime[1] + this.earlyDismissalTime[0] <= millisecondsLastDay && + millisecondsLastDay < this.earlyDismissalTime[1] ) { // Subtract one day milliSeconds -= this.earlyDismissalTime[1] - this.earlyDismissalTime[0]; - milliSeconds += hoursLastDay - this.earlyDismissalTime[0]; - } else if (hoursLastDay < this.earlyDismissalTime[0]) { + milliSeconds += millisecondsLastDay - this.earlyDismissalTime[0]; + } else if (millisecondsLastDay < this.earlyDismissalTime[0]) { // Subtract extra counted day milliSeconds -= this.earlyDismissalTime[1] - this.earlyDismissalTime[0]; - } else if (hoursLastDay >= this.earlyDismissalTime[1]) { + } else if (millisecondsLastDay >= this.earlyDismissalTime[1]) { // Do nothing } } @@ -385,29 +434,6 @@ export default class Calendar { return d.getDay(); } - /** - * This function must be called with an index to check first - * as a parameter, preferably -1. - */ - getLastDay(indexToCheckFirst: number): number { - const day = Object.values(this.calendar).at(indexToCheckFirst); - if (!day) { - throw new CalendarError("Could not find last school day."); - } - - if (day.hasSchool) { - const foundDate = new Date(day.date); - if (day.timeSlot === "Regular") { - foundDate.setMilliseconds(this.regularSchoolDayTime[1]); - } else if (day.timeSlot === "Early Dismissal") { - foundDate.setMilliseconds(this.earlyDismissalTime[1]); - } - return foundDate.getTime(); - } else { - return this.getLastDay(--indexToCheckFirst); - } - } - findNextLongWeekend(): number { const day = Object.values(this.calendar).find((day, index, array) => { const first = array[index]; @@ -428,7 +454,7 @@ export default class Calendar { }); if (!day) { - const previousFoundDay = new Date(this.getLastDay(-1)); + const previousFoundDay = new Date(this.lastDay); return previousFoundDay.getTime(); } const previousFoundDay = new Date(day.date); @@ -467,13 +493,37 @@ export default class Calendar { }); if (!day) { - const previousFoundDay = new Date(this.getLastDay(-1)); + const previousFoundDay = new Date(this.lastDay); return previousFoundDay.getTime(); } const foundDate = new Date(day.date); return this.schoolTimeify(foundDate).getTime(); } + /** + * Takes any number of arrays of numbers as an argument, + * each array must have exactly 5 numbers, to be passed on + * to a Date object constructor, they must be valid dates. + * and arranged in chronological order. + */ + findEndTerm(...termEnds: Array): number { + let termEndDates: Array = []; + for (const arr of termEnds) { + const date = new Date(...arr); + termEndDates.push(date); + } + + for (const date of termEndDates) { + // First term that has not passed + if (date.getTime() - this.now > 0) { + return date.getTime(); + } + } + + return this.now; + } + + getDateAt(dateStamp: string): DayInfo { if (!this.calendar[dateStamp]) { throw new RangeError(`Calendar does not contain ${dateStamp}.`); diff --git a/src/dom.ts b/src/dom.ts index 517d922..4499949 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -44,6 +44,15 @@ let causeOfDeath: string; const lastMessage = document.getElementById("last-message") as HTMLDivElement | null; +type DateArgs = [number, number, number, number, number]; + +const termEnds: Array = [ + [2026, 10, 18, 15, 40], + [2027, 1, 5, 15, 40], + [2027, 3, 13, 15, 40], + [2027, 5, 21, 15, 40], +]; + async function start() { // Welcome console.log( @@ -162,7 +171,7 @@ function getPreferredDates(value: string) { causeOfDeath = "Long Weekend"; break; case "term": - findEndTerm(); + endDate = new Date(calendar.findEndTerm(...termEnds)); causeOfDeath = "🎉School Has Ended🎉"; break; case "start": @@ -299,33 +308,6 @@ async function setPreferredCalendars(value: string) { localStorage.setItem("calendar", value); } -function findEndTerm() { - type DateArgs = [number, number, number, number, number]; - - const termEnds: DateArgs[] = [ - [2026, 10, 18, 15, 40], - [2027, 1, 5, 15, 40], - [2027, 3, 13, 15, 40], - [2027, 5, 21, 15, 40], - ]; - let termEndDates: Date[] = []; - for (const arr of termEnds) { - const date = new Date(...arr); - termEndDates.push(date); - } - - // Get current term - for (const date of termEndDates) { - // First term that has not passed - if (date.getTime() - calendar.now > 0) { - endDate = new Date(date.getTime()); - return; - } - } - - endDate = new Date(calendar.now); -} - function triggerFinish() { finish = true; if (!container || !lastMessage) return; From 62e505136a17e3e8b9bc8e75ad58fd395159eb86 Mon Sep 17 00:00:00 2001 From: 29miaoetlrsdnet <29miaoet@lrsd.net> Date: Fri, 28 Aug 2026 19:45:18 -0500 Subject: [PATCH 07/13] Run format --- src/calendar.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/calendar.ts b/src/calendar.ts index 057252d..97d6935 100644 --- a/src/calendar.ts +++ b/src/calendar.ts @@ -58,7 +58,7 @@ export default class Calendar { } /** - * Loads data using fetch from an external JSON file, data + * Loads data using fetch from an external JSON file, data * format must match type `dayInfo`. */ async loadData(): Promise { @@ -76,9 +76,9 @@ export default class Calendar { /** * This function must be called with an index to check first - * as a parameter, preferably -1. It should only be used once - * inside loadDate(), to prevent unnecessary calculation; to - * get the last day of school, reference the `this.lastDay` + * as a parameter, preferably -1. It should only be used once + * inside loadDate(), to prevent unnecessary calculation; to + * get the last day of school, reference the `this.lastDay` * variable. */ getLastDay(indexToCheckFirst: number): number { @@ -100,7 +100,6 @@ export default class Calendar { } } - // Check whether calendar has been loaded. get calendar(): CalendarObject { if (!this._calendar) { @@ -115,7 +114,9 @@ export default class Calendar { get lastDay(): number { if (!this._lastDay) { - throw new CalendarError("Last day of school not found, this may be due to failing to load a valid calendar."); + throw new CalendarError( + "Last day of school not found, this may be due to failing to load a valid calendar." + ); } return this._lastDay; } @@ -218,9 +219,9 @@ export default class Calendar { /** * This method takes two arguments, the timeStamp parameter represents the target - * to which the school time to will be calculated, the second parameter, which is - * optional, represents the time where the function should take as it's starting - * time. Please take note that the FIRST parameter is the ending time, and the + * to which the school time to will be calculated, the second parameter, which is + * optional, represents the time where the function should take as it's starting + * time. Please take note that the FIRST parameter is the ending time, and the * SECOND is the starting time. */ getSchoolTimeTo(timeStamp: number, startingTime: number = this.now): number { @@ -501,8 +502,8 @@ export default class Calendar { } /** - * Takes any number of arrays of numbers as an argument, - * each array must have exactly 5 numbers, to be passed on + * Takes any number of arrays of numbers as an argument, + * each array must have exactly 5 numbers, to be passed on * to a Date object constructor, they must be valid dates. * and arranged in chronological order. */ @@ -523,7 +524,6 @@ export default class Calendar { return this.now; } - getDateAt(dateStamp: string): DayInfo { if (!this.calendar[dateStamp]) { throw new RangeError(`Calendar does not contain ${dateStamp}.`); From 011588a85c34adcdb4e0fe357f355f1ae093b825 Mon Sep 17 00:00:00 2001 From: 29miaoetlrsdnet <29miaoet@lrsd.net> Date: Fri, 28 Aug 2026 19:47:55 -0500 Subject: [PATCH 08/13] Update documentation --- .github/CONTRIBUTING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9742d6a..e6e9d4b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -46,9 +46,10 @@ git push -u origin your-branch-name ## Guidelines -- Use HTML syntax instead of XHTML syntax. -- Use LF line returns instead of CRLF or CR whenever possible. +- Use `Array` for typing arrays instead of `T[]`. - Use CSS variables instead of direct values. +- Use LF line returns instead of CRLF or CR whenever possible, or ensure you have the correct git configurations. +- Use HTML syntax instead of XHTML syntax. - Background color schemes should follow those of the existing `:root` elements. - Prefer aria-labels for accessibility over direct `