export class Time { constructor(obj) { this.hour = parseInt(obj.hour ?? 0); this.minute = parseInt(obj.minute ?? 0); this.second = parseInt(obj.second ?? 0); } static fromInputValue(str) { const parts = str.split(':').map(part => parseInt(part)); return new Time({hour: parts[0], minute: parts[1], second: parts[2]}); } asJsonObject() { let res = {} if (this.hour !== 0) res.hour = this.hour; if (this.minute !== 0) res.minute = this.minute; if (this.second !== 0) res.second = this.second; return res; } cmp(other) { if (this.hour < other.hour) return -1; if (this.hour > other.hour) return 1; if (this.minute < other.minute) return -1; if (this.minute > other.minute) return 1; if (this.second < other.second) return -1; if (this.second > other.second) return 1; return 0; } get durationSinceMidnight() { return new TimeRange({start_time: new Time({}), end_time: this}).duration; } plus(duration) { const total = this.durationSinceMidnight.total_seconds + duration.total_seconds; const second = total % 60; const minute = Math.floor(total / 60) % 60; const hour = Math.floor(total / 60 / 60) % 24; return new Time({hour, minute, second}); } // TODO Just ignoring seconds? to12HourString() { let hour = this.hour % 12; if (hour === 0) hour = 12; return hour + ':' + this.minute.toString().padStart(2, '0'); } get inputValue() { return this.hour.toString().padStart(2, '0') + ':' + this.minute.toString().padStart(2, '0') + ':' + this.second.toString().padStart(2, '0'); } } export class Duration { constructor(obj) { if (obj.total_seconds) { this.total_seconds = obj.total_seconds; this.hour = Math.floor(obj.total_seconds / 60 / 60); this.minute = Math.floor(obj.total_seconds / 60) % 60; this.second = obj.total_seconds % 60; } else { this.hour = obj.hour ?? 0; this.minute = obj.minute ?? 0; this.second = obj.second ?? 0; this.total_seconds = ((this.hour * 60) + this.minute) * 60 + this.second; } } asJsonObject() { let res = {} if (this.hour !== 0) res.hour = this.hour; if (this.minute !== 0) res.minute = this.minute; if (this.second !== 0) res.second = this.second; return res; } dividedBy(other) { return this.total_seconds / other.total_seconds; } times(num) { const total = this.total_seconds * num; const second = total % 60; const minute = Math.floor(total / 60) % 60; const hour = Math.floor(total / 60 / 60); return new Duration({hour, minute, second}); } toString() { let res = ""; if (this.hour) { res += this.hour + (this.hour === 1 ? " hour" : " hours "); } if (this.minute) { res += this.minute + (this.minute === 1 ? " minute" : " minutes "); } if (this.second) { res += this.second + (this.second === 1 ? " second" : " seconds"); } return this.total_seconds === 0 ? "0 seconds" : res.trimEnd(); } get inputValue() { return new Time({}).plus(this).inputValue; } } export class TimeRange { constructor(args) { this.start_time = args.start_time; this.end_time = args.end_time; } get duration() { return new Duration({ 'total_seconds': 60 * 60 * (this.end_time.hour - this.start_time.hour) + 60 * (this.end_time.minute - this.start_time.minute) + this.end_time.second - this.start_time.second, }); } overlaps(other) { return (other.start_time.cmp(this.start_time) <= 0 && this.start_time.cmp(other.end_time) < 0) || (this.start_time.cmp(other.start_time) <= 0 && other.start_time.cmp(this.end_time) < 0); } to12HourString() { return this.start_time.to12HourString() + '-' + this.end_time.to12HourString(); } } const dayDictionary = { 'M': 'Monday', 'T': 'Tuesday', 'W': 'Wednesday', 'R': 'Thursday', 'F': 'Friday', 'S': 'Saturday', 'U': 'Sunday', }; export function DayString(day) { return dayDictionary[day]; } export const allDays = Object.keys(dayDictionary); export function clearChildren(el) { while (el.firstChild) el.removeChild(el.lastChild); } // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set export function setDifference(setA, setB) { const _difference = new Set(setA); for (const elem of setB) { _difference.delete(elem); } return _difference; } export function setIntersection(setA, setB) { if (!(setA instanceof Set)) setA = new Set(setA); const _intersection = new Set(); for (const elem of setB) { if (setA.has(elem)) { _intersection.add(elem); } } return _intersection; } export function setEqual(setA, setB) { if (!(setA instanceof Set)) setA = new Set(setA); if (!(setB instanceof Set)) setB = new Set(setB); if (setA.size != setB.size) return false; return setDifference(setA, setB).size === 0; } // From https://stackoverflow.com/a/68227965 export function debounceAsync(task, ms) { function deferred() { let cancel, promise = new Promise((resolve, reject) => { cancel = reject setTimeout(resolve, ms) }) return { promise, cancel } } let t = { promise: null, cancel: _ => void 0 } return async (...args) => { try { t.cancel() t = deferred() await t.promise await task(...args) } catch (_) { /* prevent memory leak */ } } }