Skip to content

Commit

Permalink
Re-add grouping & combining schedules
Browse files Browse the repository at this point in the history
  • Loading branch information
arnimattr committed Dec 31, 2022
1 parent 0a0c274 commit 60879ec
Show file tree
Hide file tree
Showing 24 changed files with 726 additions and 72 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ jobs:
- name: Type-check code
run: deno check mod.ts

- name: Type-check examples
run: |
shopt -s globstar
for f in examples/**/*.ts; do deno check $f; done
- name: Run tests
run: deno test --allow-none --doc

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ if (status !== LoginStatus.Ok) {
}

let timetable = await client.getOwnTimetable("2022-01-01", "2022-12-31");
client.logout();
await client.logout();

console.log(timetable.lessons);
```
Expand Down
5 changes: 5 additions & 0 deletions deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,10 @@

"tasks": {
"build_npm": "deno run -A scripts/build_npm.ts"
},

"compilerOptions": {
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true
}
}
10 changes: 10 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions examples/combine_schedules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* This example shows how lesson schedules can be retrieved and combined.
*/

import { LoginStatus, UntisClient } from "../mod.ts";

let client = new UntisClient("nessa.webuntis.com", "School name");

let status = await client.login("username", "password");
if (status !== LoginStatus.Ok) {
throw new Error(`Login failed: ${LoginStatus[status]}`);
}

let timetable = await client.getOwnTimetable("2022-01-01", "2022-12-31");
await client.logout();

let collection = timetable.getScheduleCollection();

collection = collection.combineSequentialSchedules();

console.log(collection.schedules);
10 changes: 8 additions & 2 deletions examples/1.ts → examples/get_timetable.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/**
* This example shows a basic usecase of
* - searching your school by its name,
* - logging in with your credentials and
* - fetching your timetable.
*/

import { LoginStatus, searchSchoolsByName } from "../mod.ts";

let [school] = await searchSchoolsByName("School name");
Expand All @@ -9,12 +16,11 @@ if (!school) {
let client = school.getClient();

let status = await client.login("username", "password");

if (status !== LoginStatus.Ok) {
throw new Error(`Login failed: ${LoginStatus[status]}`);
}

let timetable = await client.getOwnTimetable("2022-01-01", "2022-12-31");
client.logout();
await client.logout();

console.log(timetable.lessons);
29 changes: 23 additions & 6 deletions lib/datetime/Time.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { TimeUnits } from "./TimeUnits.ts";

/** Helper for comparing the start and end times of lessons. By no means a replacement for {@link Date}. */
export class Time {
constructor(
Expand All @@ -18,14 +20,29 @@ export class Time {
}

selectEarlier(other: Time): Time {
return this.hours <= other.hours && this.minutes <= other.minutes
? this
: other;
if (this.hours === other.hours) {
return this.minutes <= other.minutes ? this : other;
}
return this.hours < other.hours ? this : other;
}

selectLater(other: Time): Time {
return this.hours >= other.hours && this.minutes >= other.minutes
? this
: other;
if (this.hours === other.hours) {
return this.minutes >= other.minutes ? this : other;
}
return this.hours > other.hours ? this : other;
}

diffTo(other: Time): number {
let diff = (other.hours - this.hours) * TimeUnits.Hour +
(other.minutes - this.minutes) * TimeUnits.Minute;

return diff;
}

withDate(date: Date): Date {
date = new Date(date);
date.setHours(this.hours, this.minutes);
return date;
}
}
9 changes: 9 additions & 0 deletions lib/datetime/TimeUnits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** The number of milliseconds in each unit of time. */
export enum TimeUnits {
Millisecond = 1,
Second = 1000 * TimeUnits.Millisecond,
Minute = 60 * TimeUnits.Second,
Hour = 60 * TimeUnits.Minute,
Day = 24 * TimeUnits.Hour,
Week = 7 * TimeUnits.Day,
}
37 changes: 37 additions & 0 deletions lib/datetime/Time_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { assert } from "std/testing/asserts.ts";
import { Time, TimeUnits } from "./mod.ts";

Deno.test("Time.selectEarlier() - should work with different hours", () => {
let a = new Time(9, 25);
let b = new Time(10, 0);

assert(a.selectEarlier(b).equals(a));
});

Deno.test("Time.selectEarlier() - should work with the same hour", () => {
let a = new Time(9, 25);
let b = new Time(9, 30);

assert(a.selectEarlier(b).equals(a));
});

Deno.test("Time.selectLater() - should work with different hours", () => {
let a = new Time(9, 25);
let b = new Time(10, 0);

assert(a.selectLater(b).equals(b));
});

Deno.test("Time.selectLater() - should work with the same hour", () => {
let a = new Time(9, 25);
let b = new Time(9, 30);

assert(a.selectLater(b).equals(b));
});

Deno.test("Time.diffTo() - should be +1 hour", () => {
let a = new Time(10, 30);
let b = new Time(11, 30);

assert(a.diffTo(b) === TimeUnits.Hour * 1);
});
1 change: 1 addition & 0 deletions lib/datetime/mod.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./Time.ts";
export * from "./untis.ts";
export * from "./TimeUnits.ts";
5 changes: 2 additions & 3 deletions lib/datetime/untis.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { format } from "std/datetime/format.ts";
import { parse } from "std/datetime/parse.ts";
import { format, parse } from "std/datetime/mod.ts";
import { Time } from "./Time.ts";

/** Parse a date from the WebUntis API into a JS Date object using local time. */
Expand All @@ -10,7 +9,7 @@ export function parseUntisDate(date: string | number): Date {
/** Parse a time from the WebUntis API into a {@link Time} object. */
export function parseUntisTime(time: string | number): Time {
time = String(time).padStart(4, "0");
return Time.fromDate(parse(time, "hhmm"));
return Time.fromDate(parse(time, "HHmm"));
}

/** Format a JS Date object into a format understood by the WebUntis API. */
Expand Down
1 change: 0 additions & 1 deletion lib/jsonrpc/RpcClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export class RpcClient {
}

for (let { name, value } of getSetCookies(response.headers)) {
console.log({ name, value });
this.cookies[name] = value;
}

Expand Down
10 changes: 5 additions & 5 deletions src/UntisClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class UntisClient {

/**
* Logs in as a user. Needs to be called before accessing all other methods.
* You should log out ({@link UntisClient.logout()}) as soon as possible to free resources on WebUntis' servers.
* You should log out ({@link logout()}) as soon as possible to free resources on WebUntis' servers.
* @param username username to log in with.
* @param password user password.
* @returns a {@link LoginStatus} describing whether the login was successful.
Expand Down Expand Up @@ -209,10 +209,10 @@ export class UntisClient {
showLsText: true,
showLsNumber: true,
showStudentgroup: true,
klasseFields: ["id", "name", "longname"],
roomFields: ["id", "name", "longname"],
subjectFields: ["id", "name", "longname"],
teacherFields: ["id", "name", "longname"],
klasseFields: ["id", "name"],
roomFields: ["id", "name"],
subjectFields: ["id", "name"],
teacherFields: ["id", "name"],
},
};

Expand Down
1 change: 0 additions & 1 deletion src/webuntis/resources/lesson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export type lesson = {
export type lessonElement = {
id: number;
name: string;
longname: string;

/**
* If this is not `undefined`, this is the id of the original element and
Expand Down
85 changes: 63 additions & 22 deletions src/wrappers/Lesson.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { parseUntisDate, Time } from "lib/datetime/mod.ts";
import { lesson } from "webuntis/resources";
import { parseUntisTime } from "../../lib/datetime/untis.ts";
import { LessonElements } from "./LessonElements.ts";
import { LessonElementCollection } from "./LessonElementCollection.ts";
import { Schedule } from "./Schedule.ts";

type lessonType =
Expand Down Expand Up @@ -38,32 +38,34 @@ function getLessonCode(lesson: lesson): lessonCode {
return lesson.code || "regular";
}

/**
* Wrapper around the lesson object.
*/
export class Lesson extends Schedule {
/** Wrapper around the lesson object. */
export class Lesson {
constructor(
/** The lesson's id. Is unique and not the same for repeated occurrences. */
readonly id: number,
/** The date the lesson occurs on. */
readonly date: Date,
startTime: Time,
endTime: Time,
/** Time the lesson starts at. */
readonly startTime: Time,
/** Time the lesson ends at. */
readonly endTime: Time,
/** The type of lesson. */
readonly lessonType: lessonType,
/** Code that represents whether the lesson is regular, cancelled or irregular. */
readonly lessonCode: lessonCode,
lessonNumber: number,
lessonText: string,
/** Lesson number that is shared by all lessons in this student group (combination of class and subject). */
readonly lessonNumber: number,
/** Lesson text. */
readonly lessonText: string,
/** Text containing info about a substitution if present. */
readonly substitutionText: string | null,
/** Activitiy type. */
readonly activityType: string,
studentGroup: string | null,
readonly elements: LessonElements,
/** Name of the lesson's student group. */
readonly studentGroup: string | null,
/** Collection of elements included in the lesson. */
readonly elements: LessonElementCollection,
) {
super(
lessonNumber,
date.getDay(),
startTime,
endTime,
studentGroup,
lessonText,
);
}

/** Creates a new instance of this class from a lesson object returned by WebUntis. */
Expand All @@ -80,24 +82,30 @@ export class Lesson extends Schedule {
lesson.substText || null,
lesson.activityType,
lesson.sg || null,
LessonElements.fromLesson(lesson),
LessonElementCollection.fromLesson(lesson),
);
}

/** Creates a new schedule object that represents the schedule the lesson belongs to. */
getSchedule(): Schedule {
return new Schedule(
this.lessonNumber,
this.weekDay,
this.date.getDay(),
this.startTime,
this.endTime,
this.studentGroup,
this.lessonText,
);
}

/** Checks whether the lesson belongs to a schedule. */
belongsToSchedule(schedule: Schedule): boolean {
return this.getSchedule().equals(schedule);
}

/**
* Checks whether this lesson deviates from its weekly schedule.
* A deviation could be that the lesson is cancelled or that it has substituted or missing elements compared to its schedule.
* A deviation could be that the lesson is cancelled or that it has substituted or removed elements compared to its schedule.
*/
deviatesFromSchedule(): boolean {
return this.substitutionText !== null ||
Expand All @@ -106,4 +114,37 @@ export class Lesson extends Schedule {
.flat()
.some((element) => element.isSubstituted());
}

/** Checks whether two lessons are sequential and can be combined. */
isSequential(other: Lesson): boolean {
return this.date.valueOf() === other.date.valueOf() &&
(this.endTime.equals(other.startTime) ||
other.endTime.equals(this.startTime)) &&
this.lessonType === other.lessonType &&
this.lessonCode === other.lessonCode &&
this.lessonNumber === other.lessonNumber &&
this.lessonText === other.lessonText &&
this.substitutionText === other.substitutionText &&
this.activityType === other.activityType &&
this.studentGroup === other.studentGroup &&
this.elements.equals(other.elements);
}

/** Creates a new lesson by combining this lesson with another lesson. */
combineWith(other: Lesson): Lesson {
return new Lesson(
this.id,
this.date,
this.startTime.selectEarlier(other.startTime),
this.endTime.selectLater(other.endTime),
this.lessonType,
this.lessonCode,
this.lessonNumber,
this.lessonText,
this.substitutionText,
this.activityType,
this.studentGroup,
this.elements,
);
}
}
Loading

0 comments on commit 60879ec

Please sign in to comment.