2022-09-08 21:40:48 +02:00
|
|
|
import { Utils } from "../../Utils"
|
|
|
|
import opening_hours from "opening_hours"
|
2023-12-04 03:32:25 +01:00
|
|
|
import { Store } from "../../Logic/UIEventSource"
|
2023-12-15 18:14:21 +01:00
|
|
|
import { Translation, TypedTranslation } from "../i18n/Translation"
|
|
|
|
import Translations from "../i18n/Translations"
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-02 19:00:24 +02:00
|
|
|
export interface OpeningHour {
|
2022-09-08 21:40:48 +02:00
|
|
|
weekday: number // 0 is monday, 1 is tuesday, ...
|
|
|
|
startHour: number
|
|
|
|
startMinutes: number
|
|
|
|
endHour: number
|
2020-10-02 19:00:24 +02:00
|
|
|
endMinutes: number
|
|
|
|
}
|
|
|
|
|
2023-12-15 18:14:21 +01:00
|
|
|
export interface OpeningRange {
|
|
|
|
isOpen: boolean
|
|
|
|
isSpecial: boolean
|
|
|
|
comment: string
|
|
|
|
startDate: Date
|
|
|
|
endDate: Date
|
|
|
|
}
|
|
|
|
|
2021-06-16 14:23:53 +02:00
|
|
|
/**
|
|
|
|
* Various utilities manipulating opening hours
|
|
|
|
*/
|
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,
|
2024-04-05 17:49:31 +02:00
|
|
|
su: 6
|
2020-10-04 12:55:44 +02:00
|
|
|
}
|
|
|
|
|
2020-10-08 19:03:00 +02:00
|
|
|
public static hhmm(h: number, m: number): string {
|
|
|
|
if (h == 24) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return "00:00"
|
2020-10-08 19:03:00 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
return Utils.TwoDigits(h) + ":" + Utils.TwoDigits(m)
|
2020-10-08 19:03:00 +02:00
|
|
|
}
|
|
|
|
|
2022-03-21 02:00:50 +01:00
|
|
|
/**
|
2022-09-08 21:40:48 +02:00
|
|
|
* const rules = [{weekday: 6,endHour: 17,endMinutes: 0,startHour: 13,startMinutes: 0},
|
|
|
|
* {weekday: 1,endHour: 12,endMinutes: 0,startHour: 10,startMinutes: 0}]
|
|
|
|
* OH.ToString(rules) // => "Tu 10:00-12:00; Su 13:00-17:00"
|
|
|
|
*
|
|
|
|
* const rules = [{weekday: 3,endHour: 17,endMinutes: 0,startHour: 13,startMinutes: 0}, {weekday: 1,endHour: 12,endMinutes: 0,startHour: 10,startMinutes: 0}]
|
|
|
|
* OH.ToString(rules) // => "Tu 10:00-12:00; Th 13:00-17:00"
|
|
|
|
*
|
|
|
|
* const rules = [ { weekday: 1, endHour: 17, endMinutes: 0, startHour: 13, startMinutes: 0 }, { weekday: 1, endHour: 12, endMinutes: 0, startHour: 10, startMinutes: 0 }]);
|
|
|
|
* OH.ToString(rules) // => "Tu 10:00-12:00, 13:00-17:00"
|
|
|
|
*
|
|
|
|
* const rules = [ { weekday: 0, endHour: 12, endMinutes: 0, startHour: 10, startMinutes: 0 }, { weekday: 0, endHour: 17, endMinutes: 0, startHour: 13, startMinutes: 0}, { weekday: 1, endHour: 17, endMinutes: 0, startHour: 13, startMinutes: 0 }, { weekday: 1, endHour: 12, endMinutes: 0, startHour: 10, startMinutes: 0 }];
|
|
|
|
* OH.ToString(rules) // => "Mo-Tu 10:00-12:00, 13:00-17:00"
|
|
|
|
*
|
2022-03-21 02:00:50 +01:00
|
|
|
* // should merge overlapping opening hours
|
|
|
|
* const timerange0 = {weekday: 1, endHour: 23, endMinutes: 30, startHour: 23, startMinutes: 0 }
|
|
|
|
* const touchingTimeRange = { weekday: 1, endHour: 0, endMinutes: 0, startHour: 23, startMinutes: 30 }
|
|
|
|
* OH.ToString(OH.MergeTimes([timerange0, touchingTimeRange])) // => "Tu 23:00-00:00"
|
|
|
|
*
|
|
|
|
* // should merge touching opening hours
|
|
|
|
* const timerange0 = {weekday: 1, endHour: 23, endMinutes: 30, startHour: 23, startMinutes: 0 }
|
|
|
|
* const overlappingTimeRange = { weekday: 1, endHour: 24, endMinutes: 0, startHour: 23, startMinutes: 30 }
|
|
|
|
* OH.ToString(OH.MergeTimes([timerange0, overlappingTimeRange])) // => "Tu 23:00-00:00"
|
2022-09-08 21:40:48 +02:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
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) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return ""
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
const partsPerWeekday: string[][] = [[], [], [], [], [], [], []]
|
2020-10-04 12:55:44 +02:00
|
|
|
|
|
|
|
for (const oh of ohs) {
|
2022-09-08 21:40:48 +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
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
const stringPerWeekday = partsPerWeekday.map((parts) => parts.sort().join(", "))
|
2020-10-06 01:37:02 +02:00
|
|
|
|
2024-04-05 17:49:31 +02:00
|
|
|
const rules: string[] = []
|
2020-10-06 01:37:02 +02:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
let rangeStart = 0
|
|
|
|
let rangeEnd = 0
|
2021-06-16 16:39:48 +02:00
|
|
|
|
|
|
|
function pushRule() {
|
2022-09-08 21:40:48 +02:00
|
|
|
const rule = stringPerWeekday[rangeStart]
|
2021-06-16 16:39:48 +02:00
|
|
|
if (rule === "") {
|
2022-09-08 21:40:48 +02:00
|
|
|
return
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
if (rangeStart == rangeEnd - 1) {
|
|
|
|
rules.push(`${OH.days[rangeStart]} ${rule}`)
|
2020-10-06 01:37:02 +02:00
|
|
|
} else {
|
2022-09-08 21:40:48 +02:00
|
|
|
rules.push(`${OH.days[rangeStart]}-${OH.days[rangeEnd - 1]} ${rule}`)
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
|
|
|
}
|
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]) {
|
2022-09-08 21:40:48 +02:00
|
|
|
pushRule()
|
2020-10-06 01:37:02 +02:00
|
|
|
rangeStart = rangeEnd
|
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
pushRule()
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2024-04-05 17:49:31 +02:00
|
|
|
if (rules.length === 1) {
|
|
|
|
const rule = rules[0]
|
|
|
|
if (rule === "Mo-Su 00:00-00:00") {
|
|
|
|
return "24/7"
|
|
|
|
}
|
|
|
|
if (rule.startsWith("Mo-Su ")) {
|
|
|
|
return rule.substring("Mo-Su ".length)
|
|
|
|
}
|
2020-10-06 02:09:09 +02:00
|
|
|
}
|
2024-04-05 17:49:31 +02:00
|
|
|
|
|
|
|
return rules.join("; ")
|
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
|
2022-09-08 21:40:48 +02:00
|
|
|
*
|
2022-03-21 02:00:50 +01:00
|
|
|
* // should merge overlapping opening hours
|
|
|
|
* const oh1: OpeningHour = { weekday: 0, startHour: 10, startMinutes: 0, endHour: 11, endMinutes: 0 };
|
|
|
|
* const oh0: OpeningHour = { weekday: 0, startHour: 10, startMinutes: 30, endHour: 12, endMinutes: 0 };
|
|
|
|
* OH.MergeTimes([oh0, oh1]) // => [{ weekday: 0, startHour: 10, startMinutes: 0, endHour: 12, endMinutes: 0 }]
|
|
|
|
*
|
|
|
|
* // should merge touching opening hours
|
|
|
|
* const oh1: OpeningHour = { weekday: 0, startHour: 10, startMinutes: 0, endHour: 11, endMinutes: 0 };
|
|
|
|
* const oh0: OpeningHour = { weekday: 0, startHour: 11, startMinutes: 0, endHour: 12, endMinutes: 0 };
|
|
|
|
* OH.MergeTimes([oh0, oh1]) // => [{ weekday: 0, startHour: 10, startMinutes: 0, endHour: 12, endMinutes: 0 }]
|
2024-01-13 03:42:07 +01:00
|
|
|
*
|
|
|
|
* // should merge touching opening hours spanning days
|
|
|
|
* const oh0: OpeningHour = { weekday: 0, startHour: 10, startMinutes: 0, endHour: 24, endMinutes: 0 };
|
|
|
|
* const oh1: OpeningHour = { weekday: 1, startHour: 0, startMinutes: 0, endHour: 12, endMinutes: 0 };
|
|
|
|
* OH.MergeTimes([oh0, oh1]) // => [{ weekday: 0, startHour: 10, startMinutes: 0, endHour: 24, endMinutes: 0 }, { weekday: 1, startHour: 0, startMinutes: 0, endHour: 12, endMinutes: 0 }]
|
2020-10-04 01:04:46 +02:00
|
|
|
*/
|
|
|
|
public static MergeTimes(ohs: OpeningHour[]): OpeningHour[] {
|
2022-09-08 21:40:48 +02:00
|
|
|
const queue = ohs.map((oh) => {
|
2021-06-16 16:39:48 +02:00
|
|
|
if (oh.endHour === 0 && oh.endMinutes === 0) {
|
|
|
|
const newOh = {
|
2024-04-05 17:49:31 +02:00
|
|
|
...oh
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
newOh.endHour = 24
|
|
|
|
return newOh
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
return oh
|
|
|
|
})
|
|
|
|
const newList = []
|
2020-10-04 01:04:46 +02:00
|
|
|
while (queue.length > 0) {
|
2024-04-05 17:49:31 +02:00
|
|
|
const maybeAdd = queue.pop()
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
let doAddEntry = true
|
2021-06-16 16:39:48 +02:00
|
|
|
if (maybeAdd.weekday == undefined) {
|
2022-09-08 21:40:48 +02:00
|
|
|
doAddEntry = false
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
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--) {
|
2022-09-08 21:40:48 +02:00
|
|
|
let guard = newList[i]
|
2020-10-04 01:04:46 +02:00
|
|
|
if (maybeAdd.weekday != guard.weekday) {
|
|
|
|
// Not the same day
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:48 +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
|
2022-09-08 21:40:48 +02:00
|
|
|
doAddEntry = false
|
|
|
|
break
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:48 +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
|
2022-09-08 21:40:48 +02:00
|
|
|
newList.splice(i, 1)
|
|
|
|
break
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:48 +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
|
2022-09-08 21:40:48 +02:00
|
|
|
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)) {
|
2022-09-08 21:40:48 +02:00
|
|
|
startHour = maybeAdd.startHour
|
|
|
|
startMinutes = maybeAdd.startMinutes
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
let endHour = guard.endHour
|
|
|
|
let endMinutes = guard.endMinutes
|
2020-10-06 01:37:02 +02:00
|
|
|
if (OH.endTime(maybeAdd) > OH.endTime(guard)) {
|
2022-09-08 21:40:48 +02:00
|
|
|
endHour = maybeAdd.endHour
|
|
|
|
endMinutes = maybeAdd.endMinutes
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-04 01:04:46 +02:00
|
|
|
queue.push({
|
|
|
|
startHour: startHour,
|
|
|
|
startMinutes: startMinutes,
|
2021-06-16 16:39:48 +02:00
|
|
|
endHour: endHour,
|
|
|
|
endMinutes: endMinutes,
|
2024-04-05 17:49:31 +02:00
|
|
|
weekday: guard.weekday
|
2022-09-08 21:40:48 +02:00
|
|
|
})
|
2020-10-04 01:04:46 +02:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
doAddEntry = false
|
|
|
|
break
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (doAddEntry) {
|
2022-09-08 21:40:48 +02:00
|
|
|
newList.push(maybeAdd)
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2024-01-13 03:42:07 +01:00
|
|
|
newList.sort((a, b) => b.weekday - a.weekday)
|
2022-09-08 21:40:48 +02:00
|
|
|
return newList
|
2020-10-04 01:04:46 +02:00
|
|
|
} else {
|
2022-09-08 21:40:48 +02:00
|
|
|
return ohs
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-16 14:23:53 +02:00
|
|
|
/**
|
|
|
|
* Gives the number of hours since the start of day.
|
|
|
|
* E.g.
|
|
|
|
* startTime({startHour: 9, startMinuts: 15}) == 9.25
|
|
|
|
* @param oh
|
|
|
|
*/
|
2020-10-08 19:03:00 +02:00
|
|
|
public static startTime(oh: OpeningHour): number {
|
2022-09-08 21:40:48 +02:00
|
|
|
return oh.startHour + oh.startMinutes / 60
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
|
|
|
|
2020-10-08 19:03:00 +02:00
|
|
|
public static endTime(oh: OpeningHour): number {
|
2022-09-08 21:40:48 +02:00
|
|
|
return oh.endHour + oh.endMinutes / 60
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static startTimeLiesInRange(checked: OpeningHour, mightLieIn: OpeningHour) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return (
|
|
|
|
OH.startTime(mightLieIn) <= OH.startTime(checked) &&
|
2020-10-06 01:37:02 +02:00
|
|
|
OH.startTime(checked) <= OH.endTime(mightLieIn)
|
2022-09-08 21:40:48 +02:00
|
|
|
)
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static endTimeLiesInRange(checked: OpeningHour, mightLieIn: OpeningHour) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return (
|
|
|
|
OH.startTime(mightLieIn) <= OH.endTime(checked) &&
|
2020-10-06 01:37:02 +02:00
|
|
|
OH.endTime(checked) <= OH.endTime(mightLieIn)
|
2022-09-08 21:40:48 +02:00
|
|
|
)
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-08 19:03:00 +02:00
|
|
|
public static parseHHMMRange(hhmmhhmm: string): {
|
2022-09-08 21:40:48 +02:00
|
|
|
startHour: number
|
|
|
|
startMinutes: number
|
|
|
|
endHour: number
|
2020-10-06 01:37:02 +02:00
|
|
|
endMinutes: number
|
|
|
|
} {
|
2020-10-08 19:03:00 +02:00
|
|
|
if (hhmmhhmm == "off") {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2020-10-06 02:09:09 +02:00
|
|
|
}
|
2020-10-08 19:03:00 +02:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
const timings = hhmmhhmm.split("-")
|
2020-10-06 01:37:02 +02:00
|
|
|
const start = OH.parseHHMM(timings[0])
|
2022-09-08 21:40:48 +02:00
|
|
|
const end = OH.parseHHMM(timings[1])
|
2020-10-06 01:37:02 +02:00
|
|
|
return {
|
|
|
|
startHour: start.hours,
|
|
|
|
startMinutes: start.minutes,
|
|
|
|
endHour: end.hours,
|
2024-04-05 17:49:31 +02:00
|
|
|
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
|
|
|
|
2022-03-15 13:40:23 +01:00
|
|
|
/**
|
|
|
|
* Converts an OH-syntax rule into an object
|
2022-09-08 21:40:48 +02:00
|
|
|
*
|
|
|
|
*
|
2022-03-15 13:40:23 +01:00
|
|
|
* const rules = OH.ParsePHRule("PH 12:00-17:00")
|
|
|
|
* rules.mode // => " "
|
|
|
|
* rules.start // => "12:00"
|
|
|
|
* rules.end // => "17:00"
|
2022-09-08 21:40:48 +02:00
|
|
|
*
|
2022-03-15 13:40:23 +01:00
|
|
|
* OH.ParseRule("PH 12:00-17:00") // => null
|
|
|
|
* OH.ParseRule("Th[-1] off") // => null
|
2022-09-08 21:40:48 +02:00
|
|
|
*
|
2022-03-15 13:40:23 +01:00
|
|
|
* const rules = OH.Parse("24/7");
|
|
|
|
* rules.length // => 7
|
|
|
|
* rules[0].startHour // => 0
|
|
|
|
* OH.ToString(rules) // => "24/7"
|
2022-09-08 21:40:48 +02:00
|
|
|
*
|
2022-03-18 13:04:12 +01:00
|
|
|
* const rules = OH.ParseRule("11:00-19:00");
|
|
|
|
* rules.length // => 7
|
|
|
|
* rules[0].weekday // => 0
|
|
|
|
* rules[0].startHour // => 11
|
|
|
|
* rules[3].endHour // => 19
|
2022-09-08 21:40:48 +02:00
|
|
|
*
|
2022-03-18 13:04:12 +01:00
|
|
|
* const rules = OH.ParseRule("Mo-Th 11:00-19:00");
|
|
|
|
* rules.length // => 4
|
|
|
|
* rules[0].weekday // => 0
|
|
|
|
* rules[0].startHour // => 11
|
|
|
|
* rules[3].endHour // => 19
|
2022-09-08 21:40:48 +02:00
|
|
|
*
|
2023-10-18 02:08:49 +02:00
|
|
|
* const rules = OH.ParseRule("Mo 20:00-02:00");
|
|
|
|
* rules.length // => 2
|
|
|
|
* rules[0].weekday // => 0
|
|
|
|
* rules[0].startHour // => 20
|
|
|
|
* rules[0].endHour // => 0
|
|
|
|
* rules[1].weekday // => 1
|
|
|
|
* rules[1].startHour // => 0
|
|
|
|
* rules[1].endHour // => 2
|
2024-01-13 03:42:07 +01:00
|
|
|
*
|
|
|
|
* const rules = OH.ParseRule("Mo 00:00-24:00")
|
|
|
|
* rules.length // => 1
|
|
|
|
* rules[0].weekday // => 0
|
|
|
|
* rules[0].startHour // => 0
|
|
|
|
* rules[0].endHour // => 24
|
2022-03-15 13:40:23 +01:00
|
|
|
*/
|
2020-10-06 01:37:02 +02:00
|
|
|
public static ParseRule(rule: string): OpeningHour[] {
|
2020-10-08 19:03:00 +02:00
|
|
|
try {
|
|
|
|
if (rule.trim() == "24/7") {
|
2022-09-08 21:40:48 +02:00
|
|
|
return OH.multiply(
|
|
|
|
[0, 1, 2, 3, 4, 5, 6],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
startHour: 0,
|
|
|
|
startMinutes: 0,
|
|
|
|
endHour: 24,
|
2024-04-05 17:49:31 +02:00
|
|
|
endMinutes: 0
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
]
|
|
|
|
)
|
2020-10-08 19:03:00 +02:00
|
|
|
}
|
2020-10-06 02:09:09 +02:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
const split = rule.trim().replace(/, */g, ",").split(" ")
|
2020-10-08 19:03:00 +02:00
|
|
|
if (split.length == 1) {
|
|
|
|
// First, try to parse this rule as a rule without weekdays
|
2022-09-08 21:40:48 +02:00
|
|
|
let timeranges = OH.ParseHhmmRanges(rule)
|
|
|
|
let weekdays = [0, 1, 2, 3, 4, 5, 6]
|
|
|
|
return OH.multiply(weekdays, timeranges)
|
2020-10-08 19:03:00 +02:00
|
|
|
}
|
2020-10-04 12:55:44 +02:00
|
|
|
|
2020-10-08 19:03:00 +02:00
|
|
|
if (split.length == 2) {
|
2022-09-08 21:40:48 +02:00
|
|
|
const weekdays = OH.ParseWeekdayRanges(split[0])
|
|
|
|
const timeranges = OH.ParseHhmmRanges(split[1])
|
|
|
|
return OH.multiply(weekdays, timeranges)
|
2020-10-08 19:03:00 +02:00
|
|
|
}
|
2024-04-05 17:49:31 +02:00
|
|
|
return []
|
2020-10-08 19:03:00 +02:00
|
|
|
} catch (e) {
|
2022-09-08 21:40:48 +02:00
|
|
|
console.log("Could not parse weekday rule ", rule)
|
2024-04-05 17:49:31 +02:00
|
|
|
return []
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-29 16:57:29 +02:00
|
|
|
/**
|
2022-09-08 21:40:48 +02:00
|
|
|
*
|
2022-06-29 16:57:29 +02:00
|
|
|
* OH.ParsePHRule("PH Off") // => {mode: "off"}
|
|
|
|
* OH.ParsePHRule("PH OPEN") // => {mode: "open"}
|
|
|
|
* OH.ParsePHRule("PH 10:00-12:00") // => {mode: " ", start: "10:00", end: "12:00"}
|
|
|
|
* OH.ParsePHRule(undefined) // => null
|
|
|
|
* OH.ParsePHRule(null) // => null
|
|
|
|
* OH.ParsePHRule("some random string") // => null
|
|
|
|
*/
|
2021-06-16 16:39:48 +02:00
|
|
|
public static ParsePHRule(str: string): {
|
2022-09-08 21:40:48 +02:00
|
|
|
mode: string
|
|
|
|
start?: string
|
2021-06-16 16:39:48 +02:00
|
|
|
end?: string
|
|
|
|
} {
|
2022-06-29 16:57:29 +02:00
|
|
|
if (str === undefined || str === null) {
|
2021-09-02 21:22:34 +02:00
|
|
|
return null
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
str = str.trim()
|
2021-06-16 16:39:48 +02:00
|
|
|
if (!str.startsWith("PH")) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
str = str.trim()
|
2022-06-29 16:57:29 +02:00
|
|
|
if (str.toLowerCase() === "ph off") {
|
2021-06-16 16:39:48 +02:00
|
|
|
return {
|
2024-04-05 17:49:31 +02:00
|
|
|
mode: "off"
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-29 16:57:29 +02:00
|
|
|
if (str.toLowerCase() === "ph open") {
|
2021-06-16 16:39:48 +02:00
|
|
|
return {
|
2024-04-05 17:49:31 +02:00
|
|
|
mode: "open"
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!str.startsWith("PH ")) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
try {
|
2022-09-08 21:40:48 +02:00
|
|
|
const timerange = OH.parseHHMMRange(str.substring(2))
|
2021-06-16 16:39:48 +02:00
|
|
|
if (timerange === null) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
mode: " ",
|
|
|
|
start: OH.hhmm(timerange.startHour, timerange.startMinutes),
|
2024-04-05 17:49:31 +02:00
|
|
|
end: OH.hhmm(timerange.endHour, timerange.endMinutes)
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
} catch (e) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
}
|
2020-10-06 01:37:02 +02:00
|
|
|
|
2022-01-26 21:40:38 +01:00
|
|
|
public static simplify(str: string): string {
|
2022-01-07 04:14:53 +01:00
|
|
|
return OH.ToString(OH.MergeTimes(OH.Parse(str)))
|
|
|
|
}
|
2022-01-26 21:40:38 +01:00
|
|
|
|
2022-06-05 02:24:14 +02:00
|
|
|
/**
|
|
|
|
* Parses a string into Opening Hours
|
|
|
|
*/
|
2022-01-26 21:40:38 +01:00
|
|
|
public static Parse(rules: string): OpeningHour[] {
|
2020-10-06 01:37:02 +02:00
|
|
|
if (rules === undefined || rules === "") {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
|
2022-01-26 21:40:38 +01:00
|
|
|
const ohs: OpeningHour[] = []
|
2020-10-06 01:37:02 +02:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
const split = rules.split(";")
|
2020-10-06 01:37:02 +02:00
|
|
|
|
|
|
|
for (const rule of split) {
|
2021-06-16 16:39:48 +02:00
|
|
|
if (rule === "") {
|
2022-09-08 21:40:48 +02:00
|
|
|
continue
|
2020-10-06 01:37:02 +02:00
|
|
|
}
|
|
|
|
try {
|
2020-10-08 19:03:00 +02:00
|
|
|
const parsed = OH.ParseRule(rule)
|
|
|
|
if (parsed !== null) {
|
2022-09-08 21:40:48 +02:00
|
|
|
ohs.push(...parsed)
|
2020-10-08 19:03:00 +02:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
return ohs
|
2020-10-04 12:55:44 +02:00
|
|
|
}
|
2021-06-16 14:23:53 +02:00
|
|
|
|
|
|
|
/*
|
2023-10-18 02:08:49 +02:00
|
|
|
This function converts a number of ranges (generated by OpeningHours.js) into all the hours of day that a change occurs.
|
|
|
|
E.g.
|
|
|
|
Monday, some business is opended from 9:00 till 17:00
|
|
|
|
Tuesday from 9:30 till 18:00
|
|
|
|
Wednesday from 9:30 till 12:30
|
|
|
|
This function will extract all those moments of change and will return 9:00, 9:30, 12:30, 17:00 and 18:00
|
|
|
|
This list will be sorted
|
|
|
|
*/
|
2022-09-08 21:40:48 +02:00
|
|
|
public static allChangeMoments(
|
|
|
|
ranges: {
|
|
|
|
isOpen: boolean
|
|
|
|
isSpecial: boolean
|
|
|
|
comment: string
|
|
|
|
startDate: Date
|
|
|
|
endDate: Date
|
|
|
|
}[][]
|
|
|
|
): [number[], string[]] {
|
2021-06-16 14:23:53 +02:00
|
|
|
const changeHours: number[] = []
|
2022-09-08 21:40:48 +02:00
|
|
|
const changeHourText: string[] = []
|
2021-06-16 16:39:48 +02:00
|
|
|
|
2021-06-16 14:23:53 +02:00
|
|
|
const extrachangeHours: number[] = []
|
2022-09-08 21:40:48 +02:00
|
|
|
const extrachangeHourText: string[] = []
|
2021-06-16 14:23:53 +02:00
|
|
|
|
|
|
|
for (const weekday of ranges) {
|
|
|
|
for (const range of weekday) {
|
|
|
|
if (!range.isOpen && !range.isSpecial) {
|
2022-09-08 21:40:48 +02:00
|
|
|
continue
|
2021-06-16 14:23:53 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
const startOfDay: Date = new Date(range.startDate)
|
|
|
|
startOfDay.setHours(0, 0, 0, 0)
|
2021-06-16 16:39:48 +02:00
|
|
|
|
2021-06-16 14:23:53 +02:00
|
|
|
// The number of seconds since the start of the day
|
|
|
|
// @ts-ignore
|
2022-09-08 21:40:48 +02:00
|
|
|
const changeMoment: number = (range.startDate - startOfDay) / 1000
|
2021-06-16 14:23:53 +02:00
|
|
|
if (changeHours.indexOf(changeMoment) < 0) {
|
2022-09-08 21:40:48 +02:00
|
|
|
changeHours.push(changeMoment)
|
|
|
|
changeHourText.push(
|
|
|
|
OH.hhmm(range.startDate.getHours(), range.startDate.getMinutes())
|
|
|
|
)
|
2021-06-16 14:23:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// The number of seconds till between the start of the day and closing
|
|
|
|
// @ts-ignore
|
2022-09-08 21:40:48 +02:00
|
|
|
let changeMomentEnd: number = (range.endDate - startOfDay) / 1000
|
2021-06-16 14:23:53 +02:00
|
|
|
if (changeMomentEnd >= 24 * 60 * 60) {
|
|
|
|
if (extrachangeHours.indexOf(changeMomentEnd) < 0) {
|
2022-09-08 21:40:48 +02:00
|
|
|
extrachangeHours.push(changeMomentEnd)
|
|
|
|
extrachangeHourText.push(
|
|
|
|
OH.hhmm(range.endDate.getHours(), range.endDate.getMinutes())
|
|
|
|
)
|
2021-06-16 14:23:53 +02:00
|
|
|
}
|
|
|
|
} else if (changeHours.indexOf(changeMomentEnd) < 0) {
|
2022-09-08 21:40:48 +02:00
|
|
|
changeHours.push(changeMomentEnd)
|
|
|
|
changeHourText.push(
|
|
|
|
OH.hhmm(range.endDate.getHours(), range.endDate.getMinutes())
|
|
|
|
)
|
2021-06-16 14:23:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note that 'changeHours' and 'changeHourText' will be more or less in sync - one is in numbers, the other in 'HH:MM' format.
|
|
|
|
// But both can be sorted without problem; they'll stay in sync
|
2022-09-08 21:40:48 +02:00
|
|
|
changeHourText.sort()
|
|
|
|
changeHours.sort()
|
|
|
|
extrachangeHourText.sort()
|
|
|
|
extrachangeHours.sort()
|
2021-06-16 16:39:48 +02:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
changeHourText.push(...extrachangeHourText)
|
|
|
|
changeHours.push(...extrachangeHours)
|
2021-06-16 14:23:53 +02:00
|
|
|
|
|
|
|
return [changeHours, changeHourText]
|
|
|
|
}
|
2023-12-15 18:14:21 +01:00
|
|
|
|
2023-12-04 03:32:25 +01:00
|
|
|
public static CreateOhObjectStore(
|
|
|
|
tags: Store<Record<string, string>>,
|
|
|
|
key: string = "opening_hours",
|
|
|
|
prefixToIgnore?: string,
|
|
|
|
postfixToIgnore?: string
|
|
|
|
): Store<opening_hours | undefined | "error"> {
|
|
|
|
prefixToIgnore ??= ""
|
|
|
|
postfixToIgnore ??= ""
|
|
|
|
const country = tags.map((tags) => tags._country)
|
|
|
|
return tags
|
|
|
|
.mapD((tags) => {
|
|
|
|
const value: string = tags[key]
|
|
|
|
if (value === undefined) {
|
|
|
|
return undefined
|
|
|
|
}
|
2021-06-16 14:23:53 +02:00
|
|
|
|
2023-12-04 03:32:25 +01:00
|
|
|
if (
|
|
|
|
(prefixToIgnore || postfixToIgnore) &&
|
|
|
|
value.startsWith(prefixToIgnore) &&
|
|
|
|
value.endsWith(postfixToIgnore)
|
|
|
|
) {
|
|
|
|
return value
|
|
|
|
.substring(prefixToIgnore.length, value.length - postfixToIgnore.length)
|
|
|
|
.trim()
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
})
|
|
|
|
.mapD(
|
|
|
|
(ohtext) => {
|
|
|
|
try {
|
|
|
|
return OH.CreateOhObject(<any>tags.data, ohtext, country.data)
|
|
|
|
} catch (e) {
|
|
|
|
return "error"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[country]
|
|
|
|
)
|
|
|
|
}
|
2023-12-15 18:14:21 +01:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
public static CreateOhObject(
|
2023-03-28 05:13:48 +02:00
|
|
|
tags: Record<string, string> & { _lat: number; _lon: number; _country?: string },
|
2023-12-04 03:32:25 +01:00
|
|
|
textToParse: string,
|
|
|
|
country?: string
|
2022-09-08 21:40:48 +02:00
|
|
|
) {
|
2022-04-28 00:32:15 +02:00
|
|
|
// noinspection JSPotentiallyInvalidConstructorUsage
|
2022-09-08 21:40:48 +02:00
|
|
|
return new opening_hours(
|
|
|
|
textToParse,
|
|
|
|
{
|
|
|
|
lat: tags._lat,
|
|
|
|
lon: tags._lon,
|
|
|
|
address: {
|
2023-12-04 03:32:25 +01:00
|
|
|
country_code: country.toLowerCase(),
|
2024-04-05 17:49:31 +02:00
|
|
|
state: undefined
|
|
|
|
}
|
2022-04-28 00:32:15 +02:00
|
|
|
},
|
2022-09-08 21:40:48 +02:00
|
|
|
<any>{ tag_key: "opening_hours" }
|
|
|
|
)
|
2022-04-28 00:32:15 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2023-12-15 18:14:21 +01:00
|
|
|
/**
|
|
|
|
* let ranges = <any> [
|
|
|
|
* [
|
|
|
|
* {
|
|
|
|
* "isSpecial": false,
|
|
|
|
* "isOpen": true,
|
|
|
|
* "startDate": new Date("2023-12-11T09:00:00.000Z"),
|
|
|
|
* "endDate": new Date("2023-12-11T12:30:00.000Z")
|
|
|
|
* },
|
|
|
|
* {
|
|
|
|
* "isSpecial": false,
|
|
|
|
* "isOpen": true,
|
|
|
|
* "startDate": new Date("2023-12-11T13:30:00.000Z"),
|
|
|
|
* "endDate": new Date("2023-12-11T18:00:00.000Z")
|
|
|
|
* }
|
|
|
|
* ],
|
|
|
|
* [
|
|
|
|
* {
|
|
|
|
* "isSpecial": false,
|
|
|
|
* "isOpen": true,
|
|
|
|
* "startDate": new Date("2023-12-12T09:00:00.000Z"),
|
|
|
|
* "endDate": new Date("2023-12-12T12:30:00.000Z")
|
|
|
|
* },
|
|
|
|
* {
|
|
|
|
* "isSpecial": false,
|
|
|
|
* "isOpen": true,
|
|
|
|
* "startDate": new Date("2023-12-12T13:30:00.000Z"),
|
|
|
|
* "endDate": new Date("2023-12-12T18:00:00.000Z")
|
|
|
|
* }
|
|
|
|
* ],
|
|
|
|
* [
|
|
|
|
* {
|
|
|
|
* "isSpecial": false,
|
|
|
|
* "isOpen": true,
|
|
|
|
* "startDate": new Date("2023-12-13T09:00:00.000Z"),
|
|
|
|
* "endDate": new Date("2023-12-13T12:30:00.000Z")
|
|
|
|
* },
|
|
|
|
* {
|
|
|
|
* "isSpecial": false,
|
|
|
|
* "isOpen": true,
|
|
|
|
* "startDate": new Date("2023-12-13T13:30:00.000Z"),
|
|
|
|
* "endDate": new Date("2023-12-13T18:00:00.000Z")
|
|
|
|
* }
|
|
|
|
* ],
|
|
|
|
* [
|
|
|
|
* {
|
|
|
|
* "isSpecial": false,
|
|
|
|
* "isOpen": true,
|
|
|
|
* "startDate": new Date("2023-12-14T09:00:00.000Z"),
|
|
|
|
* "endDate": new Date("2023-12-14T12:30:00.000Z")
|
|
|
|
* },
|
|
|
|
* {
|
|
|
|
* "isSpecial": false,
|
|
|
|
* "isOpen": true,
|
|
|
|
* "startDate": new Date("2023-12-14T13:30:00.000Z"),
|
|
|
|
* "endDate": new Date("2023-12-14T18:00:00.000Z")
|
|
|
|
* }
|
|
|
|
* ],
|
|
|
|
* [
|
|
|
|
* {
|
|
|
|
* "isSpecial": false,
|
|
|
|
* "isOpen": true,
|
|
|
|
* "startDate": new Date("2023-12-15T09:00:00.000Z"),
|
|
|
|
* "endDate": new Date("2023-12-15T12:30:00.000Z")
|
|
|
|
* },
|
|
|
|
* {
|
|
|
|
* "isSpecial": false,
|
|
|
|
* "isOpen": true,
|
|
|
|
* "startDate": new Date("2023-12-15T13:30:00.000Z"),
|
|
|
|
* "endDate": new Date("2023-12-15T18:00:00.000Z")
|
|
|
|
* }
|
|
|
|
* ],
|
|
|
|
* [],
|
|
|
|
* []
|
|
|
|
* ]
|
|
|
|
* OH.weekdaysIdentical(ranges, 0, 1) // => true
|
|
|
|
* OH.weekdaysIdentical(ranges, 0, 4) // => true
|
|
|
|
* OH.weekdaysIdentical(ranges, 4, 5) // => false
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs the opening-ranges for either this week, or for next week if there are no more openings this week
|
|
|
|
*/
|
|
|
|
public static createRangesForApplicableWeek(oh: opening_hours): {
|
|
|
|
ranges: OpeningRange[][]
|
|
|
|
startingMonday: Date
|
|
|
|
} {
|
|
|
|
const today = new Date()
|
|
|
|
today.setHours(0, 0, 0, 0)
|
|
|
|
const lastMonday = OH.getMondayBefore(today)
|
|
|
|
const nextSunday = new Date(lastMonday)
|
|
|
|
nextSunday.setDate(nextSunday.getDate() + 7)
|
|
|
|
|
|
|
|
if (!oh.getState() && !oh.getUnknown()) {
|
|
|
|
// POI is currently closed
|
|
|
|
const nextChange: Date = oh.getNextChange()
|
|
|
|
if (
|
|
|
|
// Shop isn't gonna open anymore in this timerange
|
|
|
|
nextSunday < nextChange &&
|
|
|
|
// And we are already in the weekend to show next week
|
|
|
|
(today.getDay() == 0 || today.getDay() == 6)
|
|
|
|
) {
|
|
|
|
// We move the range to next week!
|
|
|
|
lastMonday.setDate(lastMonday.getDate() + 7)
|
|
|
|
nextSunday.setDate(nextSunday.getDate() + 7)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We calculate the ranges when it is opened! */
|
|
|
|
return { startingMonday: lastMonday, ranges: OH.GetRanges(oh, lastMonday, nextSunday) }
|
|
|
|
}
|
2023-12-20 02:50:08 +01:00
|
|
|
|
2023-12-15 18:14:21 +01:00
|
|
|
public static weekdaysIdentical(openingRanges: OpeningRange[][], startday = 0, endday = 4) {
|
|
|
|
const monday = openingRanges[startday]
|
|
|
|
for (let i = startday + 1; i <= endday; i++) {
|
|
|
|
let weekday = openingRanges[i]
|
|
|
|
if (weekday.length !== monday.length) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for (let j = 0; j < weekday.length; j++) {
|
|
|
|
const openingRange = weekday[j]
|
|
|
|
const mondayRange = monday[j]
|
|
|
|
if (
|
|
|
|
openingRange.isOpen !== mondayRange.isOpen &&
|
|
|
|
openingRange.isSpecial !== mondayRange.isSpecial &&
|
|
|
|
openingRange.comment !== mondayRange.comment
|
|
|
|
) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
openingRange.startDate.toTimeString() !== mondayRange.startDate.toTimeString()
|
|
|
|
) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if (openingRange.endDate.toTimeString() !== mondayRange.endDate.toTimeString()) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculates when the business is opened (or on holiday) between two dates.
|
|
|
|
* Returns a matrix of ranges, where [0] is a list of ranges when it is opened on monday, [1] is a list of ranges for tuesday, ...
|
|
|
|
*/
|
|
|
|
public static GetRanges(oh: opening_hours, from: Date, to: Date): OpeningRange[][] {
|
2022-09-08 21:40:48 +02:00
|
|
|
const values = [[], [], [], [], [], [], []]
|
2021-06-16 14:23:53 +02:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
const start = new Date(from)
|
2021-06-16 14:23:53 +02:00
|
|
|
// We go one day more into the past, in order to force rendering of holidays in the start of the period
|
2022-09-08 21:40:48 +02:00
|
|
|
start.setDate(from.getDate() - 1)
|
2021-06-16 14:23:53 +02:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
const iterator = oh.getIterator(start)
|
2021-06-16 14:23:53 +02:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
let prevValue = undefined
|
2021-06-16 14:23:53 +02:00
|
|
|
while (iterator.advance(to)) {
|
|
|
|
if (prevValue) {
|
|
|
|
prevValue.endDate = iterator.getDate() as Date
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
const endDate = new Date(iterator.getDate()) as Date
|
2021-06-16 14:23:53 +02:00
|
|
|
endDate.setHours(0, 0, 0, 0)
|
2022-09-08 21:40:48 +02:00
|
|
|
endDate.setDate(endDate.getDate() + 1)
|
2021-06-16 14:23:53 +02:00
|
|
|
const value = {
|
|
|
|
isSpecial: iterator.getUnknown(),
|
|
|
|
isOpen: iterator.getState(),
|
|
|
|
comment: iterator.getComment(),
|
|
|
|
startDate: iterator.getDate() as Date,
|
2024-04-05 17:49:31 +02:00
|
|
|
endDate: endDate // Should be overwritten by the next iteration
|
2021-06-16 14:23:53 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
prevValue = value
|
2021-06-16 14:23:53 +02:00
|
|
|
|
|
|
|
if (value.comment === undefined && !value.isOpen && !value.isSpecial) {
|
|
|
|
// simply closed, nothing special here
|
2022-09-08 21:40:48 +02:00
|
|
|
continue
|
2021-06-16 14:23:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (value.startDate < from) {
|
2022-09-08 21:40:48 +02:00
|
|
|
continue
|
2021-06-16 14:23:53 +02:00
|
|
|
}
|
|
|
|
// Get day: sunday is 0, monday is 1. We move everything so that monday == 0
|
2022-09-08 21:40:48 +02:00
|
|
|
values[(value.startDate.getDay() + 6) % 7].push(value)
|
2021-06-16 14:23:53 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
return values
|
2021-06-16 14:23:53 +02:00
|
|
|
}
|
2021-06-16 16:39:48 +02:00
|
|
|
|
2023-12-15 18:14:21 +01:00
|
|
|
public static getMondayBefore(d) {
|
|
|
|
d = new Date(d)
|
|
|
|
const day = d.getDay()
|
|
|
|
const diff = d.getDate() - day + (day == 0 ? -6 : 1) // adjust when day is sunday
|
|
|
|
return new Date(d.setDate(diff))
|
|
|
|
}
|
|
|
|
|
2023-10-18 02:08:49 +02:00
|
|
|
/**
|
|
|
|
* OH.parseHHMM("12:30") // => {hours: 12, minutes: 30}
|
|
|
|
*/
|
2022-09-08 21:40:48 +02:00
|
|
|
private static parseHHMM(hhmm: string): { hours: number; minutes: number } {
|
2021-06-16 16:39:48 +02:00
|
|
|
if (hhmm === undefined || hhmm == null) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
const spl = hhmm.trim().split(":")
|
2021-06-16 16:39:48 +02:00
|
|
|
if (spl.length != 2) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
const hm = { hours: Number(spl[0].trim()), minutes: Number(spl[1].trim()) }
|
2021-06-16 16:39:48 +02:00
|
|
|
if (isNaN(hm.hours) || isNaN(hm.minutes)) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
return hm
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
|
2023-10-18 02:08:49 +02:00
|
|
|
/**
|
|
|
|
* OH.ParseHhmmRanges("20:00-22:15") // => [{startHour: 20, startMinutes: 0, endHour: 22, endMinutes: 15}]
|
|
|
|
* OH.ParseHhmmRanges("20:00-02:15") // => [{startHour: 20, startMinutes: 0, endHour: 2, endMinutes: 15}]
|
2024-01-13 03:42:07 +01:00
|
|
|
* OH.ParseHhmmRanges("00:00-24:00") // => [{startHour: 0, startMinutes: 0, endHour: 24, endMinutes: 0}]
|
2023-10-18 02:08:49 +02:00
|
|
|
*/
|
2021-06-16 16:39:48 +02:00
|
|
|
private static ParseHhmmRanges(hhmms: string): {
|
2022-09-08 21:40:48 +02:00
|
|
|
startHour: number
|
|
|
|
startMinutes: number
|
|
|
|
endHour: number
|
2021-06-16 16:39:48 +02:00
|
|
|
endMinutes: number
|
|
|
|
}[] {
|
|
|
|
if (hhmms === "off") {
|
2022-09-08 21:40:48 +02:00
|
|
|
return []
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
return hhmms
|
|
|
|
.split(",")
|
|
|
|
.map((s) => s.trim())
|
|
|
|
.filter((str) => str !== "")
|
2021-06-16 16:39:48 +02:00
|
|
|
.map(OH.parseHHMMRange)
|
2022-09-08 21:40:48 +02:00
|
|
|
.filter((v) => v != null)
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static ParseWeekday(weekday: string): number {
|
2022-09-08 21:40:48 +02:00
|
|
|
return OH.daysIndexed[weekday.trim().toLowerCase()]
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static ParseWeekdayRange(weekdays: string): number[] {
|
2022-09-08 21:40:48 +02:00
|
|
|
const split = weekdays.split("-")
|
2021-06-16 16:39:48 +02:00
|
|
|
if (split.length == 1) {
|
2022-09-08 21:40:48 +02:00
|
|
|
const parsed = OH.ParseWeekday(weekdays)
|
2021-06-16 16:39:48 +02:00
|
|
|
if (parsed == null) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
return [parsed]
|
2021-06-16 16:39:48 +02:00
|
|
|
} else if (split.length == 2) {
|
2022-09-08 21:40:48 +02:00
|
|
|
let start = OH.ParseWeekday(split[0])
|
|
|
|
let end = OH.ParseWeekday(split[1])
|
2021-06-16 16:39:48 +02:00
|
|
|
if ((start ?? null) === null || (end ?? null) === null) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
let range = []
|
2021-06-16 16:39:48 +02:00
|
|
|
for (let i = start; i <= end; i++) {
|
2022-09-08 21:40:48 +02:00
|
|
|
range.push(i)
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
return range
|
2021-06-16 16:39:48 +02:00
|
|
|
} else {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static ParseWeekdayRanges(weekdays: string): number[] {
|
2022-09-08 21:40:48 +02:00
|
|
|
let ranges = []
|
|
|
|
let split = weekdays.split(",")
|
2021-06-16 16:39:48 +02:00
|
|
|
for (const weekday of split) {
|
|
|
|
const parsed = OH.ParseWeekdayRange(weekday)
|
|
|
|
if (parsed === undefined || parsed === null) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
ranges.push(...parsed)
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
return ranges
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
private static multiply(
|
|
|
|
weekdays: number[],
|
|
|
|
timeranges: {
|
|
|
|
startHour: number
|
|
|
|
startMinutes: number
|
|
|
|
endHour: number
|
|
|
|
endMinutes: number
|
|
|
|
}[]
|
2023-10-18 02:08:49 +02:00
|
|
|
): {
|
|
|
|
weekday: number
|
|
|
|
startHour: number
|
|
|
|
startMinutes: number
|
|
|
|
endHour: number
|
|
|
|
endMinutes: number
|
|
|
|
}[] {
|
2021-06-16 16:39:48 +02:00
|
|
|
if ((weekdays ?? null) == null || (timeranges ?? null) == null) {
|
2022-09-08 21:40:48 +02:00
|
|
|
return null
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
const ohs: OpeningHour[] = []
|
|
|
|
for (const timerange of timeranges) {
|
2023-10-18 02:08:49 +02:00
|
|
|
const overMidnight =
|
|
|
|
!(timerange.endHour === 0 && timerange.endMinutes === 0) &&
|
|
|
|
(timerange.endHour < timerange.startHour ||
|
|
|
|
(timerange.endHour == timerange.startHour &&
|
|
|
|
timerange.endMinutes < timerange.startMinutes))
|
2021-06-16 16:39:48 +02:00
|
|
|
for (const weekday of weekdays) {
|
2023-10-18 02:08:49 +02:00
|
|
|
if (!overMidnight) {
|
|
|
|
ohs.push({
|
|
|
|
weekday: weekday,
|
|
|
|
startHour: timerange.startHour,
|
|
|
|
startMinutes: timerange.startMinutes,
|
|
|
|
endHour: timerange.endHour,
|
2024-04-05 17:49:31 +02:00
|
|
|
endMinutes: timerange.endMinutes
|
2023-10-18 02:08:49 +02:00
|
|
|
})
|
|
|
|
} else {
|
|
|
|
ohs.push({
|
|
|
|
weekday: weekday,
|
|
|
|
startHour: timerange.startHour,
|
|
|
|
startMinutes: timerange.startMinutes,
|
|
|
|
endHour: 0,
|
2024-04-05 17:49:31 +02:00
|
|
|
endMinutes: 0
|
2023-10-18 02:08:49 +02:00
|
|
|
})
|
|
|
|
ohs.push({
|
|
|
|
weekday: (weekday + 1) % 7,
|
|
|
|
startHour: 0,
|
|
|
|
startMinutes: 0,
|
|
|
|
endHour: timerange.endHour,
|
2024-04-05 17:49:31 +02:00
|
|
|
endMinutes: timerange.endMinutes
|
2023-10-18 02:08:49 +02:00
|
|
|
})
|
|
|
|
}
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
return ohs
|
2021-06-16 16:39:48 +02:00
|
|
|
}
|
2023-12-15 18:14:21 +01:00
|
|
|
}
|
2023-10-18 02:08:49 +02:00
|
|
|
|
2023-12-15 18:14:21 +01:00
|
|
|
export class ToTextualDescription {
|
2024-01-29 16:43:23 +01:00
|
|
|
/**
|
|
|
|
* const oh = new opening_hours("mon 12:00-16:00")
|
|
|
|
* const ranges = OH.createRangesForApplicableWeek(oh)
|
|
|
|
* const tr = ToTextualDescription.createTextualDescriptionFor(oh, ranges.ranges)
|
|
|
|
* tr.textFor("en") // => "On monday from 12:00 till 16:00"
|
|
|
|
* tr.textFor("nl") // => "Op maandag van 12:00 tot 16:00"
|
|
|
|
*
|
|
|
|
* const oh = new opening_hours("mon 12:00-16:00; tu 13:00-14:00")
|
|
|
|
* const ranges = OH.createRangesForApplicableWeek(oh)
|
|
|
|
* const tr = ToTextualDescription.createTextualDescriptionFor(oh, ranges.ranges)
|
|
|
|
* tr.textFor("en") // => "On monday from 12:00 till 16:00. On tuesday from 13:00 till 14:00"
|
|
|
|
* tr.textFor("nl") // => "Op maandag van 12:00 tot 16:00. Op dinsdag van 13:00 tot 14:00"
|
|
|
|
*/
|
2023-12-15 18:14:21 +01:00
|
|
|
public static createTextualDescriptionFor(
|
|
|
|
oh: opening_hours,
|
|
|
|
ranges: OpeningRange[][]
|
|
|
|
): Translation {
|
|
|
|
const t = Translations.t.general.opening_hours
|
|
|
|
|
|
|
|
if (!ranges?.some((r) => r.length > 0)) {
|
|
|
|
// <!-- No changes to the opening hours in the next week; probably open 24/7, permanently closed, opening far in the future or unkown -->
|
|
|
|
if (oh.getNextChange() === undefined) {
|
|
|
|
// <!-- Permenantly in the same state -->
|
|
|
|
if (oh.getComment() !== undefined) {
|
|
|
|
return new Translation({ "*": oh.getComment() })
|
|
|
|
}
|
|
|
|
|
|
|
|
if (oh.getUnknown()) {
|
|
|
|
return t.unknown
|
|
|
|
}
|
|
|
|
if (oh.getState()) {
|
|
|
|
return t.open_24_7
|
|
|
|
} else {
|
|
|
|
return t.closed_permanently
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Opened at a more-or-less normal, weekly rhythm
|
|
|
|
if (OH.weekdaysIdentical(ranges, 0, 6)) {
|
|
|
|
return t.all_days_from.Subs({ ranges: ToTextualDescription.createRangesFor(ranges[0]) })
|
|
|
|
}
|
|
|
|
|
|
|
|
const result: Translation[] = []
|
|
|
|
const weekdays = [
|
|
|
|
"monday",
|
|
|
|
"tuesday",
|
|
|
|
"wednesday",
|
|
|
|
"thursday",
|
|
|
|
"friday",
|
|
|
|
"saturday",
|
2024-04-05 17:49:31 +02:00
|
|
|
"sunday"
|
2023-12-15 18:14:21 +01:00
|
|
|
]
|
2023-12-20 02:50:08 +01:00
|
|
|
|
|
|
|
function addRange(start: number, end: number) {
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
|
|
const day = weekdays[i]
|
|
|
|
if (ranges[i]?.length > 0) {
|
|
|
|
result.push(
|
|
|
|
t[day].Subs({ ranges: ToTextualDescription.createRangesFor(ranges[i]) })
|
|
|
|
)
|
|
|
|
}
|
2023-12-15 18:14:21 +01:00
|
|
|
}
|
|
|
|
}
|
2023-12-20 02:50:08 +01:00
|
|
|
|
|
|
|
if (OH.weekdaysIdentical(ranges, 0, 4)) {
|
2023-12-21 17:36:43 +01:00
|
|
|
if (ranges[0].length > 0) {
|
|
|
|
result.push(
|
|
|
|
t.on_weekdays.Subs({ ranges: ToTextualDescription.createRangesFor(ranges[0]) })
|
|
|
|
)
|
|
|
|
}
|
2023-12-20 02:50:08 +01:00
|
|
|
} else {
|
|
|
|
addRange(0, 4)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (OH.weekdaysIdentical(ranges, 5, 6)) {
|
2023-12-21 17:36:43 +01:00
|
|
|
if (ranges[5].length > 0) {
|
|
|
|
result.push(
|
|
|
|
t.on_weekdays.Subs({ ranges: ToTextualDescription.createRangesFor(ranges[5]) })
|
|
|
|
)
|
|
|
|
}
|
2023-12-20 02:50:08 +01:00
|
|
|
} else {
|
|
|
|
addRange(5, 6)
|
|
|
|
}
|
2023-12-15 18:14:21 +01:00
|
|
|
return ToTextualDescription.chain(result)
|
|
|
|
}
|
|
|
|
|
|
|
|
private static chain(trs: Translation[]): Translation {
|
2024-01-29 16:43:23 +01:00
|
|
|
const languages: Record<string, string> = {}
|
|
|
|
for (const tr1 of trs) {
|
|
|
|
for (const supportedLanguage of tr1.SupportedLanguages()) {
|
|
|
|
languages[supportedLanguage] = "{a}. {b}"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let chainer = new TypedTranslation<{ a; b }>(languages)
|
2023-12-15 18:14:21 +01:00
|
|
|
let tr = trs[0]
|
|
|
|
for (let i = 1; i < trs.length; i++) {
|
2024-01-29 16:43:23 +01:00
|
|
|
tr = chainer.PartialSubsTr("a", tr).PartialSubsTr("b", trs[i])
|
2023-12-15 18:14:21 +01:00
|
|
|
}
|
|
|
|
return tr
|
|
|
|
}
|
2023-12-20 02:50:08 +01:00
|
|
|
|
2023-12-15 18:14:21 +01:00
|
|
|
private static timeString(date: Date) {
|
|
|
|
return OH.hhmm(date.getHours(), date.getMinutes())
|
|
|
|
}
|
|
|
|
|
|
|
|
private static createRangeFor(range: OpeningRange): Translation {
|
|
|
|
return Translations.t.general.opening_hours.ranges.Subs({
|
|
|
|
starttime: ToTextualDescription.timeString(range.startDate),
|
2024-04-05 17:49:31 +02:00
|
|
|
endtime: ToTextualDescription.timeString(range.endDate)
|
2023-12-15 18:14:21 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
private static createRangesFor(ranges: OpeningRange[]): Translation {
|
2023-12-21 17:36:43 +01:00
|
|
|
if (ranges.length === 0) {
|
|
|
|
// return undefined
|
|
|
|
}
|
2023-12-15 18:14:21 +01:00
|
|
|
let tr = ToTextualDescription.createRangeFor(ranges[0])
|
|
|
|
for (let i = 1; i < ranges.length; i++) {
|
|
|
|
tr = Translations.t.general.opening_hours.rangescombined.Subs({
|
|
|
|
range0: tr,
|
2024-04-05 17:49:31 +02:00
|
|
|
range1: ToTextualDescription.createRangeFor(ranges[i])
|
2023-12-15 18:14:21 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
return tr
|
2022-04-28 00:32:15 +02:00
|
|
|
}
|
2020-10-04 01:04:46 +02:00
|
|
|
}
|