jquery-yohours
Version:
Jquery and bootstrap based input widget to create opening_hours data
1,581 lines (1,468 loc) • 165 kB
JavaScript
/* globals jQuery,window,moment,jSmart:false */
var YoHours = YoHours || {};
(function (window, $, moment, YoHours, jSmart, Jed) {
'use strict';
let i18n;
jSmart.prototype.registerPlugin('modifier', 't', function (s, ctx) {
let t = s;
if (i18n instanceof Jed) {
if (ctx === undefined) {
ctx = 'yohours';
}
t = i18n.translate(s).withContext(ctx).fetch();
}
return t;
});
/**
* Converts the day code from a sunday-starting week into the day code of a monday-starting week
* @param {int|string} d The sunday-starting week day (as string)
* @return {int} The monday-starting week day
*/
function swDayToMwDay(d) {
let day = parseInt(d);
return (day === 0) ? 6 : day - 1;
}
/**
* Compares to array for equality
* @param {Array} array
* @return {boolean}
*/
window.Array.prototype.equals = function (array) {
// if the other array is a falsy value, return
if (!array) {
return false;
}
// compare lengths - can save a lot of time
if (this.length !== array.length) {
return false;
}
let i = 0, l = this.length;
for (; i < l; i++) {
// Check if we have nested arrays
if (this[i] instanceof Array && array[i] instanceof Array) {
// recurse into the nested arrays
if (!this[i].equals(array[i])) {
return false;
}
} else if (this[i] !== array[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
};
/**
* Does the array contains the given object
* @param {*} obj The object to look for
* @return {boolean} True if obj in array
*/
window.Array.prototype.contains = function (obj) {
let i = this.length;
while (i--) {
if (this[i] === obj) {
return true;
}
}
return false;
};
YoHours = $.extend(YoHours, {
counter: 0,
DAYS: {
MONDAY: 0, TUESDAY: 1, WEDNESDAY: 2, THURSDAY: 3, FRIDAY: 4, SATURDAY: 5, SUNDAY: 6
},
OSM_DAYS: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
IRL_DAYS: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
OSM_MONTHS: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
IRL_MONTHS: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
MONTH_END_DAY: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
MINUTES_MAX: 1440,
DAYS_MAX: 6,
PH_WEEKDAY: -2,
RGX_RULE_MODIFIER: /^(open|closed|off)$/i,
RGX_WEEK_KEY: /^week$/,
RGX_WEEK_VAL: /^([01234]?[0-9]|5[0123])(-([01234]?[0-9]|5[0123]))?(,([01234]?[0-9]|5[0123])(-([01234]?[0-9]|5[0123]))?)*:?$/,
RGX_MONTH: /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))?:?$/,
RGX_MONTHDAY: /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([012]?[0-9]|3[01])(-((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) )?([012]?[0-9]|3[01]))?:?$/,
RGX_TIME: /^((([01]?[0-9]|2[01234]):[012345][0-9](-([01]?[0-9]|2[01234]):[012345][0-9])?(,([01]?[0-9]|2[01234]):[012345][0-9](-([01]?[0-9]|2[01234]):[012345][0-9])?)*)|(24\/7))$/,
RGX_WEEKDAY: /^(((Mo|Tu|We|Th|Fr|Sa|Su)(-(Mo|Tu|We|Th|Fr|Sa|Su))?)|(PH|SH|easter))(,(((Mo|Tu|We|Th|Fr|Sa|Su)(-(Mo|Tu|We|Th|Fr|Sa|Su))?)|(PH|SH|easter)))*$/,
RGX_HOLIDAY: /^(PH|SH|easter)$/,
RGX_WD: /^(Mo|Tu|We|Th|Fr|Sa|Su)(-(Mo|Tu|We|Th|Fr|Sa|Su))?$/
});
/**
* Class Interval, defines an interval in a week where the POI is open.
* @param {int} dayStart The start week day (use DAYS constants)
* @param {int|void} dayEnd The end week day (use DAYS constants)
* @param {int|void} minStart The interval start (in minutes since midnight)
* @param {int|void} minEnd The interval end (in minutes since midnight)
* @constructor
*/
YoHours.Interval = function (dayStart, dayEnd, minStart, minEnd) {
/** The start day in the week, see DAYS **/
this._dayStart = dayStart;
/** The end day in the week, see DAYS **/
this._dayEnd = dayEnd;
/** The interval start, in minutes since midnight (local hour) **/
this._start = minStart;
/** The interval end, in minutes since midnight (local hour) **/
this._end = minEnd;
if (!this._dayEnd && !this._end) {
this._dayEnd = YoHours.DAYS_MAX;
this._end = YoHours.MINUTES_MAX;
}
};
/**
* @return {int} The start day in the week, see DAYS constants
*/
YoHours.Interval.prototype.getStartDay = function () {
return parseInt(this._dayStart);
};
/**
* @return {int} The end day in the week, see DAYS constants
*/
YoHours.Interval.prototype.getEndDay = function () {
return parseInt(this._dayEnd);
};
/**
* @return {int} The interval start, in minutes since midnight
*/
YoHours.Interval.prototype.getFrom = function () {
return parseInt(this._start);
};
/**
* @return {int} The interval end, in minutes since midnight
*/
YoHours.Interval.prototype.getTo = function () {
return parseInt(this._end);
};
/**
* A wide interval is an interval of one or more days, weeks, months, holidays.
* Use WideInterval.days/weeks/months/holidays methods to construct one object.
* @constructor
*/
YoHours.WideInterval = function () {
//ATTRIBUTES
/** The start of the interval **/
this._start = null;
/** The end of the interval **/
this._end = null;
/** The kind of interval **/
this._type = null;
};
/**
* @param {int} startDay
* @param {int} startMonth,
* @param {int|void} endDay
* @param {int|void} endMonth
* @return {YoHours.WideInterval} a day-based interval
*/
YoHours.WideInterval.prototype.day = function (startDay, startMonth, endDay, endMonth) {
if (startDay === undefined || startMonth === undefined) {
throw Error(i18n.translate('Start day and month can\'t be null').withContext('yohours/notifications').fetch());
}
this._start = {day: startDay, month: startMonth};
this._end = (endDay && endMonth && (endDay !== startDay || endMonth !== startMonth)) ? {
day: endDay, month: endMonth
} : null;
this._type = 'day';
return this;
};
/**
* @param {int} startWeek
* @param {int|void} endWeek
* @return {YoHours.WideInterval} a week-based interval
*/
YoHours.WideInterval.prototype.week = function (startWeek, endWeek) {
if (startWeek === undefined) {
throw Error(i18n.translate('Start week can\'t be null').withContext('yohours/notifications').fetch());
}
this._start = {week: startWeek};
this._end = (endWeek && endWeek !== startWeek) ? {week: endWeek} : null;
this._type = 'week';
return this;
};
/**
* @param {int} startMonth
* @param {int|void} endMonth
* @return {YoHours.WideInterval} a month-based interval
*/
YoHours.WideInterval.prototype.month = function (startMonth, endMonth) {
if (startMonth === undefined) {
throw Error(i18n.translate('Start month can\'t be null').withContext('yohours/notifications').fetch());
}
this._start = {month: startMonth};
this._end = (endMonth && endMonth !== startMonth) ? {month: endMonth} : null;
this._type = 'month';
return this;
};
/**
* @param {string} holiday
* @return {YoHours.WideInterval} a holiday-based interval
*/
YoHours.WideInterval.prototype.holiday = function (holiday) {
if (holiday === undefined || (holiday !== 'PH' && holiday !== 'SH' && holiday !== 'easter')) {
throw Error(i18n.translate('Invalid holiday, must be PH, SH or easter').withContext('yohours/notifications').fetch());
}
this._start = {holiday: holiday};
this._end = null;
this._type = 'holiday';
return this;
};
/**
* @return {YoHours.WideInterval} a holiday-based interval
*/
YoHours.WideInterval.prototype.always = function () {
this._start = null;
this._end = null;
this._type = 'always';
return this;
};
/**
* @return {string|null} The kind of wide interval (always, day, month, week, holiday)
*/
YoHours.WideInterval.prototype.getType = function () {
return this._type;
};
/**
* @return {Object} The start moment
*/
YoHours.WideInterval.prototype.getStart = function () {
return this._start;
};
/**
* @return {Object} end moment
*/
YoHours.WideInterval.prototype.getEnd = function () {
return this._end;
};
/**
* @return {boolean} if the given object concerns the same interval as this one
*/
YoHours.WideInterval.prototype.equals = function (o) {
if (!(o instanceof YoHours.WideInterval)) {
return false;
}
if (this === o) {
return true;
}
if (o._type === 'always') {
return this._type === 'always';
}
let result = false;
switch (this._type) {
case 'always':
result = o._start === null;
break;
case 'day':
result = (o._type === 'day' && o._start.month === this._start.month && o._start.day === this._start.day && ((o._end === null && this._end === null) || (o._end !== null && this._end !== null && this._end.month === o._end.month && this._end.day === o._end.day))) || (o._type === 'month' && o._start.month === this._start.month && (this.isFullMonth() && o.isFullMonth()) || (o._end !== null && this._end !== null && this._end.month === o._end.month && this.endsMonth() && o.endsMonth()));
break;
case 'week':
result = o._start.week === this._start.week && (o._end === this._end || (this._end !== null && o._end !== null && o._end.week === this._end.week));
break;
case 'month':
result = (o._type === 'day' && this._start.month === o._start.month && o.startsMonth() && ((this._end === null && o._end !== null && this._start.month === o._end.month && o.endsMonth()) || (this._end !== null && o._end !== null && this._end.month === o._end.month && o.endsMonth()))) || (o._type === 'month' && o._start.month === this._start.month && ((this._end === null && o._end === null) || (this._end !== null && o._end !== null && this._end.month === o._end.month)));
break;
case 'holiday':
result = o._start.holiday === this._start.holiday;
break;
default:
}
return result;
};
/**
* @return {string} The human readable time
*/
YoHours.WideInterval.prototype.getTimeForHumans = function () {
let result;
switch (this._type) {
case 'day':
if (this._end != null) {
result = i18n.translate('every week from %s %u to %s %u')
.withContext('yohours/timespan')
.fetch(i18n.translate(YoHours.IRL_MONTHS[this._start.month - 1]).withContext('yohours').fetch(), this._start.day, (this._start.month !== this._end.month) ? i18n.translate(YoHours.IRL_MONTHS[this._end.month - 1]).withContext('yohours').fetch() : '', this._end.day);
} else {
result = i18n.translate('day %s %u').withContext('yohours/timespan')
.fetch(i18n.translate(YoHours.IRL_MONTHS[this._start.month - 1]).withContext('yohours').fetch(), this._start.day);
}
break;
case 'week':
if (this._end != null) {
result = i18n.translate('every week from week %u to %u').withContext('yohours/timespan').fetch(this._start.week, this._end.week);
} else {
result = i18n.translate('week %u').withContext('yohours/timespan').fetch(this._start.week);
}
break;
case 'month':
if (this._end != null) {
result = i18n.translate('every week from %s to %s').withContext('yohours/timespan')
.fetch(i18n.translate(YoHours.IRL_MONTHS[this._start.month - 1]).withContext('yohours').fetch(), i18n.translate(YoHours.IRL_MONTHS[this._end.month - 1]).withContext('yohours').fetch());
} else {
result = 'every week in ' + YoHours.IRL_MONTHS[this._start.month - 1];
result = i18n.translate('every week in %s').withContext('yohours/timespan').fetch(i18n.translate(YoHours.IRL_MONTHS[this._start.month - 1]).withContext('yohours').fetch());
}
break;
case 'holiday':
if (this._start.holiday === 'SH') {
result = i18n.translate('every week during school holidays').withContext('yohours/timespan').fetch();
} else if (this._start.holiday === 'PH') {
result = i18n.translate('every public holidays').withContext('yohours/timespan').fetch();
} else if (this._start.holiday === 'easter') {
result = i18n.translate('each easter day').withContext('yohours/timespan').fetch();
} else {
throw new Error(i18n.translate('Invalid holiday type: %s').withContext('yohours/notifications').fetch(this._start.holiday));
}
break;
case 'always':
result = i18n.translate('every week of year').withContext('yohours/timespan').fetch();
break;
default:
result = i18n.translate('invalid time').withContext('yohours/timespan').fetch();
}
return result;
};
/**
* @return {string} The time selector for OSM opening_hours
*/
YoHours.WideInterval.prototype.getTimeSelector = function () {
let result;
switch (this._type) {
case 'day':
result = YoHours.OSM_MONTHS[this._start.month - 1] + ' ' + ((this._start.day < 10) ? '0' : '') + this._start.day;
if (this._end != null) {
//Same month as start ?
if (this._start.month === this._end.month) {
result += '-' + ((this._end.day < 10) ? '0' : '') + this._end.day;
} else {
result += '-' + YoHours.OSM_MONTHS[this._end.month - 1] + ' ' + ((this._end.day < 10) ? '0' : '') + this._end.day;
}
}
break;
case 'week':
result = 'week ' + ((this._start.week < 10) ? '0' : '') + this._start.week;
if (this._end != null) {
result += '-' + ((this._end.week < 10) ? '0' : '') + this._end.week;
}
break;
case 'month':
result = YoHours.OSM_MONTHS[this._start.month - 1];
if (this._end != null) {
result += '-' + YoHours.OSM_MONTHS[this._end.month - 1];
}
break;
case 'holiday':
result = this._start.holiday;
break;
case 'always':
default:
result = '';
}
return result;
};
/**
* Does this interval corresponds to a full month ?
* @return {boolean}
*/
YoHours.WideInterval.prototype.isFullMonth = function () {
if (this._type === 'month' && this._end === null) {
return true;
} else if (this._type === 'day') {
return (this._start.day === 1 && this._end !== null && this._end.month === this._start.month && this._end.day !== undefined && this._end.day === YoHours.MONTH_END_DAY[this._end.month - 1]);
} else {
return false;
}
};
/**
* Does this interval starts the first day of a month
* @return {boolean}
*/
YoHours.WideInterval.prototype.startsMonth = function () {
return this._type === 'month' || this._type === 'always' || (this._type === 'day' && this._start.day === 1);
};
/**
* Does this interval ends the last day of a month
* @return {boolean}
*/
YoHours.WideInterval.prototype.endsMonth = function () {
return this._type === 'month' || this._type === 'always' || (this._type === 'day' && this._end !== null && this._end.day === YoHours.MONTH_END_DAY[this._end.month - 1]);
};
/**
* Does this interval strictly contains the given one (ie the second is a refinement of the first, and not strictly equal)
* @param {Object} o The other wide interval
* @return {boolean} True if this date contains the given one (and is not strictly equal to)
*/
YoHours.WideInterval.prototype.contains = function (o) {
let result = false;
/*
* Check if it is contained in this one
*/
if (this.equals(o)) {
result = false;
} else if (this._type === 'always') {
result = true;
} else if (this._type === 'day') {
if (o._type === 'day') {
//Starting after
if (o._start.month > this._start.month || (o._start.month === this._start.month && o._start.day >= this._start.day)) {
//Ending before
if (o._end != null) {
if (this._end != null && (o._end.month < this._end.month || (o._end.month === this._end.month && o._end.day <= this._end.day))) {
result = true;
}
} else {
if (this._end != null && (o._start.month < this._end.month || (o._start.month === this._end.month && o._start.day <= this._end.day))) {
result = true;
}
}
}
} else if (o._type === 'month') {
//Starting after
if (o._start.month > this._start.month || (o._start.month === this._start.month && this._start.day === 1)) {
//Ending before
if (o._end != null && this._end != null && (o._end.month < this._end.month || (o._end.month === this._end.month && this._end.day === YoHours.MONTH_END_DAY[end.month - 1]))) {
result = true;
} else if (o._end === null && (this._end != null && o._start.month < this._end.month)) {
result = true;
}
}
}
} else if (this._type === 'week') {
if (o._type === 'week') {
if (o._start.week >= this._start.week) {
if (o._end != null && this._end != null && o._end.week <= this._end.week) {
result = true;
} else if (o._end == null && ((this._end != null && o._start.week <= this._end.week) || o._start.week === this._start.week)) {
result = true;
}
}
}
} else if (this._type === 'month') {
if (o._type === 'month') {
if (o._start.month >= this._start.month) {
if (o._end != null && this._end != null && o._end.month <= this._end.month) {
result = true;
} else if (o._end == null && ((this._end != null && o._start.month <= this._end.month) || o._start.month === this._start.month)) {
result = true;
}
}
} else if (o._type === 'day') {
if (o._end != null) {
if (this._end == null) {
if (o._start.month === this._start.month && o._end.month === this._start.month && ((o._start.day >= 1 && o._end.day < YoHours.MONTH_END_DAY[o._start.month - 1]) || (o._start.day > 1 && o._end.day <= YoHours.MONTH_END_DAY[o._start.month - 1]))) {
result = true;
}
} else {
if (o._start.month >= this._start.month && o._end.month <= this._end.month) {
if ((o._start.month > this._start.month && o._end.month < this._end.month) || (o._start.month === this._start.month && o._end.month < this._end.month && o._start.day > 1) || (o._start.month > this._start.month && o._end.month === this._end.month && o._end.day < YoHours.MONTH_END_DAY[o._end.month - 1]) || (o._start.day >= 1 && o._end.day < YoHours.MONTH_END_DAY[o._end.month - 1]) || (o._start.day > 1 && o._end.day <= YoHours.MONTH_END_DAY[o._end.month - 1])) {
result = true;
}
}
}
} else {
if (this._end == null) {
if (this._start.month === o._start.month) {
result = true;
}
} else {
if (o._start.month >= this._start.month && o._start.month <= this._end.month) {
result = true;
}
}
}
}
}
return result;
};
/**
* Class Day, represents a typical day
* @constructor
*/
YoHours.Day = function () {
/** The intervals defining this week **/
this._intervals = [];
/** The next interval ID **/
this._nextInterval = 0;
};
/**
* @return {Array} This day, as a boolean array (minutes since midnight). True if open, false else.
*/
YoHours.Day.prototype.getAsMinutesArray = function () {
//Create array with all values set to false
let minute;
//For each minute
let minuteArray = [];
for (minute = 0; minute <= YoHours.MINUTES_MAX; minute++) {
minuteArray[minute] = false;
}
//Set to true values where an interval is defined
let id = 0, l = this._intervals.length;
for (; id < l; id++) {
if (this._intervals[id] !== undefined) {
let startMinute = null;
let endMinute = null;
if (this._intervals[id].getStartDay() === this._intervals[id].getEndDay() || (this._intervals[id].getEndDay() === YoHours.DAYS_MAX && this._intervals[id].getTo() === YoHours.MINUTES_MAX)) {
//Define start and end minute regarding the current day
startMinute = this._intervals[id].getFrom();
endMinute = this._intervals[id].getTo();
}
//Set to true the minutes for this day
if (startMinute !== null && endMinute !== null) {
for (minute = startMinute; minute <= endMinute; minute++) {
minuteArray[minute] = true;
}
} else {
console.log(this._intervals[id].getFrom() + ' ' + this._intervals[id].getTo() + ' ' + this._intervals[id].getStartDay() + ' ' + this._intervals[id].getEndDay());
throw new Error(i18n.translate('Invalid interval').withContext('yohours/notifications').fetch());
}
}
}
return minuteArray;
};
/**
* @param {boolean|void} clean Clean intervals ? (default: false)
* @return {Array} The intervals in this week
*/
YoHours.Day.prototype.getIntervals = function (clean) {
clean = clean || false;
if (clean) {
//Create continuous intervals over days
let minuteArray = this.getAsMinutesArray();
let intervals = [];
let minStart = -1;
let min = 0, lm = minuteArray.length;
for (; min < lm; min++) {
//First minute
if (min === 0) {
if (minuteArray[min]) {
minStart = min;
}
}
//Last minute
else if (min === lm - 1) {
if (minuteArray[min]) {
intervals.push(new YoHours.Interval(0, 0, minStart, min));
}
}
//Other minutes
else {
//New interval
if (minuteArray[min] && minStart < 0) {
minStart = min;
} else if (!minuteArray[min] && minStart >= 0) {
intervals.push(new YoHours.Interval(0, 0, minStart, min - 1));
minStart = -1;
}
}
}
return intervals;
} else {
return this._intervals;
}
};
/**
* Add a new interval to this week
* @param interval The new interval
* @return {int} The ID of the added interval
*/
YoHours.Day.prototype.addInterval = function (interval) {
this._intervals[this._nextInterval] = interval;
this._nextInterval++;
return this._nextInterval - 1;
};
/**
* Edits the given interval
* @param {int} id The interval ID
* @param interval The new interval
*/
YoHours.Day.prototype.editInterval = function (id, interval) {
this._intervals[id] = interval;
};
/**
* Remove the given interval
* @param {int} id the interval ID
*/
YoHours.Day.prototype.removeInterval = function (id) {
this._intervals[id] = undefined;
delete this._intervals[id];
};
/**
* Redefines this date range intervals with a copy of the given ones
* @param {YoHours.Interval[]} intervals
*/
YoHours.Day.prototype.copyIntervals = function (intervals) {
this._intervals = [];
for (let i = 0; i < intervals.length; i++) {
if (intervals[i] !== undefined && intervals[i].getStartDay() === 0 && intervals[i].getEndDay() === 0) {
this._intervals.push($.extend(true, {}, intervals[i]));
}
}
this._intervals = this.getIntervals(true);
};
/**
* Removes all defined intervals
*/
YoHours.Day.prototype.clearIntervals = function () {
this._intervals = [];
};
/**
* Is this day defining the same intervals as the given one ?
* @param {YoHours.Day} d
*/
YoHours.Day.prototype.sameAs = function (d) {
return d.getAsMinutesArray().equals(this.getAsMinutesArray());
};
/**
* Class Week, represents a typical week of opening hours.
* @constructor
*/
YoHours.Week = function () {
/** The intervals defining this week **/
this._intervals = [];
};
/**
* @return {Array} This week, as a two-dimensional boolean array. First dimension is for days (see DAYS), second dimension for minutes since midnight. True if open, false else.
*/
YoHours.Week.prototype.getAsMinutesArray = function () {
let minute;
let day;
let minuteArray = [];
for (day = 0; day <= YoHours.DAYS_MAX; day++) {
//For each minute
minuteArray[day] = [];
for (minute = 0; minute <= YoHours.MINUTES_MAX; minute++) {
minuteArray[day][minute] = false;
}
}
//Set to true values where an interval is defined
let id = 0, l = this._intervals.length;
for (; id < l; id++) {
if (this._intervals[id] !== undefined) {
for (day = this._intervals[id].getStartDay(); day <= this._intervals[id].getEndDay(); day++) {
//Define start and end minute regarding the current day
let startMinute = (day === this._intervals[id].getStartDay()) ? this._intervals[id].getFrom() : 0;
let endMinute = (day === this._intervals[id].getEndDay()) ? this._intervals[id].getTo() : YoHours.MINUTES_MAX;
//Set to true the minutes for this day
if (startMinute != null && endMinute != null) {
for (minute = startMinute; minute <= endMinute; minute++) {
minuteArray[day][minute] = true;
}
}
}
}
}
return minuteArray;
};
/**
* @param {boolean|void} clean Clean intervals ? (default: false)
* @return {Array} The intervals in this week
*/
YoHours.Week.prototype.getIntervals = function (clean) {
clean = clean || false;
if (clean) {
//Create continuous intervals over days
let minuteArray = this.getAsMinutesArray();
let intervals = [];
let dayStart = -1, minStart = -1;
let day = 0, l = minuteArray.length;
for (; day < l; day++) {
let min = 0, lm = minuteArray[day].length;
for (; min < lm; min++) {
//First minute of monday
if (day === 0 && min === 0) {
if (minuteArray[day][min]) {
dayStart = day;
minStart = min;
}
} else if (day === YoHours.DAYS_MAX && min === lm - 1) {
if (dayStart >= 0 && minuteArray[day][min]) {
intervals.push(new YoHours.Interval(dayStart, day, minStart, min));
}
} else {
//New interval
if (minuteArray[day][min] && dayStart < 0) {
dayStart = day;
minStart = min;
} else if (!minuteArray[day][min] && dayStart >= 0) {
if (min === 0) {
intervals.push(new YoHours.Interval(dayStart, day - 1, minStart, YoHours.MINUTES_MAX));
} else {
intervals.push(new YoHours.Interval(dayStart, day, minStart, min - 1));
}
dayStart = -1;
minStart = -1;
}
}
}
}
return intervals;
} else {
return this._intervals;
}
};
/**
* Returns the intervals which are different from those defined in the given week
* @param {YoHours.Week} w The general week
* @return {Object} The intervals which are different, as object { open: [ Intervals ], closed: [ Intervals ] }
*/
YoHours.Week.prototype.getIntervalsDiff = function (w) {
//Get minutes arrays
let myMinArray = this.getAsMinutesArray();
let wMinArray = w.getAsMinutesArray();
//Create diff array
let intervals = {open: [], closed: []};
let dayStart = -1, minStart = -1;
let diffDay, m, intervalsLength;
for (let d = 0; d <= YoHours.DAYS_MAX; d++) {
diffDay = false;
m = 0;
intervalsLength = intervals.open.length;
while (m <= YoHours.MINUTES_MAX) {
//Copy entire day
if (diffDay) {
//First minute of monday
if (d === 0 && m === 0) {
if (myMinArray[d][m]) {
dayStart = d;
minStart = m;
}
} else if (d === YoHours.DAYS_MAX && m === YoHours.MINUTES_MAX) {
if (dayStart >= 0 && myMinArray[d][m]) {
intervals.open.push(new YoHours.Interval(dayStart, d, minStart, m));
}
} else {
//New interval
if (myMinArray[d][m] && dayStart < 0) {
dayStart = d;
minStart = m;
} else if (!myMinArray[d][m] && dayStart >= 0) {
if (m === 0) {
intervals.open.push(new YoHours.Interval(dayStart, d - 1, minStart, YoHours.MINUTES_MAX));
} else {
intervals.open.push(new YoHours.Interval(dayStart, d, minStart, m - 1));
}
dayStart = -1;
minStart = -1;
}
}
m++;
} else {
diffDay = myMinArray[d][m] ? !wMinArray[d][m] : wMinArray[d][m];
//If there is a difference, start to copy full day
if (diffDay) {
m = 0;
}
//Else, continue checking
else {
m++;
}
}
}
//Close intervals if day is identical
if (!diffDay && dayStart > -1) {
intervals.open.push(new YoHours.Interval(dayStart, d - 1, minStart, YoHours.MINUTES_MAX));
dayStart = -1;
minStart = -1;
}
//Create closed intervals if closed all day
if (diffDay && dayStart === -1 && intervalsLength === intervals.open.length) {
//Merge with previous interval if possible
if (intervals.closed.length > 0 && intervals.closed[intervals.closed.length - 1].getEndDay() === d - 1) {
intervals.closed[intervals.closed.length - 1] = new YoHours.Interval(intervals.closed[intervals.closed.length - 1].getStartDay(), d, 0, YoHours.MINUTES_MAX);
} else {
intervals.closed.push(new YoHours.Interval(d, d, 0, YoHours.MINUTES_MAX));
}
}
}
return intervals;
};
/**
* Add a new interval to this week
* @param {YoHours.Interval} interval The new interval
* @return {int} The ID of the added interval
*/
YoHours.Week.prototype.addInterval = function (interval) {
this._intervals[this._intervals.length] = interval;
return this._intervals.length - 1;
};
/**
* Edits the given interval
* @param {string|int} id The interval ID
* @param {YoHours.Interval} interval The new interval
*/
YoHours.Week.prototype.editInterval = function (id, interval) {
this._intervals[id] = interval;
};
/**
* Remove the given interval
* @param {string|int} id the interval ID
*/
YoHours.Week.prototype.removeInterval = function (id) {
this._intervals[id] = undefined;
delete this._intervals[id];
};
/**
* Removes all intervals during a given day
*/
YoHours.Week.prototype.removeIntervalsDuringDay = function (day) {
let interval, itLength = this._intervals.length, dayDiff;
for (let i = 0; i < itLength; i++) {
interval = this._intervals[i];
if (interval !== undefined) {
//If interval over given day
if (interval.getStartDay() <= day && interval.getEndDay() >= day) {
dayDiff = interval.getEndDay() - interval.getStartDay();
//Avoid deletion if over night interval
if (dayDiff > 1 || dayDiff === 0 || interval.getStartDay() === day || interval.getFrom() <= interval.getTo()) {
//Create new interval if several day
if (interval.getEndDay() - interval.getStartDay() >= 1 && interval.getFrom() <= interval.getTo()) {
if (interval.getStartDay() < day) {
this.addInterval(new YoHours.Interval(interval.getStartDay(), day - 1, interval.getFrom(), 24 * 60));
}
if (interval.getEndDay() > day) {
this.addInterval(new YoHours.Interval(day + 1, interval.getEndDay(), 0, interval.getTo()));
}
}
//Delete
this.removeInterval(i);
}
}
}
}
};
/**
* Redefines this date range intervals with a copy of the given ones
* @param {YoHours.Interval[]} intervals
*/
YoHours.Week.prototype.copyIntervals = function (intervals) {
this._intervals = [];
for (let i = 0; i < intervals.length; i++) {
if (intervals[i] !== undefined) {
this._intervals.push($.extend(true, {}, intervals[i]));
}
}
};
/**
* Is this week defining the same intervals as the given one ?
* @param {YoHours.Week} w
* @return {boolean}
*/
YoHours.Week.prototype.sameAs = function (w) {
return w.getAsMinutesArray().equals(this.getAsMinutesArray());
};
/**
* Class DateRange, defines a range of months, weeks or days.
* A typical week or day will be associated.
* @param {YoHours.WideInterval} w
* @constructor
*/
YoHours.DateRange = function (w) {
/** The wide interval of this date range **/
this._wideInterval = null;
/** The typical week or day associated **/
this._typical = undefined;
this.updateRange(w);
};
/**
* Is this interval defining a typical day ?
* @return {boolean}
*/
YoHours.DateRange.prototype.definesTypicalDay = function () {
return this._typical instanceof YoHours.Day;
};
/**
* Is this interval defining a typical week ?
* @return {boolean}
*/
YoHours.DateRange.prototype.definesTypicalWeek = function () {
return this._typical instanceof YoHours.Week;
};
/**
* @return {YoHours.Day|YoHours.Week|null} The typical day or week
*/
YoHours.DateRange.prototype.getTypical = function () {
return this._typical;
};
/**
* @return {YoHours.WideInterval} The wide interval this date range concerns
*/
YoHours.DateRange.prototype.getInterval = function () {
return this._wideInterval;
};
/**
* Changes the date range
*/
YoHours.DateRange.prototype.updateRange = function (wide) {
this._wideInterval = (wide != null) ? wide : new YoHours.WideInterval().always();
//Create typical week/day
if (this._typical === undefined) {
switch (this._wideInterval.getType()) {
case 'day':
if (this._wideInterval.getEnd() === null) {
this._typical = new YoHours.Day();
} else {
this._typical = new YoHours.Week();
}
break;
case 'week':
this._typical = new YoHours.Week();
break;
case 'month':
this._typical = new YoHours.Week();
break;
case 'holiday':
if (this._wideInterval.getStart().holiday === 'SH') {
this._typical = new YoHours.Week();
} else {
this._typical = new YoHours.Day();
}
break;
case 'always':
this._typical = new YoHours.Week();
break;
default:
throw Error('Invalid interval type: ' + this._wideInterval.getType());
}
}
};
/**
* Check if the typical day/week of this date range is the same as in the given date range
* @param {YoHours.DateRange} dr The other DateRange
* @return {boolean} if same typical day/week
*/
YoHours.DateRange.prototype.hasSameTypical = function (dr) {
return this.definesTypicalDay() === dr.definesTypicalDay() && this._typical.sameAs(dr.getTypical());
};
/**
* Does this date range contains the given date range (ie the second is a refinement of the first)
* @param {YoHours.DateRange} dr The other DateRange
* @return {boolean} if this date contains the given one (and is not strictly equal to)
*/
YoHours.DateRange.prototype.isGeneralFor = function (dr) {
return dr.definesTypicalDay() === this.definesTypicalDay() && this._wideInterval.contains(dr.getInterval());
};
/**
* An opening_hours time, such as "08:00" or "08:00-10:00" or "off" (if no start and end)
* @param {int|void|string} start The start minute (from midnight), can be null
* @param {int|void|string} end The end minute (from midnight), can be null
* @constructor
*/
YoHours.OhTime = function (start, end) {
/** The start minute **/
this._start = (start >= 0) ? start : null;
/** The end minute **/
this._end = (end >= 0 && end !== start) ? end : null;
};
/**
* @return {string} The time in opening_hours format
*/
YoHours.OhTime.prototype.get = function () {
if (this._start === null && this._end === null) {
return 'off';
} else {
return this._timeString(this._start) + ((this._end == null) ? '' : '-' + this._timeString(this._end));
}
};
/**
* @return {string|null} The start minutes
*/
YoHours.OhTime.prototype.getStart = function () {
return this._start === null ? null : this._start.toString();
};
/**
* @return {string|null} The end minutes
*/
YoHours.OhTime.prototype.getEnd = function () {
return this._end === null ? null : this._end.toString();
};
/**
* @param {YoHours.OhTime} t
* @return {boolean} True if same time
*/
YoHours.OhTime.prototype.equals = function (t) {
return this.getStart() === t.getStart() && this.getEnd() === t.getEnd();
};
/**
* @return {string} The hour in HH:MM format
*/
YoHours.OhTime.prototype._timeString = function (minutes) {
let h = Math.floor(minutes / 60);
let period = '';
let m = minutes % 60;
return (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m + period;
};
/**
* An opening_hours date, such as "Apr 21", "week 1-15 Mo,Tu", "Apr-Dec Mo-Fr", "SH Su", ...
* @param {string} w The wide selector, as string
* @param {string} wt The wide selector type (month, week, day, holiday, always)
* @param {int[]} wd The weekdays, as integer array (0 to 6 = Monday to Sunday, -1 = single day date, -2 = PH)
* @constructor
*/
YoHours.OhDate = function (w, wt, wd) {
/** Kind of wide date (month, week, day, holiday, always) **/
this._wideType = wt;
/** Wide date **/
this._wide = w;
/** Weekdays + PH **/
this._weekdays = wd.sort();
/** Overwritten days (to allow create simpler rules) **/
this._wdOver = [];
if (w === null || wt === null || wd === null) {
throw Error(i18n.translate('Missing parameter').withContext('yohours/notifications').fetch());
}
};
/**
* @return {string} The wide type
*/
YoHours.OhDate.prototype.getWideType = function () {
return this._wideType.toString();
};
/**
* @return {string} The month day, month, week, SH (depends of type)
*/
YoHours.OhDate.prototype.getWideValue = function () {
return this._wide.toString();
};
/**
* @return {int[]} The weekdays array
*/
YoHours.OhDate.prototype.getWd = function () {
return this._weekdays;
};
/**
* @return {int[]} The overwritten weekdays array
*/
YoHours.OhDate.prototype.getWdOver = function () {
return this._wdOver;
};
/**
* @param {Array} a The other weekdays array
* @return {boolean} True if same weekdays as other object
*/
YoHours.OhDate.prototype.sameWd = function (a) {
return a.equals(this._weekdays);
};
/**
* @return {string} The weekdays in opening_hours syntax
*/
YoHours.OhDate.prototype.getWeekdays = function () {
let i;
let result = '';
let wd = this._weekdays.concat(this._wdOver).sort();
//PH as weekday
if (wd.length > 0 && parseInt(wd[0]) === YoHours.PH_WEEKDAY) {
result = 'PH';
wd.shift();
}
//Check if we should create a continuous interval for week-end
if (wd.length > 0 && wd.contains(6) && wd.contains(0) && (wd.contains(5) || wd.contains(1))) {
//Find when the week-end starts
let startWE = 6;
i = wd.length - 2;
let stopLooking = false;
while (!stopLooking && i >= 0) {
if (wd[i] === wd[i + 1] - 1) {
startWE = wd[i];
i--;
} else {
stopLooking = true;
}
}
//Find when it stops
i = 1;
stopLooking = false;
let endWE = 0;
while (!stopLooking && i < wd.length) {
if (wd[i - 1] === wd[i] - 1) {
endWE = wd[i];
i++;
} else {
stopLooking = true;
}
}
//If long enough, add it as first weekday interval
let length = 7 - startWE + endWE + 1;
if (length >= 3 && startWE > endWE) {
if (result.length > 0) {
result += ',';
}
result += YoHours.OSM_DAYS[startWE] + '-' + YoHours.OSM_DAYS[endWE];
//Remove processed days
let j = 0;
while (j < wd.length) {
if (wd[j] <= endWE || wd[j] >= startWE) {
wd.splice(j, 1);
} else {
j++;
}
}
}
}
//Process only if not empty weekday list
if (wd.length > 1 || (wd.length === 1 && wd[0] !== -1)) {
result += (result.length > 0) ? ',' + YoHours.OSM_DAYS[wd[0]] : YoHours.OSM_DAYS[wd[0]];
let firstInRow = wd[0];
for (i = 1; i < wd.length; i++) {
//When days aren't following
if (wd[i - 1] !== wd[i] - 1) {
//Previous day range length > 1
if (firstInRow !== wd[i - 1]) {
//Two days
if (wd[i - 1] - firstInRow === 1) {
result += ',' + YoHours.OSM_DAYS[wd[i - 1]];
} else {
result += '-' + YoHours.OSM_DAYS[wd[i - 1]];
}
}
//Add the current day
result += ',' + YoHours.OSM_DAYS[wd[i]];
firstInRow = wd[i];
} else if (i === wd.length - 1) {
if (wd[i] - firstInRow === 1) {
result += ',' + YoHours.OSM_DAYS[wd[i]];
} else {
result += '-' + YoHours.OSM_DAYS[wd[i]];
}
}
}
}
if (result === 'Mo-Su') {
result = '';
}
return result;
};
/**
* Is the given object of the same kind as this one
* @param {YoHours.OhDate} d
* @return {boolean} True if same weekdays and same wide type
*/
YoHours.OhDate.prototype.sameKindAs = function (d) {
return this._wideType === d.getWideType() && d.sameWd(this._weekdays);
};
/**
* @param {Object} o
* @return {boolean} True if this object is equal to the given one
*/
YoHours.OhDate.prototype.equals = function (o) {
return o instanceof YoHours.OhDate && this._wideType === o.getWideType() && this._wide === o.getWideValue() && o.sameWd(this._weekdays);
};
/**
* Adds a new weekday in this date
* @param {int} wd
*/
YoHours.OhDate.prototype.addWeekday = function (wd) {
if (!this._weekdays.contains(wd) && !this._wdOver.contains(wd)) {
this._weekdays.push(wd);
this._weekdays = this._weekdays.sort();
}
};
/**
* Adds public holiday as a weekday of this date
*/
YoHours.OhDate.prototype.addPhWeekday = function () {
this.addWeekday(YoHours.PH_WEEKDAY);
};
/**
* Adds an overwritten weekday, which can be included in this date and that will be overwritten in a following rule
* @param {int} wd
*/
YoHours.OhDate.prototype.addOverwrittenWeekday = function (wd) {
if (!this._wdOver.contains(wd) && !this._weekdays.contains(wd)) {
this._wdOver.push(wd);
this._wdOver = this._wdOver.sort();
}
};
/**
* An opening_hours rule, such as "Mo,Tu 08:00-18:00"
* @constructor
*/
YoHours.OhRule = function () {
/** The date selectors **/
this._date = [];
/** The time selectors **/
this._time = [];
};
/**
* @return {YoHours.OhDate[]} The date selectors, as an array
*/
YoHours.OhRule.prototype.getDate = function () {
return this._date;
};
/**
* @return {YoHours.OhTime[]} The time selectors, as an array
*/
YoHours.OhRule.prototype.getTime = function () {
return this._time;
};
/**
* @return {string} The opening_hours value
*/
YoHours.OhRule.prototype.get = function () {
let i;
let l;
let result = '';
//Create date part
if (this._date.length > 1 || this._date[0].getWideValue() !== '') {
//Add wide selectors
i = 0;
l = this._date.length;
for (; i < l; i++) {
if (i > 0) {
result += ',';
}
result += this._date[i].getWideValue();
}
}
//Add weekdays
if (this._date.length > 0) {
let wd = this._date[0].getWeekdays();
if (wd.length > 0) {
result += ' ' + wd;
}
}
//Create time part
if (this._time.length > 0) {
result += ' ';
i = 0;
l = this._time.length;
for (; i < l; i++) {
if (i > 0) {
result += ',';
}
result += this._time[i].get();
}
} else {
result += ' off';
}
if (result.trim() === '00:00-24:00') {
result = '24/7';
}
return result.trim();
};
/**
* @param {Object} o
* @return {boolean} True if the given rule has the same time as this one
*/
YoHours.OhRule.prototype.sameTime = function (o) {
if (o === undefined || o == null || o.getTime().length !== this._time.length) {
return false;
} else {
let i = 0, l = this._time.length;
for (; i < l; i++) {
if (!this._time[i].equals(o.getTime()[i])) {
return false;
}
}
return true;
}
};
/**
* Is this rule concerning off time ?
* @return {boolean}
*/
YoHours.OhRule.prototype.isOff = function () {
return this._time.length === 0 || (this._time.length === 1 && this._time[0].getStart() === null);
};
/**
* Does the rule have any overwritten weekday ?
* @return {boolean}
*/
YoHours.OhRule.prototype.hasOverwrittenWeekday = function () {
return this._date.length > 0 && this._date[0]._wdOver.length > 0;
};
/**
* Adds a weekday to all the dates
* @param {int} wd
*/
YoHours.OhRule.prototype.addWeekday = function (wd) {
for (let i = 0; i < this._date.length; i++) {
this._date[i].addWeekday(wd);
}
};
/**
* Adds public holidays as weekday to all dates
*/
YoHours.OhRule.prototype.addPhWeekday = function () {
for (let i = 0; i < this._date.length; i++) {
this._date[i].addPhWeekday();
}
};
/**
* Adds an overwritten weekday to all the dates
* @param {int} wd
*/
YoHours.OhRule.prototype.addOverwrittenWeekday = function (wd) {
for (let i = 0; i < this._date.length; i++) {
this._date[i].addOverwrittenWeekday(wd);
}
};
/**
* @param {YoHours.OhDate} d A new date selector
*/
YoHours.OhRule.prototype.addDate = function (d) {
//Check param
if (d === null || d === undefined || !(d instanceof YoHours.OhDate)) {
throw Error(i18n.translate('Invalid parameter').withContext('yohours/notifications').fetch());
}
//Check if date can be added
if (this._date.length === 0 || (this._date[0].getWideType() !== 'always' && this._date[0].sameKindAs(d))) {
this._date.push(d);
} else {
if (this._date.length !== 1 || this._date[0].getWideType() !== 'always' || !this._date[0].sameWd(d.getWd())) {
throw Error(i18n.translate('This date can\'t be added to this rule').withContext('yohours/notifications').fetch());
}
}
};
/**
* @param {YoHours.OhTime} t A new time selector
*/
YoHours.OhRule.prototype.addTime = function (t) {
if ((this._time.length === 0 || this._time[0].get() !== 'off') && !this._time.contains(t)) {
this._time.push(t);
} else {
throw Error(i18n.translate('This time can\'t be added to this rule').withContext('yohours/notifications').fetch());
}
};
/**
* Class OpeningHoursBuilder, creates opening_hours value from date range object
* @constructor
*/
YoHours.OpeningHoursBuilder = function () {
};
/**
* Parses several date ranges to create an opening_hours string
* @param {YoHours.DateRange[]} dateRanges The date ranges to parse
* @return {string} The opening_hours string
*/
YoHours.OpeningHoursBuilder.prototype.build = function (dateRanges) {
let rangeId;
let rules = [];
let dateRange, ohRules, ohRule, ohRuleAdded, ruleId, rangeGeneral, rangeGeneralFor;
//Read each date range
rangeId = 0;
let l = dateRanges.length;
for (; rangeId < l; rangeId++) {
dateRange = dateRanges[rangeId];
if (dateRange !== undefined) {
//Check if the defined typical week/day is not strictly equal to a previous wider rule
rangeGeneral = null;
rangeGeneralFor = null;
let rangeGenId = rangeId - 1;
while (rangeGenId >= 0 && rangeGeneral == null) {
if (dateRanges[rangeGenId] !== undefined) {
let generalFor = dateRanges[rangeGenId].isGeneralFor(dateRange);
if (dateRanges[rangeGenId].hasSameTypical(dateRange) && (dateRanges[rangeGenId].getInterval().equals(dateRange.getInterval()) || generalFor)) {
rangeGeneral = rangeGenId;
} else if (generalFor && dateRanges[rangeGenId].definesTypicalWeek() && dateRange.definesTypicalWeek()) {
rangeGeneralFor = rangeGenId; //Keep this ID to make differences in order to simplify result
}
}
rangeGenId--;
}
if (rangeId === 0 || rangeGeneral == null) {
//Get rules for this date range
if (dateRange.definesTypicalWeek()) {
if (rangeGeneralFor != null) {
ohRules = this._buildWeekDiff(dateRange, dateRanges[rangeGeneralFor]);
} else {
ohRules = this._buildWeek(dateRange);
}
} else {
ohRules = this._buildDay(dateRange);
}
//Process each rule
let ohruleId = 0, orl = ohRules.length;
for (; ohruleId < orl; ohruleId++) {
ohRule = ohRules[ohruleId];
ohRuleAdded = false;
ruleId = 0;
//Try to add them to previously defined ones
while (!ohRuleAdded && ruleId < rules.length) {
//Identical one
if (rules[ruleId].sameTime(ohRule)) {
try {
let dateId = 0, dl = ohRule.getDate().length;
for (; dateId < dl; dateId++) {
rules[ruleId].addDate(ohRule.getDate()[dateId]);
}
ohRuleAdded = true;
} catch (e) {
//But before, try to merge PH with always weekdays
if (ohRule.getDate()[0].getWideType() === 'holiday' && ohRule.getDate()[0].getWideValue() === 'PH' && rules[ruleId].getDate()[0].getWideType() === 'always') {
rules[ruleId].addPhWeekday();
ohRuleAdded = true;
} else if (rules[ruleId].getDate()[0].getWideType() === 'holiday' && rules[ruleId].getDate()[0].getWideValue() === 'PH' && ohRule.getDate()[0].getWideType() === 'always') {
ohRule.addPhWeekday();
rules[ruleId] = ohRule;
ohRuleAdded = true;
} else {
ruleId++;
}
}
} else {
ruleId++;
}
}
//If not, add as new rule
if (!ohRuleAdded) {
rules.push(ohRule);
}
//If some overwritten weekdays are still in last rule
if (ohruleId === orl - 1 && ohRule.hasOverwrittenWeekday()) {
let ohruleOWD = new YoHours.OhRule();
for (let ohruleDateId = 0; ohruleDateId < ohRule.getDate().length; ohruleDateId++) {
ohruleOWD.addDate(new YoHours.OhDate(ohRule.getDate()[ohruleDateId].getWideValue(), ohRule.getDate()[ohruleDateId].getWideType(), ohRule.getDate()[ohruleDateId].getWdOver()));
}
ohruleOWD.addTime(new YoHours.OhTime());
ohRules.push(ohruleOWD);
orl++;
}
}
}
}
}
//Create result string
let result = '';
ruleId = 0;
l = rules.length;
for (; ruleId < l; ruleId++) {
if (ruleId > 0) {
result += '; ';
}
result += rules[ruleId].get();
}
return result;
};
/**
* Creates rules for a given typical day
* @param {YoHours.DateRange} dateRange The date range defining a typical day
* @return {YoHours.OhRule[]} An array of OhRules
*/
YoHours.OpeningHoursBuilder.prototype._buildDay = function (dateRange) {
let intervals = dateRange.getTypical().getIntervals(true);
let interval;
//Create rule
let rule = new YoHours.OhRule();
let date = new YoHours.OhDate(dateRange.getInterval().getTimeSelector(), dateRange.getInterval().getType(), [-1]);
rule.addDate(date);
//Read time
let i = 0, l = intervals.length;
for (; i < l; i++) {
interval = intervals[i];
if (interval !== undefined) {
rule.addTime(new YoHours.OhTime(interval.getFrom(), interval.getTo()));
}
}
return [rule];
};
/**
* Create rules for a date range defining a typical week
* Algorithm inspired by OpeningHoursEdit plugin for JOSM
* @param {YoHours.DateRange} dateRange The date range defining a typical day
* @return {YoHours.OhRule[]} An array of OhRules
*/
YoHours.OpeningHoursBuilder.prototype._buildWeek = function (dateRange) {
let i;
let result = [];
let intervals = dateRange.getTypical().getIntervals(true);
/*
* Create time intervals per day
*/
let timeInterv