feat/#3 binary decoder for occupancy events and stubs

This commit is contained in:
survellow
2024-05-29 23:00:36 +02:00
parent 7575286cf4
commit 6e41013788
6 changed files with 769 additions and 31 deletions

View File

@ -19,6 +19,7 @@
"bson": "^5.5.1",
"country-flag-emoji-polyfill": "^0.1.8",
"date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3",
"pinia": "^2.1.7",
"primeflex": "^3.3.1",
"primeicons": "^6.0.1",
@ -4581,6 +4582,14 @@
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/date-fns-tz": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.1.3.tgz",
"integrity": "sha512-ZfbMu+nbzW0mEzC8VZrLiSWvUIaI3aRHeq33mTe7Y38UctKukgqPR4nTDwcwS4d64Gf8GghnVsroBuMY3eiTeA==",
"peerDependencies": {
"date-fns": "^3.0.0"
}
},
"node_modules/de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",

View File

@ -24,6 +24,7 @@
"bson": "^5.5.1",
"country-flag-emoji-polyfill": "^0.1.8",
"date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3",
"pinia": "^2.1.7",
"primeflex": "^3.3.1",
"primeicons": "^6.0.1",

View File

@ -36,7 +36,7 @@ export async function fetchRoomOccupancy(
return null;
}
const data = new Uint8Array(roomsResponse);
roomOccupancyList = BSON.deserialize(data) as RoomOccupancyList;
roomOccupancyList = RoomOccupancyList.fromJSON(BSON.deserialize(data));
return roomOccupancyList;
});

View File

@ -31,6 +31,7 @@ import { useQuery } from "@tanstack/vue-query";
import { watch } from "vue";
import { fetchRoomOccupancy } from "@/api/fetchRoomOccupancy";
import { isValid } from "date-fns";
import { RoomOccupancyList } from "@/model/roomOccupancyList";
const { t } = useI18n({ useScope: "global" });
@ -70,6 +71,22 @@ const mobilePage = inject("mobilePage") as Ref<boolean>;
const selectedRoom = computed(() => props.room);
/**
* Transform decoded JSON object with binary data
* to anonymized occupancy events
* @param data RoomOccupancyList with binary data
* @returns Anonymized occupancy events
*/
function transformData(data: RoomOccupancyList) {
const events = data
.decodeOccupancy(selectedRoom.value, new Date(currentDateFrom.value), new Date(currentDateTo.value))
.map((event, index) => ({
id: index,
event: event,
}));
return events;
}
const { data: occupations } = useQuery({
queryKey: ["roomOccupation", selectedRoom, currentDateFrom, currentDateTo],
queryFn: () =>
@ -77,12 +94,7 @@ const { data: occupations } = useQuery({
new Date(currentDateFrom.value).toISOString(),
new Date(currentDateTo.value).toISOString()
),
select: (data) => data
.decodeOccupancy(selectedRoom.value, new Date(currentDateFrom.value), new Date(currentDateTo.value))
.map((event, index) => ({
id: index,
event: event,
})),
select: (data) => transformData(data),
enabled: () => selectedRoom.value !== "" && currentDateFrom.value !== "",
staleTime: 5000000, // 500 seconds
});

View File

@ -0,0 +1,651 @@
//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
//Copyright (C) 2024 HTWKalender support@htwkalender.de
//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU Affero General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU Affero General Public License for more details.
//You should have received a copy of the GNU Affero General Public License
//along with this program. If not, see <https://www.gnu.org/licenses/>.
import { beforeEach, describe, expect, test } from "vitest";
import { RoomOccupancyList } from "./roomOccupancyList";
import { Binary } from "bson";
import { add, addHours, addMinutes, interval, subHours } from "date-fns";
import { fromZonedTime, toZonedTime } from "date-fns-tz";
const testListStart = new Date("2022-01-01T00:00:00Z");
var testList : RoomOccupancyList; //= RoomOccupancyList.fromJSON({});
var alternating : Uint8Array = new Uint8Array(Array(4).fill(0xF0));
var booked : Uint8Array = new Uint8Array(Array(4).fill(0xFF));
var empty : Uint8Array = new Uint8Array(Array(4).fill(0x00));
var counting : Uint8Array = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
const localTimezone = "Europe/Berlin";
describe("RoomOccupancyList", () => {
beforeEach(() => {
alternating = new Uint8Array(Array(4).fill(0xF0));
booked = new Uint8Array(Array(4).fill(0xFF));
empty = new Uint8Array(Array(4).fill(0x00));
counting = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
testList = RoomOccupancyList.fromJSON({
start: testListStart,
granularity: 60,
blocks: 32,
rooms: [
{
name: "BOOKED",
occupancy: new Binary(booked, 0),
},
{
name: "EMPTY",
occupancy: new Binary(empty, 0),
},
{
name: "ALTERNATING",
occupancy: new Binary(alternating, 0),
},
{
name: "COUNTING",
occupancy: new Binary(counting, 0),
}
]
});
});
describe("getRooms", () => {
test("get rooms", () => {
// act
const rooms = testList["getRooms"]();
// assert
expect(rooms).toEqual([
"BOOKED",
"EMPTY",
"ALTERNATING",
"COUNTING"
]);
});
test("get empty rooms", () => {
// arrange
const emptyRoomOccupancy = RoomOccupancyList.fromJSON({
start: testListStart,
granularity: 60,
blocks: 32,
rooms: []
});
// act
const rooms = emptyRoomOccupancy["getRooms"]();
// assert
expect(rooms).toEqual([]);
});
})
describe("decodeOccupancy", () => {
test("generate stubs for missing room", () => {
// arrange
// act
const decoded = testList["decodeOccupancy"](
"MISSING",
testListStart,
addHours(testListStart, 8),
);
// assert
expect(decoded).toEqual([
{
start: "2022-01-01T06:00:00.000Z",
end: "2022-01-01T08:00:00.000Z",
rooms: "MISSING",
free: true,
stub: true
}
]);
});
test("generate stubs out of range", () => {
// arrange
// act
const decoded = testList["decodeOccupancy"](
"BOOKED",
subHours(testListStart, 10),
testListStart,
);
// assert
expect(decoded).toEqual([
{
start: "2021-12-31T14:00:00.000Z",
end: "2021-12-31T19:00:00.000Z",
rooms: "BOOKED",
free: true,
stub: true
}
]);
});
test("decode occupancy in range", () => {
// arrange
// act
const decoded = testList["decodeOccupancy"](
"BOOKED",
subHours(testListStart, 2),
addHours(testListStart, 8),
);
expect(decoded).toEqual([
{
start: "2022-01-01T00:00:00.000Z",
end: "2022-01-01T08:00:00.000Z",
rooms: "BOOKED",
free: false,
stub: false
}
]);
});
});
describe("sliceOccupancy", () => {
test.each([
booked,
empty,
alternating
])("getCompleteOccupancy of %j", (occupancy) => {
// arrange
const startTime = new Date(testList.start);
const endTime = new Date(addHours(startTime, 32));
const sliceInterval = interval(startTime, endTime);
// act
const sliced = testList["sliceOccupancy"](
sliceInterval,
occupancy
)
// assert
expect(sliced).toEqual({
decodeSliceStart: startTime,
decodeSlice: new Uint8Array(occupancy),
});
});
test("throws start out of bounds", () => {
// arrange
const startTime = new Date(subHours(testList.start,1));
const endTime = new Date(addHours(startTime, 32));
const sliceInterval = interval(startTime, endTime);
// act and assert
expect(() => {
testList["sliceOccupancy"](
sliceInterval,
alternating
)
}).toThrowError();
});
test("throws end out of bounds", () => {
// arrange
const startTime = new Date(testList.start);
const endTime = new Date(addHours(startTime, 33));
const sliceInterval = interval(startTime, endTime);
// act and assert
expect(() => {
testList["sliceOccupancy"](
sliceInterval,
alternating
)
}).toThrowError();
});
test("range at byte boundaries", () => {
// arrange
const startTime = new Date(addHours(testList.start, 8));
const endTime = new Date(addHours(testList.start, 24));
const sliceInterval = interval(startTime, endTime);
// act
const sliced = testList["sliceOccupancy"](
sliceInterval,
counting
)
// assert
expect(sliced).toEqual({
decodeSliceStart: startTime,
decodeSlice: new Uint8Array([0x01, 0x02]),
});
});
test("range within byte boundaries", () => {
// arrange
const startTime = new Date(addMinutes(testListStart, 500));
const endTime = new Date(addHours(testListStart, 15));
const sliceInterval = interval(startTime, endTime);
// act
const sliced = testList["sliceOccupancy"](
sliceInterval,
counting
)
// assert
expect(sliced).toEqual({
decodeSliceStart: addHours(testListStart,8),
decodeSlice: new Uint8Array([0x01]),
});
});
});
describe("getOccupancyInterval", () => {
test("get empty interval", () => {
// arrange
const emptyRoomOccupancy = RoomOccupancyList.fromJSON({
start: testListStart,
granularity: 60,
blocks: 0,
rooms: []
});
// act
const emptyInterval = emptyRoomOccupancy["getOccupancyInterval"]();
// assert
expect(emptyInterval).toEqual(interval(testListStart, testListStart));
});
test("get interval of valid room occupancy list", () => {
// act
const testInterval = testList["getOccupancyInterval"]();
// assert
expect(testInterval).toEqual(interval(testListStart, addHours(testListStart, 32)));
});
});
describe("decodeOccupancyData", () => {
test("decode occupancy without length", () => {
// arrange
// act
const decoded = RoomOccupancyList["decodeOccupancyData"](
new Uint8Array([]),
testListStart,
15,
"Raum"
);
// assert
expect(decoded).toEqual([]);
});
test("decode blocked occupancy", () => {
// arrange
// act
const decoded = RoomOccupancyList["decodeOccupancyData"](
booked,
testListStart,
15,
"BOOKED"
);
// assert
expect(decoded).toEqual([
{
start: testListStart.toISOString(),
end: addHours(testListStart, 8).toISOString(),
rooms: "BOOKED",
free: false,
stub: false
}
]);
});
test("decode empty occupancy", () => {
// arrange
// act
const decoded = RoomOccupancyList["decodeOccupancyData"](
empty,
testListStart,
15,
"BOOKED"
);
// assert
expect(decoded).toEqual([]);
});
test("decode alternating occupancy", () => {
// arrange
// act
const decoded = RoomOccupancyList["decodeOccupancyData"](
alternating,
new Date("2024-01-01T00:00:00Z"),
15,
"ALTERNATING"
);
// assert
expect(decoded).toEqual([
{
start: "2024-01-01T00:00:00.000Z",
end: "2024-01-01T01:00:00.000Z",
rooms: "ALTERNATING",
free: false,
stub: false
},
{
start: "2024-01-01T02:00:00.000Z",
end: "2024-01-01T03:00:00.000Z",
rooms: "ALTERNATING",
free: false,
stub: false
},
{
start: "2024-01-01T04:00:00.000Z",
end: "2024-01-01T05:00:00.000Z",
rooms: "ALTERNATING",
free: false,
stub: false
},
{
start: "2024-01-01T06:00:00.000Z",
end: "2024-01-01T07:00:00.000Z",
rooms: "ALTERNATING",
free: false,
stub: false
}
]);
});
});
describe("generateStubEvents", () => {
test("no events if negative range", () => {
// arrange
const startTime = new Date("2022-01-01T00:00:00Z");
const endTime = new Date("2021-01-01T00:00:00Z");
// act
const stubEvents = RoomOccupancyList["generateStubEvents"](
"ROOM",
startTime,
endTime
);
// assert
expect(stubEvents).toEqual([]);
});
test("no events if start and end the same", () => {
// arrange
const startTime = new Date("2022-01-01T00:00:00Z");
const endTime = new Date("2022-01-01T00:00:00Z");
// act
const stubEvents = RoomOccupancyList["generateStubEvents"](
"ROOM",
startTime,
endTime
);
// assert
expect(stubEvents).toEqual([]);
});
test("generate week", () => {
// arrange
const startTime = new Date("2022-01-01T12:00:00Z");
const endTime = new Date("2022-01-07T12:30:00Z");
// act
const stubEvents = RoomOccupancyList["generateStubEvents"](
"ROOM",
startTime,
endTime
);
// assert
expect(stubEvents).toEqual([
{
start: "2022-01-01T12:00:00.000Z",
end: "2022-01-01T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-02T06:00:00.000Z",
end: "2022-01-02T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-03T06:00:00.000Z",
end: "2022-01-03T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-04T06:00:00.000Z",
end: "2022-01-04T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-05T06:00:00.000Z",
end: "2022-01-05T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-06T06:00:00.000Z",
end: "2022-01-06T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-07T06:00:00.000Z",
end: "2022-01-07T12:30:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
}
]);
});
test("generate day", () => {
// arrange
const startTime = new Date("2022-01-01T16:00:00Z");
const endTime = new Date("2022-01-01T19:00:00Z");
// act
const stubEvents = RoomOccupancyList["generateStubEvents"](
"ROOM",
startTime,
endTime
);
// assert
expect(stubEvents).toEqual([
{
start: "2022-01-01T16:00:00.000Z",
end: "2022-01-01T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
}
]);
});
});
describe("shiftTimeForwardInsideWorkday", () => {
test("shift time to next day", () => {
// arrange
const startTime = new Date("2022-01-01T20:00:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-01T23:00:00Z"));
});
test("don't shift time on the same day", () => {
// arrange
const startTime = new Date("2022-01-02T01:00:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T01:00:00Z"));
});
test("don't shift if already inside workday", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T12:30:00Z"));
});
});
describe("shiftTimeBackwardInsideWorkday", () => {
test("shift time to last day", () => {
// arrange
const startTime = new Date("2022-01-02T05:30:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-01T22:59:59.999Z"));
});
test("don't shift time on the same day", () => {
// arrange
const startTime = new Date("2022-01-02T22:00:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T22:00:00Z"));
});
test("don't shift if already inside workday", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T12:30:00Z"));
});
});
describe("setTimeOfDay", () => {
test("set time to 00:00:00", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {});
// assert
expect(shiftedTime).toEqual(new Date("2022-01-01T23:00:00Z"));
});
test("set time to 23:59:59", () => {
// arrange
const startTime = new Date("2022-06-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {hours: 23, minutes: 59, seconds: 59});
// assert
expect(shiftedTime).toEqual(new Date("2022-06-02T21:59:59Z"));
});
test("set same time", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {hours: 13, minutes: 30, seconds: 0});
// assert
expect(shiftedTime).toEqual(new Date("2022-01-02T12:30:00Z"));
});
});
describe("startOfDay", () => {
test("in the winter", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
// assert
expect(shiftedTime).toEqual(new Date("2022-01-01T23:00:00Z"));
});
test("in the summer", () => {
// arrange
const startTime = new Date("2022-06-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
// assert
expect(shiftedTime).toEqual(new Date("2022-06-01:22:00Z"));
});
});
describe("endOfDay", () => {
test("in the winter", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
// assert
expect(shiftedTime).toEqual(new Date("2022-01-01T23:00:00Z"));
});
test("in the summer", () => {
// arrange
const startTime = new Date("2022-06-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
// assert
expect(shiftedTime).toEqual(new Date("2022-06-01:22:00Z"));
});
});
});

View File

@ -15,8 +15,16 @@
//along with this program. If not, see <https://www.gnu.org/licenses/>.
import { Binary } from "bson";
import { AnonymizedEventDTO, AnonymizedOccupancy } from "./event";
import { Duration, NormalizedInterval, add, addDays, addMinutes, clamp, differenceInMinutes, eachDayOfInterval, endOfDay, interval, isAfter, isBefore, isEqual, max, min, startOfDay, startOfTomorrow, subDays } from "date-fns";
import { AnonymizedOccupancy } from "./event";
import { Duration, NormalizedInterval, add, addDays, addMinutes, clamp, differenceInMinutes, eachDayOfInterval, endOfDay, interval, isAfter, isBefore, isEqual, max, min, startOfDay, subDays } from "date-fns";
import { fromZonedTime, toZonedTime } from "date-fns-tz";
/// The start time of the day. 07:00
const START_OF_WORKDAY : Duration = {hours: 7};
/// The end time of the day. 20:00
const END_OF_WORKDAY : Duration = {hours: 20};
/// The timezone of the data (Leipzig)
const TIMEZONE = "Europe/Berlin";
/**
* Represents the occupancy of a single room.
@ -29,11 +37,6 @@ class RoomOccupancy {
) {}
}
/// The start time of the day. 08:00
const START_OF_WORKDAY : Duration = {hours: 8};
/// The end time of the day. 20:00
const END_OF_WORKDAY : Duration = {hours: 20};
/**
* Represents the occupancy of multiple rooms.
* start is the start date of the occupancy list.
@ -65,7 +68,6 @@ export class RoomOccupancyList {
* @returns a list of AnonymizedEventDTO objects representing the occupancy of the room.
*/
public decodeOccupancy(room : string, from : Date, to : Date) : AnonymizedOccupancy[] {
console.log("Decoding occupancy for room " + room + " from " + from.toISOString() + " to " + to.toISOString());
const roomOccupancy = this.rooms.find((r) => r.name === room);
if (roomOccupancy === undefined) {
@ -77,15 +79,10 @@ export class RoomOccupancyList {
// Get start and end of decoded time range (within encoded list and requested range)
let decodeInterval = interval(clamp(from, this.getOccupancyInterval()), clamp(to, this.getOccupancyInterval()));
// Calculate the slice of bytes, that are needed to decode the requested time range
let minutesFromStart = differenceInMinutes(this.start, decodeInterval.start);
let minutesToEnd = differenceInMinutes(this.start, decodeInterval.end);
let firstByte = Math.floor(minutesFromStart / this.granularity / 8);
let lastByte = Math.floor(minutesToEnd / this.granularity / 8);
let decodeSliceStart = addMinutes(this.start, firstByte * 8 * this.granularity);
let decodeSlice = roomOccupancy.occupancy.buffer.slice(firstByte, lastByte + 1);
let {decodeSliceStart, decodeSlice} = this.sliceOccupancy(
decodeInterval,
roomOccupancy.occupancy.buffer
);
// Decode the occupancy data
occupancyList.push(...RoomOccupancyList.decodeOccupancyData(new Uint8Array(decodeSlice), decodeSliceStart, this.granularity, room));
@ -102,6 +99,36 @@ export class RoomOccupancyList {
return occupancyList;
}
/**
* Slice the important parts of the occupancy list for a given time range.
* @param from the start of the time range.
* @param to the end of the time range.
* @returns a new occupancy byte array with the starting time of the first byte
* @throws an error, if the selected time range is outside of the occupancy list.
*/
private sliceOccupancy(decodeInterval : NormalizedInterval, occupancy : Uint8Array) : {decodeSliceStart: Date, decodeSlice: Uint8Array} {
// Calculate the slice of bytes, that are needed to decode the requested time range
// Note: differenceInMinutes calculates (left - right)
let minutesFromStart = differenceInMinutes(decodeInterval.start, this.start);
let minutesToEnd = differenceInMinutes(decodeInterval.end, this.start);
let firstByte = Math.floor(minutesFromStart / this.granularity / 8);
let lastByte = Math.ceil(minutesToEnd / this.granularity / 8);
// check if firstByte and lastByte are within the bounds of the occupancy array and throw an error if not
if (
firstByte < 0 || firstByte >= occupancy.length ||
lastByte < 0 || lastByte > occupancy.length
) {
throw new Error("Requested time range is outside of the occupancy list.");
}
let decodeSliceStart = addMinutes(this.start, firstByte * 8 * this.granularity);
let decodeSlice = occupancy.buffer.slice(firstByte, lastByte);
return { decodeSliceStart, decodeSlice: new Uint8Array(decodeSlice) };
}
/**
* Get the decoded time interval within the current occupancy list.
* @returns the interval of the occupancy list.
@ -128,7 +155,7 @@ export class RoomOccupancyList {
// Iterate over all bits in the current byte
for (let bit_i = 0; bit_i < 8; bit_i++) {
let isOccupied = (byte & (1 << bit_i)) !== 0;
let isOccupied = (byte & (1 << (7-bit_i))) !== 0;
if (firstOccupancyBit === null && isOccupied) {
firstOccupancyBit = byte_i * 8 + bit_i;
@ -141,7 +168,8 @@ export class RoomOccupancyList {
startTime.toISOString(),
endTime.toISOString(),
room,
true
false,
false,
));
firstOccupancyBit = null;
@ -158,7 +186,8 @@ export class RoomOccupancyList {
startTime.toISOString(),
endTime.toISOString(),
room,
true
false,
false,
));
}
@ -195,6 +224,21 @@ export class RoomOccupancyList {
});
}
/**
* Generate RoomOccupancyList from plain JSON object.
* For performance no deep copy is made, reference attributes will be shared.
* @param json the JS object to read from.
* @returns a RoomOccupancyList object.
*/
public static fromJSON(json : any) : RoomOccupancyList {
return new RoomOccupancyList(
json.start,
json.granularity,
json.blocks,
json.rooms.map((room : any) => new RoomOccupancy(room.name, room.occupancy)
));
}
/**
* Shift the time forward to the start of the next day if it is after the end of the current day.
*
@ -205,7 +249,7 @@ export class RoomOccupancyList {
// if the time of date is after the end of the workday
if (isAfter(date, RoomOccupancyList.setTimeOfDay(date, END_OF_WORKDAY))) {
// shift the time to the start of the next day
return startOfDay(addDays(date,1));
return RoomOccupancyList.startOfDay(addDays(date,1));
} else {
return date;
}
@ -221,20 +265,41 @@ export class RoomOccupancyList {
// if the time of date is before the start of the workday
if (isBefore(date, RoomOccupancyList.setTimeOfDay(date, START_OF_WORKDAY))) {
// shift the time to the end of the previous day
return endOfDay(subDays(date,1));
return RoomOccupancyList.endOfDay(subDays(date,1));
} else {
return date;
}
}
/**
* Sets the date to the specified time after 00:00.
* Sets the date to the specified time after 00:00 in the current local timezone.
* @param date the date object to extract the day from.
* @param time the time as Duration after 00:00.
* @returns new date with changed time values.
*/
private static setTimeOfDay(date : Date, time : Duration) : Date {
return add(startOfDay(date), time);
return add(RoomOccupancyList.startOfDay(date), time);
}
/**
* Start of day in server timezone defined as const TIMEZONE.
* @param date
* @returns the start of the day.
*/
private static startOfDay(date : Date) : Date {
const dateInLocalTimezone = toZonedTime(date, TIMEZONE);
return fromZonedTime(startOfDay(dateInLocalTimezone), TIMEZONE);
}
/**
* End of day in server timezone defined as const TIMEZONE.
* @param date
* @returns the end of the day.
*/
private static endOfDay(date : Date) : Date {
const dateInLocalTimezone = toZonedTime(date, TIMEZONE);
return fromZonedTime(endOfDay(dateInLocalTimezone), TIMEZONE);
}
}