2021-01-02 16:04:16 +01:00
|
|
|
import {Utils} from "../../Utils";
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-02 19:00:24 +02:00
|
|
|
export interface OpeningHour {
|
2020-10-04 01:04:46 +02:00
|
|
|
weekday: number, // 0 is monday, 1 is tuesday, ...
|
2020-10-02 19:00:24 +02:00
|
|
|
startHour: number,
|
|
|
|
startMinutes: number,
|
|
|
|
endHour: number,
|
|
|
|
endMinutes: number
|
|
|
|
}
|
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
export class OH {
|
2020-10-04 12:55:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
private static readonly days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
|
|
|
|
private static readonly daysIndexed = {
|
|
|
|
mo: 0,
|
|
|
|
tu: 1,
|
|
|
|
we: 2,
|
|
|
|
th: 3,
|
|
|
|
fr: 4,
|
|
|
|
sa: 5,
|
|
|
|
su: 6
|
|
|
|
}
|
|
|
|
|
2020-10-08 19:03:00 +02:00
|
|
|
public static hhmm(h: number, m: number): string {
|
|
|
|
if (h == 24) {
|
|
|
|
return "00:00";
|
|
|
|
}
|
|
|
|
return Utils.TwoDigits(h) + ":" + Utils.TwoDigits(m);
|
|
|
|
}
|
|
|
|
|
2020-10-04 12:55:44 +02:00
|
|
|
public static ToString(ohs: OpeningHour[]) {
|
2020-10-06 01:37:02 +02:00
|
|
|
if (ohs.length == 0) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
const partsPerWeekday: string [][] = [[], [], [], [], [], [], []];
|
2020-10-04 12:55:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
for (const oh of ohs) {
|
2020-10-08 19:03:00 +02:00
|
|
|
partsPerWeekday[oh.weekday].push(OH.hhmm(oh.startHour, oh.startMinutes) + "-" + OH.hhmm(oh.endHour, oh.endMinutes));
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const stringPerWeekday = partsPerWeekday.map(parts => parts.sort().join(", "));
|
|
|
|
|
|
|
|
const rules = [];
|
|
|
|
|
|
|
|
let rangeStart = 0;
|
|
|
|
let rangeEnd = 0;
|
|
|
|
|
|
|
|
function pushRule(){
|
|
|
|
const rule = stringPerWeekday[rangeStart];
|
|
|
|
if(rule === ""){
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (rangeStart == (rangeEnd - 1)) {
|
|
|
|
rules.push(
|
|
|
|
`${OH.days[rangeStart]} ${rule}`
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
rules.push(
|
|
|
|
`${OH.days[rangeStart]}-${OH.days[rangeEnd-1]} ${rule}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-10-06 02:09:09 +02:00
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
for (; rangeEnd < 7; rangeEnd++) {
|
|
|
|
|
|
|
|
if (stringPerWeekday[rangeStart] != stringPerWeekday[rangeEnd]) {
|
|
|
|
pushRule();
|
|
|
|
rangeStart = rangeEnd
|
|
|
|
}
|
|
|
|
|
2020-10-04 12:55:44 +02:00
|
|
|
}
|
2020-10-06 01:37:02 +02:00
|
|
|
pushRule();
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-08 19:03:00 +02:00
|
|
|
const oh = rules.join("; ")
|
|
|
|
if (oh === "Mo-Su 00:00-00:00") {
|
2020-10-06 02:09:09 +02:00
|
|
|
return "24/7"
|
|
|
|
}
|
|
|
|
return oh;
|
2020-10-04 12:55:44 +02:00
|
|
|
}
|
|
|
|
|
2020-10-04 01:04:46 +02:00
|
|
|
/**
|
|
|
|
* Merge duplicate opening-hour element in place.
|
|
|
|
* Returns true if something changed
|
|
|
|
* @param ohs
|
|
|
|
* @constructor
|
|
|
|
*/
|
|
|
|
public static MergeTimes(ohs: OpeningHour[]): OpeningHour[] {
|
|
|
|
const queue = [...ohs];
|
|
|
|
const newList = [];
|
|
|
|
while (queue.length > 0) {
|
|
|
|
let maybeAdd = queue.pop();
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-04 01:04:46 +02:00
|
|
|
let doAddEntry = true;
|
|
|
|
if(maybeAdd.weekday == undefined){
|
|
|
|
doAddEntry = false;
|
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-04 01:04:46 +02:00
|
|
|
for (let i = newList.length - 1; i >= 0 && doAddEntry; i--) {
|
|
|
|
let guard = newList[i];
|
|
|
|
if (maybeAdd.weekday != guard.weekday) {
|
|
|
|
// Not the same day
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
if (OH.startTimeLiesInRange(maybeAdd, guard) && OH.endTimeLiesInRange(maybeAdd, guard)) {
|
2020-10-04 01:04:46 +02:00
|
|
|
// Guard fully covers 'maybeAdd': we can safely ignore maybeAdd
|
|
|
|
doAddEntry = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
if (OH.startTimeLiesInRange(guard, maybeAdd) && OH.endTimeLiesInRange(guard, maybeAdd)) {
|
2020-10-04 01:04:46 +02:00
|
|
|
// 'maybeAdd' fully covers Guard - the guard is killed
|
|
|
|
newList.splice(i, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
if (OH.startTimeLiesInRange(maybeAdd, guard) || OH.endTimeLiesInRange(maybeAdd, guard)
|
|
|
|
|| OH.startTimeLiesInRange(guard, maybeAdd) || OH.endTimeLiesInRange(guard, maybeAdd)) {
|
2020-10-04 01:04:46 +02:00
|
|
|
// At this point, the maybeAdd overlaps the guard: we should extend the guard and retest it
|
|
|
|
newList.splice(i, 1);
|
|
|
|
let startHour = guard.startHour;
|
|
|
|
let startMinutes = guard.startMinutes;
|
2020-10-06 01:37:02 +02:00
|
|
|
if (OH.startTime(maybeAdd) < OH.startTime(guard)) {
|
2020-10-04 01:04:46 +02:00
|
|
|
startHour = maybeAdd.startHour;
|
|
|
|
startMinutes = maybeAdd.startMinutes;
|
|
|
|
}
|
|
|
|
|
|
|
|
let endHour = guard.endHour;
|
|
|
|
let endMinutes = guard.endMinutes;
|
2020-10-06 01:37:02 +02:00
|
|
|
if (OH.endTime(maybeAdd) > OH.endTime(guard)) {
|
2020-10-04 01:04:46 +02:00
|
|
|
endHour = maybeAdd.endHour;
|
|
|
|
endMinutes = maybeAdd.endMinutes;
|
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-04 01:04:46 +02:00
|
|
|
queue.push({
|
|
|
|
startHour: startHour,
|
|
|
|
startMinutes: startMinutes,
|
|
|
|
endHour:endHour,
|
|
|
|
endMinutes:endMinutes,
|
|
|
|
weekday: guard.weekday
|
|
|
|
});
|
|
|
|
|
|
|
|
doAddEntry = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
if (doAddEntry) {
|
|
|
|
newList.push(maybeAdd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// New list can only differ from the old list by merging entries
|
|
|
|
// This means that the list is changed only if the lengths are different.
|
|
|
|
// If the lengths are the same, we might just as well return the old list and be a bit more stable
|
|
|
|
if (newList.length !== ohs.length) {
|
|
|
|
return newList;
|
|
|
|
} else {
|
|
|
|
return ohs;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-10-08 19:03:00 +02:00
|
|
|
public static startTime(oh: OpeningHour): number {
|
2020-10-04 01:04:46 +02:00
|
|
|
return oh.startHour + oh.startMinutes / 60;
|
|
|
|
}
|
|
|
|
|
2020-10-08 19:03:00 +02:00
|
|
|
public static endTime(oh: OpeningHour): number {
|
2020-10-04 01:04:46 +02:00
|
|
|
return oh.endHour + oh.endMinutes / 60;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static startTimeLiesInRange(checked: OpeningHour, mightLieIn: OpeningHour) {
|
2020-10-06 01:37:02 +02:00
|
|
|
return OH.startTime(mightLieIn) <= OH.startTime(checked) &&
|
|
|
|
OH.startTime(checked) <= OH.endTime(mightLieIn)
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static endTimeLiesInRange(checked: OpeningHour, mightLieIn: OpeningHour) {
|
2020-10-06 01:37:02 +02:00
|
|
|
return OH.startTime(mightLieIn) <= OH.endTime(checked) &&
|
|
|
|
OH.endTime(checked) <= OH.endTime(mightLieIn)
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
private static parseHHMM(hhmm: string): { hours: number, minutes: number } {
|
2020-10-08 19:03:00 +02:00
|
|
|
if(hhmm === undefined || hhmm == null){
|
|
|
|
return null;
|
|
|
|
}
|
2020-10-06 01:37:02 +02:00
|
|
|
const spl = hhmm.trim().split(":");
|
2020-10-08 19:03:00 +02:00
|
|
|
if(spl.length != 2){
|
|
|
|
return null;
|
|
|
|
}
|
2021-04-18 14:37:16 +02:00
|
|
|
const hm = {hours: Number(spl[0].trim()), minutes: Number(spl[1].trim())};
|
|
|
|
if(isNaN(hm.hours) || isNaN(hm.minutes) ){
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return hm;
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
|
|
|
|
2020-10-08 19:03:00 +02:00
|
|
|
public static parseHHMMRange(hhmmhhmm: string): {
|
2020-10-06 01:37:02 +02:00
|
|
|
startHour: number,
|
|
|
|
startMinutes: number,
|
|
|
|
endHour: number,
|
|
|
|
endMinutes: number
|
|
|
|
} {
|
2020-10-08 19:03:00 +02:00
|
|
|
if (hhmmhhmm == "off") {
|
2020-10-06 02:09:09 +02:00
|
|
|
return null;
|
|
|
|
}
|
2020-10-08 19:03:00 +02:00
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
const timings = hhmmhhmm.split("-");
|
|
|
|
const start = OH.parseHHMM(timings[0])
|
|
|
|
const end = OH.parseHHMM(timings[1]);
|
|
|
|
return {
|
|
|
|
startHour: start.hours,
|
|
|
|
startMinutes: start.minutes,
|
|
|
|
endHour: end.hours,
|
|
|
|
endMinutes: end.minutes
|
2020-10-04 12:55:44 +02:00
|
|
|
}
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
private static ParseHhmmRanges(hhmms: string): {
|
|
|
|
startHour: number,
|
|
|
|
startMinutes: number,
|
|
|
|
endHour: number,
|
|
|
|
endMinutes: number
|
|
|
|
}[] {
|
2020-10-08 19:03:00 +02:00
|
|
|
if (hhmms === "off") {
|
|
|
|
return [];
|
|
|
|
}
|
2020-10-06 01:37:02 +02:00
|
|
|
return hhmms.split(",")
|
|
|
|
.map(s => s.trim())
|
|
|
|
.filter(str => str !== "")
|
|
|
|
.map(OH.parseHHMMRange)
|
2020-10-06 02:09:09 +02:00
|
|
|
.filter(v => v != null)
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
private static ParseWeekday(weekday: string): number {
|
|
|
|
return OH.daysIndexed[weekday.trim().toLowerCase()];
|
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
private static ParseWeekdayRange(weekdays: string): number[] {
|
|
|
|
const split = weekdays.split("-");
|
|
|
|
if (split.length == 1) {
|
2020-10-08 19:03:00 +02:00
|
|
|
const parsed = OH.ParseWeekday(weekdays);
|
|
|
|
if(parsed == null){
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return [parsed];
|
2020-10-06 01:37:02 +02:00
|
|
|
} else if (split.length == 2) {
|
|
|
|
let start = OH.ParseWeekday(split[0]);
|
|
|
|
let end = OH.ParseWeekday(split[1]);
|
2020-10-08 19:03:00 +02:00
|
|
|
if ((start ?? null) === null || (end ?? null) === null) {
|
|
|
|
return null;
|
|
|
|
}
|
2020-10-06 01:37:02 +02:00
|
|
|
let range = [];
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
|
|
range.push(i);
|
2020-10-04 12:55:44 +02:00
|
|
|
}
|
2020-10-06 01:37:02 +02:00
|
|
|
return range;
|
|
|
|
} else {
|
2020-10-08 19:03:00 +02:00
|
|
|
return null;
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
private static ParseWeekdayRanges(weekdays: string): number[] {
|
|
|
|
let ranges = [];
|
|
|
|
let split = weekdays.split(",");
|
|
|
|
for (const weekday of split) {
|
2020-10-08 19:03:00 +02:00
|
|
|
const parsed = OH.ParseWeekdayRange(weekday)
|
|
|
|
if (parsed === undefined || parsed === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
ranges.push(...parsed);
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
|
|
|
return ranges;
|
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-06 01:37:02 +02:00
|
|
|
private static multiply(weekdays: number[], timeranges: { startHour: number, startMinutes: number, endHour: number, endMinutes: number }[]) {
|
2020-10-08 19:03:00 +02:00
|
|
|
if ((weekdays ?? null) == null || (timeranges ?? null) == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2020-10-06 01:37:02 +02:00
|
|
|
const ohs: OpeningHour[] = []
|
|
|
|
for (const timerange of timeranges) {
|
|
|
|
for (const weekday of weekdays) {
|
|
|
|
ohs.push({
|
2020-10-04 12:55:44 +02:00
|
|
|
weekday: weekday,
|
2020-10-06 01:37:02 +02:00
|
|
|
startHour: timerange.startHour, startMinutes: timerange.startMinutes,
|
|
|
|
endHour: timerange.endHour, endMinutes: timerange.endMinutes,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ohs;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static ParseRule(rule: string): OpeningHour[] {
|
2020-10-08 19:03:00 +02:00
|
|
|
try {
|
|
|
|
if (rule.trim() == "24/7") {
|
|
|
|
return OH.multiply([0, 1, 2, 3, 4, 5, 6], [{
|
|
|
|
startHour: 0,
|
|
|
|
startMinutes: 0,
|
|
|
|
endHour: 24,
|
|
|
|
endMinutes: 0
|
|
|
|
}]);
|
|
|
|
}
|
2020-10-06 02:09:09 +02:00
|
|
|
|
2020-10-08 19:03:00 +02:00
|
|
|
const split = rule.trim().replace(/, */g, ",").split(" ");
|
|
|
|
if (split.length == 1) {
|
|
|
|
// First, try to parse this rule as a rule without weekdays
|
|
|
|
let timeranges = OH.ParseHhmmRanges(rule);
|
|
|
|
let weekdays = [0, 1, 2, 3, 4, 5, 6];
|
|
|
|
return OH.multiply(weekdays, timeranges);
|
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-08 19:03:00 +02:00
|
|
|
if (split.length == 2) {
|
|
|
|
const weekdays = OH.ParseWeekdayRanges(split[0]);
|
|
|
|
const timeranges = OH.ParseHhmmRanges(split[1]);
|
|
|
|
return OH.multiply(weekdays, timeranges);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
} catch (e) {
|
|
|
|
console.log("Could not parse weekday rule ", rule);
|
|
|
|
return null;
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static Parse(rules: string) {
|
|
|
|
if (rules === undefined || rules === "") {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
|
|
|
|
const ohs = []
|
|
|
|
|
|
|
|
const split = rules.split(";");
|
|
|
|
|
|
|
|
for (const rule of split) {
|
|
|
|
if(rule === ""){
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
try {
|
2020-10-08 19:03:00 +02:00
|
|
|
const parsed = OH.ParseRule(rule)
|
|
|
|
if (parsed !== null) {
|
|
|
|
ohs.push(...parsed);
|
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
} catch (e) {
|
2020-10-06 01:37:02 +02:00
|
|
|
console.error("Could not parse ", rule, ": ", e)
|
2020-10-04 12:55:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ohs;
|
|
|
|
}
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
|
|
|
|