@noe-teritorio/opening_hours 3.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2791 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Adrien PAVIE
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ *
5
+ * This file is part of YoHours.
6
+ *
7
+ * YoHours is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU Affero General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * any later version.
11
+ *
12
+ * YoHours is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU Affero General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU Affero General Public License
18
+ * along with YoHours. If not, see <https://www.gnu.org/licenses/>.
19
+ */
20
+
21
+ /*
22
+ * YoHours
23
+ * Web interface to make opening hours data for OpenStreetMap the easy way
24
+ * Author: Adrien PAVIE
25
+ *
26
+ * Model JS classes
27
+ */
28
+
29
+ /*
30
+ * ========= CONSTANTS =========
31
+ */
32
+ /**
33
+ * The days in a week
34
+ */
35
+ const DAYS = {
36
+ MONDAY: 0,
37
+ TUESDAY: 1,
38
+ WEDNESDAY: 2,
39
+ THURSDAY: 3,
40
+ FRIDAY: 4,
41
+ SATURDAY: 5,
42
+ SUNDAY: 6
43
+ };
44
+
45
+ /**
46
+ * The days in OSM
47
+ */
48
+ const OSM_DAYS = [ "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su" ];
49
+
50
+ /**
51
+ * The days IRL
52
+ */
53
+ const IRL_DAYS = [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ];
54
+
55
+ /**
56
+ * The month in OSM
57
+ */
58
+ const OSM_MONTHS = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
59
+
60
+ /**
61
+ * The months IRL
62
+ */
63
+ const IRL_MONTHS = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ];
64
+
65
+ /**
66
+ * The last day of month
67
+ */
68
+ const MONTH_END_DAY = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
69
+
70
+ /**
71
+ * The maximal minute that an interval can have
72
+ */
73
+ const MINUTES_MAX = 1440;
74
+
75
+ /**
76
+ * The maximal value of days
77
+ */
78
+ const DAYS_MAX = 6;
79
+
80
+ /**
81
+ * The weekday ID for PH
82
+ */
83
+ const PH_WEEKDAY = -2;
84
+
85
+ /*
86
+ * ========== CLASSES ==========
87
+ */
88
+
89
+ /**
90
+ * Class Interval, defines an interval in a week where the POI is open.
91
+ * @param dayStart The start week day (use DAYS constants)
92
+ * @param dayEnd The end week day (use DAYS constants)
93
+ * @param minStart The interval start (in minutes since midnight)
94
+ * @param minEnd The interval end (in minutes since midnight)
95
+ */
96
+ var Interval = function(dayStart, dayEnd, minStart, minEnd) {
97
+ //ATTRIBUTES
98
+ /** The start day in the week, see DAYS **/
99
+ this._dayStart = dayStart;
100
+
101
+ /** The end day in the week, see DAYS **/
102
+ this._dayEnd = dayEnd;
103
+
104
+ /** The interval start, in minutes since midnight (local hour) **/
105
+ this._start = minStart;
106
+
107
+ /** The interval end, in minutes since midnight (local hour) **/
108
+ this._end = minEnd;
109
+
110
+ //CONSTRUCTOR
111
+ if(this._dayEnd == 0 && this._end == 0) {
112
+ this._dayEnd = DAYS_MAX;
113
+ this._end = MINUTES_MAX;
114
+ }
115
+ //console.log("Interval", this._dayStart, this._dayEnd, this._start, this._end);
116
+ };
117
+
118
+ //ACCESSORS
119
+ /**
120
+ * @return The start day in the week, see DAYS constants
121
+ */
122
+ Interval.prototype.getStartDay = function() {
123
+ return this._dayStart;
124
+ };
125
+
126
+ /**
127
+ * @return The end day in the week, see DAYS constants
128
+ */
129
+ Interval.prototype.getEndDay = function() {
130
+ return this._dayEnd;
131
+ };
132
+
133
+ /**
134
+ * @return The interval start, in minutes since midnight
135
+ */
136
+ Interval.prototype.getFrom = function() {
137
+ return this._start;
138
+ };
139
+
140
+ /**
141
+ * @return The interval end, in minutes since midnight
142
+ */
143
+ Interval.prototype.getTo = function() {
144
+ return this._end;
145
+ };
146
+
147
+
148
+
149
+ /**
150
+ * A wide interval is an interval of one or more days, weeks, months, holidays.
151
+ * Use WideInterval.days/weeks/months/holidays methods to construct one object.
152
+ */
153
+ var WideInterval = function() {
154
+ //ATTRIBUTES
155
+ /** The start of the interval **/
156
+ this._start = null;
157
+
158
+ /** The end of the interval **/
159
+ this._end = null;
160
+
161
+ /** The kind of interval **/
162
+ this._type = null;
163
+ };
164
+
165
+ //CONSTRUCTORS
166
+ /**
167
+ * @return a day-based interval
168
+ */
169
+ WideInterval.prototype.day = function(startDay, startMonth, endDay, endMonth) {
170
+ if(startDay == null || startMonth == null) {
171
+ throw Error("Start day and month can't be null");
172
+ }
173
+ this._start = { day: startDay, month: startMonth };
174
+ this._end = (endDay != null && endMonth != null && (endDay != startDay || endMonth != startMonth)) ? { day: endDay, month: endMonth } : null;
175
+ this._type = "day";
176
+ return this;
177
+ };
178
+
179
+ /**
180
+ * @return a week-based interval
181
+ */
182
+ WideInterval.prototype.week = function(startWeek, endWeek) {
183
+ if(startWeek == null) {
184
+ throw Error("Start week can't be null");
185
+ }
186
+ this._start = { week: startWeek };
187
+ this._end = (endWeek != null && endWeek != startWeek) ? { week: endWeek } : null;
188
+ this._type = "week";
189
+ return this;
190
+ };
191
+
192
+ /**
193
+ * @return a month-based interval
194
+ */
195
+ WideInterval.prototype.month = function(startMonth, endMonth) {
196
+ if(startMonth == null) {
197
+ throw Error("Start month can't be null");
198
+ }
199
+ this._start = { month: startMonth };
200
+ this._end = (endMonth != null && endMonth != startMonth) ? { month: endMonth } : null;
201
+ this._type = "month";
202
+ return this;
203
+ };
204
+
205
+ /**
206
+ * @return a holiday-based interval
207
+ */
208
+ WideInterval.prototype.holiday = function(holiday) {
209
+ if(holiday == null || (holiday != "PH" && holiday != "SH" && holiday != "easter")) {
210
+ throw Error("Invalid holiday, must be PH, SH or easter");
211
+ }
212
+ this._start = { holiday: holiday };
213
+ this._end = null;
214
+ this._type = "holiday";
215
+ return this;
216
+ };
217
+
218
+ /**
219
+ * @return a holiday-based interval
220
+ */
221
+ WideInterval.prototype.always = function() {
222
+ this._start = null;
223
+ this._end = null;
224
+ this._type = "always";
225
+ return this;
226
+ };
227
+
228
+ //ACCESSORS
229
+ /**
230
+ * @return The kind of wide interval (always, day, month, week, holiday)
231
+ */
232
+ WideInterval.prototype.getType = function() {
233
+ return this._type;
234
+ };
235
+
236
+ /**
237
+ * @return The start moment
238
+ */
239
+ WideInterval.prototype.getStart = function() {
240
+ return this._start;
241
+ };
242
+
243
+ /**
244
+ * @return The end moment
245
+ */
246
+ WideInterval.prototype.getEnd = function() {
247
+ return this._end;
248
+ };
249
+
250
+ /**
251
+ * @return True if the given object concerns the same interval as this one
252
+ */
253
+ WideInterval.prototype.equals = function(o) {
254
+ if(!o instanceof WideInterval) { return false; }
255
+ if(this === o) { return true; }
256
+ if(o._type == "always") { return this._type == "always"; }
257
+ var result = false;
258
+
259
+ switch(this._type) {
260
+ case "always":
261
+ result = o._start == null;
262
+ break;
263
+ case "day":
264
+ result =
265
+ (
266
+ o._type == "day"
267
+ && o._start.month == this._start.month
268
+ && o._start.day == this._start.day
269
+ && (
270
+ (o._end == null && this._end == null)
271
+ || (o._end != null && this._end != null && this._end.month == o._end.month && this._end.day == o._end.day)
272
+ ))
273
+ ||
274
+ (
275
+ o._type == "month"
276
+ && o._start.month == this._start.month
277
+ && (this.isFullMonth() && o.isFullMonth())
278
+ || (o._end != null && this._end != null && this._end.month == o._end.month && this.endsMonth() && o.endsMonth())
279
+ );
280
+ break;
281
+
282
+ case "week":
283
+ result =
284
+ o._start.week == this._start.week
285
+ && (o._end == this._end || (this._end != null && o._end != null && o._end.week == this._end.week));
286
+ break;
287
+
288
+ case "month":
289
+ result =
290
+ (
291
+ o._type == "day"
292
+ && this._start.month == o._start.month
293
+ && o.startsMonth()
294
+ && (
295
+ (this._end == null && o._end != null && this._start.month == o._end.month && o.endsMonth())
296
+ ||
297
+ (this._end != null && o._end != null && this._end.month == o._end.month && o.endsMonth())
298
+ )
299
+ )
300
+ ||
301
+ (
302
+ o._type == "month"
303
+ && o._start.month == this._start.month
304
+ && (
305
+ (this._end == null && o._end == null)
306
+ ||
307
+ (this._end != null && o._end != null && this._end.month == o._end.month)
308
+ )
309
+ );
310
+ break;
311
+
312
+ case "holiday":
313
+ result = o._start.holiday == this._start.holiday;
314
+ break;
315
+ default:
316
+ }
317
+
318
+ return result;
319
+ };
320
+
321
+ /**
322
+ * @return The human readable time
323
+ */
324
+ WideInterval.prototype.getTimeForHumans = function() {
325
+ var result;
326
+
327
+ switch(this._type) {
328
+ case "day":
329
+ if(this._end != null) {
330
+ result = "every week from "+IRL_MONTHS[this._start.month-1]+" "+this._start.day+" to ";
331
+ if(this._start.month != this._end.month) { result += IRL_MONTHS[this._end.month-1]+" "; }
332
+ result += this._end.day;
333
+ }
334
+ else {
335
+ result = "day "+IRL_MONTHS[this._start.month-1]+" "+this._start.day;
336
+ }
337
+ break;
338
+
339
+ case "week":
340
+ if(this._end != null) {
341
+ result = "every week from week "+this._start.week+" to "+this._end.week;
342
+ }
343
+ else {
344
+ result = "week "+this._start.week;
345
+ }
346
+ break;
347
+
348
+ case "month":
349
+ if(this._end != null) {
350
+ result = "every week from "+IRL_MONTHS[this._start.month-1]+" to "+IRL_MONTHS[this._end.month-1];
351
+ }
352
+ else {
353
+ result = "every week in "+IRL_MONTHS[this._start.month-1];
354
+ }
355
+ break;
356
+
357
+ case "holiday":
358
+ if(this._start.holiday == "SH") {
359
+ result = "every week during school holidays";
360
+ }
361
+ else if(this._start.holiday == "PH") {
362
+ result = "every public holidays";
363
+ }
364
+ else if(this._start.holiday == "easter") {
365
+ result = "each easter day";
366
+ }
367
+ else {
368
+ throw new Error("Invalid holiday type: "+this._start.holiday);
369
+ }
370
+ break;
371
+
372
+ case "always":
373
+ result = "every week of year";
374
+ break;
375
+ default:
376
+ result = "invalid time";
377
+ }
378
+
379
+ return result;
380
+ };
381
+
382
+ /**
383
+ * @return The time selector for OSM opening_hours
384
+ */
385
+ WideInterval.prototype.getTimeSelector = function() {
386
+ var result;
387
+
388
+ switch(this._type) {
389
+ case "day":
390
+ result = OSM_MONTHS[this._start.month-1]+" "+((this._start.day < 10) ? "0" : "")+this._start.day;
391
+ if(this._end != null) {
392
+ //Same month as start ?
393
+ if(this._start.month == this._end.month) {
394
+ result += "-"+((this._end.day < 10) ? "0" : "")+this._end.day;
395
+ }
396
+ else {
397
+ result += "-"+OSM_MONTHS[this._end.month-1]+" "+((this._end.day < 10) ? "0" : "")+this._end.day;
398
+ }
399
+ }
400
+ break;
401
+
402
+ case "week":
403
+ result = "week "+((this._start.week < 10) ? "0" : "")+this._start.week;
404
+ if(this._end != null) {
405
+ result += "-"+((this._end.week < 10) ? "0" : "")+this._end.week;
406
+ }
407
+ break;
408
+
409
+ case "month":
410
+ result = OSM_MONTHS[this._start.month-1];
411
+ if(this._end != null) {
412
+ result += "-"+OSM_MONTHS[this._end.month-1];
413
+ }
414
+ break;
415
+
416
+ case "holiday":
417
+ result = this._start.holiday;
418
+ break;
419
+
420
+ case "always":
421
+ default:
422
+ result = "";
423
+ }
424
+
425
+ return result;
426
+ };
427
+
428
+ /**
429
+ * Does this interval corresponds to a full month ?
430
+ */
431
+ WideInterval.prototype.isFullMonth = function() {
432
+ if(this._type == "month" && this._end == null) {
433
+ return true;
434
+ }
435
+ else if(this._type == "day") {
436
+ return (this._start.day == 1 && this._end != null && this._end.month == this._start.month && this._end.day != undefined && this._end.day == MONTH_END_DAY[this._end.month-1]);
437
+ }
438
+ else {
439
+ return false;
440
+ }
441
+ };
442
+
443
+ /**
444
+ * Does this interval starts the first day of a month
445
+ */
446
+ WideInterval.prototype.startsMonth = function() {
447
+ return this._type == "month" || this._type == "always" || (this._type == "day" && this._start.day == 1);
448
+ };
449
+
450
+ /**
451
+ * Does this interval ends the last day of a month
452
+ */
453
+ WideInterval.prototype.endsMonth = function() {
454
+ return this._type == "month" || this._type == "always" || (this._type == "day" && this._end != null && this._end.day == MONTH_END_DAY[this._end.month-1]);
455
+ };
456
+
457
+ /**
458
+ * Does this interval strictly contains the given one (ie the second is a refinement of the first, and not strictly equal)
459
+ * @param o The other wide interval
460
+ * @return True if this date contains the given one (and is not strictly equal to)
461
+ */
462
+ WideInterval.prototype.contains = function(o) {
463
+ var result = false;
464
+
465
+ /*
466
+ * Check if it is contained in this one
467
+ */
468
+ if(this.equals(o)) {
469
+ result = false;
470
+ }
471
+ else if(this._type == "always") {
472
+ result = true;
473
+ }
474
+ else if(this._type == "day") {
475
+ if(o._type == "day") {
476
+ //Starting after
477
+ if(o._start.month > this._start.month || (o._start.month == this._start.month && o._start.day >= this._start.day)) {
478
+ //Ending before
479
+ if(o._end != null) {
480
+ if(this._end != null && (o._end.month < this._end.month || (o._end.month == this._end.month && o._end.day <= this._end.day))) {
481
+ result = true;
482
+ }
483
+ }
484
+ else {
485
+ if(this._end != null && (o._start.month < this._end.month || (o._start.month == this._end.month && o._start.day <= this._end.day))) {
486
+ result = true;
487
+ }
488
+ }
489
+ }
490
+ }
491
+ else if(o._type == "month"){
492
+ //Starting after
493
+ if(o._start.month > this._start.month || (o._start.month == this._start.month && this._start.day == 1)) {
494
+ //Ending before
495
+ if(o._end != null && this._end != null && (o._end.month < this._end.month || (o._end.month == this._end.month && this._end.day == MONTH_END_DAY[end.month-1]))) {
496
+ result = true;
497
+ }
498
+ else if(o._end == null && (this._end != null && o._start.month < this._end.month)) {
499
+ result = true;
500
+ }
501
+ }
502
+ }
503
+ }
504
+ else if(this._type == "week") {
505
+ if(o._type == "week") {
506
+ if(o._start.week >= this._start.week) {
507
+ if(o._end != null && this._end != null && o._end.week <= this._end.week) {
508
+ result = true;
509
+ }
510
+ else if(o._end == null && ((this._end != null && o._start.week <= this._end.week) || o._start.week == this._start.week)) {
511
+ result = true;
512
+ }
513
+ }
514
+ }
515
+ }
516
+ else if(this._type == "month") {
517
+ if(o._type == "month") {
518
+ if(o._start.month >= this._start.month) {
519
+ if(o._end != null && this._end != null && o._end.month <= this._end.month) {
520
+ result = true;
521
+ }
522
+ else if(o._end == null && ((this._end != null && o._start.month <= this._end.month) || o._start.month == this._start.month)) {
523
+ result = true;
524
+ }
525
+ }
526
+ }
527
+ else if(o._type == "day") {
528
+ if(o._end != null) {
529
+ if(this._end == null) {
530
+ if(
531
+ o._start.month == this._start.month
532
+ && o._end.month == this._start.month
533
+ && ((o._start.day >= 1 && o._end.day < MONTH_END_DAY[o._start.month-1])
534
+ || (o._start.day > 1 && o._end.day <= MONTH_END_DAY[o._start.month-1]))
535
+ ) {
536
+ result = true;
537
+ }
538
+ }
539
+ else {
540
+ if(o._start.month >= this._start.month && o._end.month <= this._end.month) {
541
+ if(
542
+ (o._start.month > this._start.month && o._end.month < this._end.month)
543
+ || (o._start.month == this._start.month && o._end.month < this._end.month && start.day > 1)
544
+ || (o._start.month > this._start.month && o._end.month == this._end.month && o._end.day < MONTH_END_DAY[o._end.month-1])
545
+ || (o._start.day >= 1 && o._end.day < MONTH_END_DAY[o._end.month-1])
546
+ || (o._start.day > 1 && o._end.day <= MONTH_END_DAY[o._end.month-1])
547
+ ) {
548
+ result = true;
549
+ }
550
+ }
551
+ }
552
+ }
553
+ else {
554
+ if(this._end == null) {
555
+ if(this._start.month == o._start.month) {
556
+ result = true;
557
+ }
558
+ }
559
+ else {
560
+ if(o._start.month >= this._start.month && o._start.month <= this._end.month) {
561
+ result = true;
562
+ }
563
+ }
564
+ }
565
+ }
566
+ }
567
+
568
+ return result;
569
+ };
570
+
571
+
572
+
573
+ /**
574
+ * Class Day, represents a typical day
575
+ */
576
+ var Day = function() {
577
+ //ATTRIBUTES
578
+ /** The intervals defining this week **/
579
+ this._intervals = [];
580
+
581
+ /** The next interval ID **/
582
+ this._nextInterval = 0;
583
+ };
584
+
585
+ //ACCESSORS
586
+ /**
587
+ * @return This day, as a boolean array (minutes since midnight). True if open, false else.
588
+ */
589
+ Day.prototype.getAsMinutesArray = function() {
590
+ //Create array with all values set to false
591
+ //For each minute
592
+ var minuteArray = [];
593
+ for (var minute = 0; minute <= MINUTES_MAX; minute++) {
594
+ minuteArray[minute] = false;
595
+ }
596
+
597
+ //Set to true values where an interval is defined
598
+ for(var id=0, l=this._intervals.length; id < l; id++) {
599
+ if(this._intervals[id] != undefined) {
600
+ var startMinute = null;
601
+ var endMinute = null;
602
+
603
+ if(
604
+ this._intervals[id].getStartDay() == this._intervals[id].getEndDay()
605
+ || (this._intervals[id].getEndDay() == DAYS_MAX && this._intervals[id].getTo() == MINUTES_MAX)
606
+ ) {
607
+ //Define start and end minute regarding the current day
608
+ startMinute = this._intervals[id].getFrom();
609
+ endMinute = this._intervals[id].getTo();
610
+ }
611
+
612
+ //Set to true the minutes for this day
613
+ if(startMinute != null && endMinute != null){
614
+ for(var minute = startMinute; minute <= endMinute; minute++) {
615
+ minuteArray[minute] = true;
616
+ }
617
+ }
618
+ else {
619
+ console.log(this._intervals[id].getFrom()+" "+this._intervals[id].getTo()+" "+this._intervals[id].getStartDay()+" "+this._intervals[id].getEndDay());
620
+ throw new Error("Invalid interval");
621
+ }
622
+ }
623
+ }
624
+
625
+ return minuteArray;
626
+ };
627
+
628
+ /**
629
+ * @param clean Clean intervals ? (default: false)
630
+ * @return The intervals in this week
631
+ */
632
+ Day.prototype.getIntervals = function(clean) {
633
+ clean = clean || false;
634
+
635
+ if(clean) {
636
+ //Create continuous intervals over days
637
+ var minuteArray = this.getAsMinutesArray();
638
+ var intervals = [];
639
+ var minStart = -1, minEnd;
640
+
641
+ for(var min=0, lm=minuteArray.length; min < lm; min++) {
642
+ //First minute
643
+ if(min == 0) {
644
+ if(minuteArray[min]) {
645
+ minStart = min;
646
+ }
647
+ }
648
+ //Last minute
649
+ else if(min == lm-1) {
650
+ if(minuteArray[min]) {
651
+ intervals.push(new Interval(
652
+ 0,
653
+ 0,
654
+ minStart,
655
+ min
656
+ ));
657
+ }
658
+ }
659
+ //Other minutes
660
+ else {
661
+ //New interval
662
+ if(minuteArray[min] && minStart < 0) {
663
+ minStart = min;
664
+ }
665
+ //Ending interval
666
+ else if(!minuteArray[min] && minStart >= 0) {
667
+ intervals.push(new Interval(
668
+ 0,
669
+ 0,
670
+ minStart,
671
+ min-1
672
+ ));
673
+
674
+ minStart = -1;
675
+ }
676
+ }
677
+ }
678
+
679
+ return intervals;
680
+ }
681
+ else {
682
+ return this._intervals;
683
+ }
684
+ };
685
+
686
+ //MODIFIERS
687
+ /**
688
+ * Add a new interval to this week
689
+ * @param interval The new interval
690
+ * @return The ID of the added interval
691
+ */
692
+ Day.prototype.addInterval = function(interval) {
693
+ this._intervals[this._nextInterval] = interval;
694
+ this._nextInterval++;
695
+
696
+ return this._nextInterval-1;
697
+ };
698
+
699
+ /**
700
+ * Edits the given interval
701
+ * @param id The interval ID
702
+ * @param interval The new interval
703
+ */
704
+ Day.prototype.editInterval = function(id, interval) {
705
+ this._intervals[id] = interval;
706
+ };
707
+
708
+ /**
709
+ * Remove the given interval
710
+ * @param id the interval ID
711
+ */
712
+ Day.prototype.removeInterval = function(id) {
713
+ this._intervals[id] = undefined;
714
+ };
715
+
716
+ /**
717
+ * Redefines this date range intervals with a copy of the given ones
718
+ */
719
+ Day.prototype.copyIntervals = function(intervals) {
720
+ this._intervals = [];
721
+ for(var i=0; i < intervals.length; i++) {
722
+ if(intervals[i] != undefined && intervals[i].getStartDay() == 0 && intervals[i].getEndDay() == 0) {
723
+ this._intervals.push(structuredClone(intervals[i]));
724
+ }
725
+ }
726
+
727
+ this._intervals = this.getIntervals(true);
728
+ };
729
+
730
+ /**
731
+ * Removes all defined intervals
732
+ */
733
+ Day.prototype.clearIntervals = function() {
734
+ this._intervals = [];
735
+ };
736
+
737
+ //OTHER METHODS
738
+ /**
739
+ * Is this day defining the same intervals as the given one ?
740
+ */
741
+ Day.prototype.sameAs = function(d) {
742
+ return d.getAsMinutesArray().equals(this.getAsMinutesArray());
743
+ };
744
+
745
+
746
+
747
+ /**
748
+ * Class Week, represents a typical week of opening hours.
749
+ */
750
+ var Week = function() {
751
+ //ATTRIBUTES
752
+ /** The intervals defining this week **/
753
+ this._intervals = [];
754
+ };
755
+
756
+ //ACCESSORS
757
+ /**
758
+ * @return 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.
759
+ */
760
+ Week.prototype.getAsMinutesArray = function() {
761
+ //Create array with all values set to false
762
+ //For each day
763
+ var minuteArray = [];
764
+ for(var day = 0; day <= DAYS_MAX; day++) {
765
+ //For each minute
766
+ minuteArray[day] = [];
767
+ for (var minute = 0; minute <= MINUTES_MAX; minute++) {
768
+ minuteArray[day][minute] = false;
769
+ }
770
+ }
771
+
772
+ //Set to true values where an interval is defined
773
+ for(var id=0, l=this._intervals.length; id < l; id++) {
774
+ if(this._intervals[id] != undefined) {
775
+ for(var day = this._intervals[id].getStartDay(); day <= this._intervals[id].getEndDay(); day++) {
776
+ //Define start and end minute regarding the current day
777
+ var startMinute = (day == this._intervals[id].getStartDay()) ? this._intervals[id].getFrom() : 0;
778
+ var endMinute = (day == this._intervals[id].getEndDay()) ? this._intervals[id].getTo() : MINUTES_MAX;
779
+
780
+ //Set to true the minutes for this day
781
+ if(startMinute != null && endMinute != null) {
782
+ for(var minute = startMinute; minute <= endMinute; minute++) {
783
+ minuteArray[day][minute] = true;
784
+ }
785
+ }
786
+ }
787
+ }
788
+ }
789
+
790
+ return minuteArray;
791
+ };
792
+
793
+ /**
794
+ * @param clean Clean intervals ? (default: false)
795
+ * @return The intervals in this week
796
+ */
797
+ Week.prototype.getIntervals = function(clean) {
798
+ clean = clean || false;
799
+
800
+ if(clean) {
801
+ //Create continuous intervals over days
802
+ var minuteArray = this.getAsMinutesArray();
803
+ var intervals = [];
804
+ var dayStart = -1, minStart = -1, minEnd;
805
+
806
+ for(var day=0, l=minuteArray.length; day < l; day++) {
807
+ for(var min=0, lm=minuteArray[day].length; min < lm; min++) {
808
+ //First minute of monday
809
+ if(day == 0 && min == 0) {
810
+ if(minuteArray[day][min]) {
811
+ dayStart = day;
812
+ minStart = min;
813
+ }
814
+ }
815
+ //Last minute of sunday
816
+ else if(day == DAYS_MAX && min == lm-1) {
817
+ if(dayStart >= 0 && minuteArray[day][min]) {
818
+ intervals.push(new Interval(
819
+ dayStart,
820
+ day,
821
+ minStart,
822
+ min
823
+ ));
824
+ }
825
+ }
826
+ //Other days or minutes
827
+ else {
828
+ //New interval
829
+ if(minuteArray[day][min] && dayStart < 0) {
830
+ dayStart = day;
831
+ minStart = min;
832
+ }
833
+ //Ending interval
834
+ else if(!minuteArray[day][min] && dayStart >= 0) {
835
+ if(min == 0) {
836
+ intervals.push(new Interval(
837
+ dayStart,
838
+ day-1,
839
+ minStart,
840
+ MINUTES_MAX
841
+ ));
842
+ }
843
+ else {
844
+ intervals.push(new Interval(
845
+ dayStart,
846
+ day,
847
+ minStart,
848
+ min-1
849
+ ));
850
+ }
851
+ dayStart = -1;
852
+ minStart = -1;
853
+ }
854
+ }
855
+ }
856
+ }
857
+
858
+ return intervals;
859
+ }
860
+ else {
861
+ return this._intervals;
862
+ }
863
+ };
864
+
865
+ /**
866
+ * Returns the intervals which are different from those defined in the given week
867
+ * @param w The general week
868
+ * @return The intervals which are different, as object { open: [ Intervals ], closed: [ Intervals ] }
869
+ */
870
+ Week.prototype.getIntervalsDiff = function(w) {
871
+ //Get minutes arrays
872
+ var myMinArray = this.getAsMinutesArray();
873
+ var wMinArray = w.getAsMinutesArray();
874
+
875
+ //Create diff array
876
+ var intervals = { open: [], closed: [] };
877
+ var dayStart = -1, minStart = -1, minEnd;
878
+ var diffDay, m, intervalsLength;
879
+
880
+ for(var d=0; d <= DAYS_MAX; d++) {
881
+ diffDay = false;
882
+ m = 0;
883
+ intervalsLength = intervals.open.length;
884
+
885
+ while(m <= MINUTES_MAX) {
886
+ //Copy entire day
887
+ if(diffDay) {
888
+ //First minute of monday
889
+ if(d == 0 && m == 0) {
890
+ if(myMinArray[d][m]) {
891
+ dayStart = d;
892
+ minStart = m;
893
+ }
894
+ }
895
+ //Last minute of sunday
896
+ else if(d == DAYS_MAX && m == MINUTES_MAX) {
897
+ if(dayStart >= 0 && myMinArray[d][m]) {
898
+ intervals.open.push(new Interval(
899
+ dayStart,
900
+ d,
901
+ minStart,
902
+ m
903
+ ));
904
+ }
905
+ }
906
+ //Other days or minutes
907
+ else {
908
+ //New interval
909
+ if(myMinArray[d][m] && dayStart < 0) {
910
+ dayStart = d;
911
+ minStart = m;
912
+ }
913
+ //Ending interval
914
+ else if(!myMinArray[d][m] && dayStart >= 0) {
915
+ if(m == 0) {
916
+ intervals.open.push(new Interval(
917
+ dayStart,
918
+ d-1,
919
+ minStart,
920
+ MINUTES_MAX
921
+ ));
922
+ }
923
+ else {
924
+ intervals.open.push(new Interval(
925
+ dayStart,
926
+ d,
927
+ minStart,
928
+ m-1
929
+ ));
930
+ }
931
+ dayStart = -1;
932
+ minStart = -1;
933
+ }
934
+ }
935
+ m++;
936
+ }
937
+ //Check for diff
938
+ else {
939
+ diffDay = myMinArray[d][m] ? !wMinArray[d][m] : wMinArray[d][m];
940
+
941
+ //If there is a difference, start to copy full day
942
+ if(diffDay) {
943
+ m = 0;
944
+ }
945
+ //Else, continue checking
946
+ else {
947
+ m++;
948
+ }
949
+ }
950
+ }
951
+
952
+ //Close intervals if day is identical
953
+ if(!diffDay && dayStart > -1) {
954
+ intervals.open.push(new Interval(
955
+ dayStart,
956
+ d-1,
957
+ minStart,
958
+ MINUTES_MAX
959
+ ));
960
+ dayStart = -1;
961
+ minStart = -1;
962
+ }
963
+
964
+ //Create closed intervals if closed all day
965
+ if(diffDay && dayStart == -1 && intervalsLength == intervals.open.length) {
966
+ //Merge with previous interval if possible
967
+ if(intervals.closed.length > 0 && intervals.closed[intervals.closed.length-1].getEndDay() == d - 1) {
968
+ intervals.closed[intervals.closed.length-1] = new Interval(
969
+ intervals.closed[intervals.closed.length-1].getStartDay(),
970
+ d,
971
+ 0,
972
+ MINUTES_MAX
973
+ );
974
+ }
975
+ else {
976
+ intervals.closed.push(new Interval(d, d, 0, MINUTES_MAX));
977
+ }
978
+ }
979
+ }
980
+
981
+ return intervals;
982
+ };
983
+
984
+ //MODIFIERS
985
+ /**
986
+ * Add a new interval to this week
987
+ * @param interval The new interval
988
+ * @return The ID of the added interval
989
+ */
990
+ Week.prototype.addInterval = function(interval) {
991
+ this._intervals[this._intervals.length] = interval;
992
+ return this._intervals.length-1;
993
+ };
994
+
995
+ /**
996
+ * Edits the given interval
997
+ * @param id The interval ID
998
+ * @param interval The new interval
999
+ */
1000
+ Week.prototype.editInterval = function(id, interval) {
1001
+ this._intervals[id] = interval;
1002
+ };
1003
+
1004
+ /**
1005
+ * Remove the given interval
1006
+ * @param id the interval ID
1007
+ */
1008
+ Week.prototype.removeInterval = function(id) {
1009
+ this._intervals[id] = undefined;
1010
+ };
1011
+
1012
+ /**
1013
+ * Removes all intervals during a given day
1014
+ */
1015
+ Week.prototype.removeIntervalsDuringDay = function(day) {
1016
+ var interval, itLength = this._intervals.length, dayDiff;
1017
+ for(var i=0; i < itLength; i++) {
1018
+ interval = this._intervals[i];
1019
+ if(interval != undefined) {
1020
+ //If interval over given day
1021
+ if(interval.getStartDay() <= day && interval.getEndDay() >= day) {
1022
+ dayDiff = interval.getEndDay() - interval.getStartDay();
1023
+
1024
+ //Avoid deletion if over night interval
1025
+ if(dayDiff > 1 || dayDiff == 0 || interval.getStartDay() == day || interval.getFrom() <= interval.getTo()) {
1026
+ //Create new interval if several day
1027
+ if(interval.getEndDay() - interval.getStartDay() >= 1 && interval.getFrom() <= interval.getTo()) {
1028
+ if(interval.getStartDay() < day) {
1029
+ this.addInterval(new Interval(interval.getStartDay(), day-1, interval.getFrom(), 24*60));
1030
+ }
1031
+ if(interval.getEndDay() > day) {
1032
+ this.addInterval(new Interval(day+1, interval.getEndDay(), 0, interval.getTo()));
1033
+ }
1034
+ }
1035
+
1036
+ //Delete
1037
+ this.removeInterval(i);
1038
+ }
1039
+ }
1040
+ }
1041
+ }
1042
+ };
1043
+
1044
+ /**
1045
+ * Redefines this date range intervals with a copy of the given ones
1046
+ */
1047
+ Week.prototype.copyIntervals = function(intervals) {
1048
+ this._intervals = [];
1049
+ for(var i=0; i < intervals.length; i++) {
1050
+ if(intervals[i] != undefined) {
1051
+ this._intervals.push(structuredClone(intervals[i]));
1052
+ }
1053
+ }
1054
+ };
1055
+
1056
+ //OTHER METHODS
1057
+ /**
1058
+ * Is this week defining the same intervals as the given one ?
1059
+ */
1060
+ Week.prototype.sameAs = function(w) {
1061
+ return w.getAsMinutesArray().equals(this.getAsMinutesArray());
1062
+ };
1063
+
1064
+
1065
+
1066
+ /**
1067
+ * Class DateRange, defines a range of months, weeks or days.
1068
+ * A typical week or day will be associated.
1069
+ */
1070
+ var DateRange = function(w) {
1071
+ //ATTRIBUTES
1072
+ /** The wide interval of this date range **/
1073
+ this._wideInterval = null;
1074
+
1075
+ /** The typical week or day associated **/
1076
+ this._typical = undefined;
1077
+
1078
+ //CONSTRUCTOR
1079
+ this.updateRange(w);
1080
+ };
1081
+
1082
+ //ACCESSORS
1083
+ /**
1084
+ * Is this interval defining a typical day ?
1085
+ */
1086
+ DateRange.prototype.definesTypicalDay = function() {
1087
+ return this._typical instanceof Day;
1088
+ };
1089
+
1090
+ /**
1091
+ * Is this interval defining a typical week ?
1092
+ */
1093
+ DateRange.prototype.definesTypicalWeek = function() {
1094
+ return this._typical instanceof Week;
1095
+ };
1096
+
1097
+ /**
1098
+ * @return The typical day or week
1099
+ */
1100
+ DateRange.prototype.getTypical = function() {
1101
+ return this._typical;
1102
+ };
1103
+
1104
+ /**
1105
+ * @return The wide interval this date range concerns
1106
+ */
1107
+ DateRange.prototype.getInterval = function() {
1108
+ return this._wideInterval;
1109
+ };
1110
+
1111
+ //MODIFIERS
1112
+ /**
1113
+ * Changes the date range
1114
+ */
1115
+ DateRange.prototype.updateRange = function(wide) {
1116
+ this._wideInterval = (wide != null) ? wide : new WideInterval().always();
1117
+
1118
+ //Create typical week/day
1119
+ if(this._typical == undefined) {
1120
+ switch(this._wideInterval.getType()) {
1121
+ case "day":
1122
+ if(this._wideInterval.getEnd() == null) {
1123
+ this._typical = new Day();
1124
+ }
1125
+ else {
1126
+ this._typical = new Week();
1127
+ }
1128
+ break;
1129
+ case "week":
1130
+ this._typical = new Week();
1131
+ break;
1132
+ case "month":
1133
+ this._typical = new Week();
1134
+ break;
1135
+ case "holiday":
1136
+ if(this._wideInterval.getStart().holiday == "SH") {
1137
+ this._typical = new Week();
1138
+ }
1139
+ else {
1140
+ this._typical = new Day();
1141
+ }
1142
+ break;
1143
+ case "always":
1144
+ this._typical = new Week();
1145
+ break;
1146
+ default:
1147
+ throw Error("Invalid interval type: "+this._wideInterval.getType());
1148
+ }
1149
+ }
1150
+ };
1151
+
1152
+ //OTHER METHODS
1153
+ /**
1154
+ * Check if the typical day/week of this date range is the same as in the given date range
1155
+ * @param dr The other DateRange
1156
+ * @return True if same typical day/week
1157
+ */
1158
+ DateRange.prototype.hasSameTypical = function(dr) {
1159
+ return this.definesTypicalDay() == dr.definesTypicalDay() && this._typical.sameAs(dr.getTypical());
1160
+ };
1161
+
1162
+ /**
1163
+ * Does this date range contains the given date range (ie the second is a refinement of the first)
1164
+ * @param start The start of the date range
1165
+ * @param end The end of the date range
1166
+ * @return True if this date contains the given one (and is not strictly equal to)
1167
+ */
1168
+ DateRange.prototype.isGeneralFor = function(dr) {
1169
+ return dr.definesTypicalDay() == this.definesTypicalDay() && this._wideInterval.contains(dr.getInterval());
1170
+ };
1171
+
1172
+
1173
+
1174
+ /**
1175
+ * An opening_hours time, such as "08:00" or "08:00-10:00" or "off" (if no start and end)
1176
+ * @param start The start minute (from midnight), can be null
1177
+ * @param end The end minute (from midnight), can be null
1178
+ */
1179
+ var OhTime = function(start, end) {
1180
+ //ATTRIBUTES
1181
+ /** The start minute **/
1182
+ this._start = (start >= 0) ? start : null;
1183
+
1184
+ /** The end minute **/
1185
+ this._end = (end >= 0 && end != start) ? end : null;
1186
+ };
1187
+
1188
+ //ACCESSORS
1189
+ /**
1190
+ * @return The time in opening_hours format
1191
+ */
1192
+ OhTime.prototype.get = function() {
1193
+ if(this._start === null && this._end === null) {
1194
+ return "off";
1195
+ }
1196
+ else {
1197
+ return this._timeString(this._start) + ((this._end == null) ? "" : "-" + this._timeString(this._end));
1198
+ }
1199
+ };
1200
+
1201
+ /**
1202
+ * @return The start minutes
1203
+ */
1204
+ OhTime.prototype.getStart = function() {
1205
+ return this._start;
1206
+ };
1207
+
1208
+ /**
1209
+ * @return The end minutes
1210
+ */
1211
+ OhTime.prototype.getEnd = function() {
1212
+ return this._end;
1213
+ };
1214
+
1215
+ /**
1216
+ * @return True if same time
1217
+ */
1218
+ OhTime.prototype.equals = function(t) {
1219
+ return this._start == t.getStart() && this._end == t.getEnd();
1220
+ };
1221
+
1222
+ //OTHER METHODS
1223
+ /**
1224
+ * @return The hour in HH:MM format
1225
+ */
1226
+ OhTime.prototype._timeString = function(minutes) {
1227
+ var h = Math.floor(minutes / 60);
1228
+ var period = "";
1229
+ var m = minutes % 60;
1230
+ return (h < 10 ? "0" : "") + h + ":" + (m < 10 ? "0" : "") + m + period;
1231
+ };
1232
+
1233
+
1234
+
1235
+ /**
1236
+ * An opening_hours date, such as "Apr 21", "week 1-15 Mo,Tu", "Apr-Dec Mo-Fr", "SH Su", ...
1237
+ * @param w The wide selector, as string
1238
+ * @param wt The wide selector type (month, week, day, holiday, always)
1239
+ * @param wd The weekdays, as integer array (0 to 6 = Monday to Sunday, -1 = single day date, -2 = PH)
1240
+ */
1241
+ var OhDate = function(w, wt, wd) {
1242
+ //ATTRIBUTES
1243
+ /** Kind of wide date (month, week, day, holiday, always) **/
1244
+ this._wideType = wt;
1245
+
1246
+ /** Wide date **/
1247
+ this._wide = w;
1248
+
1249
+ /** Weekdays + PH **/
1250
+ this._weekdays = wd.sort();
1251
+
1252
+ /** Overwritten days (to allow create simpler rules) **/
1253
+ this._wdOver = [];
1254
+
1255
+ //CONSTRUCTOR
1256
+ if(w == null || wt == null || wd == null) {
1257
+ throw Error("Missing parameter");
1258
+ }
1259
+ };
1260
+
1261
+ //ACCESSORS
1262
+ /**
1263
+ * @return The wide type
1264
+ */
1265
+ OhDate.prototype.getWideType = function() {
1266
+ return this._wideType;
1267
+ };
1268
+
1269
+ /**
1270
+ * @return The monthday, month, week, SH (depends of type)
1271
+ */
1272
+ OhDate.prototype.getWideValue = function() {
1273
+ return this._wide;
1274
+ };
1275
+
1276
+ /**
1277
+ * @return The weekdays array
1278
+ */
1279
+ OhDate.prototype.getWd = function() {
1280
+ return this._weekdays;
1281
+ };
1282
+
1283
+ /**
1284
+ * @return The overwrittent weekdays array
1285
+ */
1286
+ OhDate.prototype.getWdOver = function() {
1287
+ return this._wdOver;
1288
+ };
1289
+
1290
+ /**
1291
+ * @param a The other weekdays array
1292
+ * @return True if same weekdays as other object
1293
+ */
1294
+ OhDate.prototype.sameWd = function(a) {
1295
+ return a.equals(this._weekdays);
1296
+ };
1297
+
1298
+ /**
1299
+ * @return The weekdays in opening_hours syntax
1300
+ */
1301
+ OhDate.prototype.getWeekdays = function() {
1302
+ var result = "";
1303
+ var wd = this._weekdays.concat(this._wdOver).sort();
1304
+
1305
+ //PH as weekday
1306
+ if(wd.length > 0 && wd[0] == PH_WEEKDAY) {
1307
+ result = "PH";
1308
+ wd.shift();
1309
+ }
1310
+
1311
+ //Check if we should create a continuous interval for week-end
1312
+ if(wd.length > 0 && wd.contains(6) && wd.contains(0) && (wd.contains(5) || wd.contains(1))) {
1313
+ //Find when the week-end starts
1314
+ var startWE = 6;
1315
+ var i=wd.length-2, stopLooking = false;
1316
+ while(!stopLooking && i >= 0) {
1317
+ if(wd[i] == wd[i+1] - 1) {
1318
+ startWE = wd[i];
1319
+ i--;
1320
+ }
1321
+ else {
1322
+ stopLooking = true;
1323
+ }
1324
+ }
1325
+
1326
+ //Find when it stops
1327
+ i=1;
1328
+ stopLooking = false;
1329
+ var endWE = 0;
1330
+
1331
+ while(!stopLooking && i < wd.length) {
1332
+ if(wd[i-1] == wd[i] - 1) {
1333
+ endWE = wd[i];
1334
+ i++;
1335
+ }
1336
+ else {
1337
+ stopLooking = true;
1338
+ }
1339
+ }
1340
+
1341
+ //If long enough, add it as first weekday interval
1342
+ var length = 7 - startWE + endWE + 1;
1343
+
1344
+ if(length >= 3 && startWE > endWE) {
1345
+ if(result.length > 0) { result += ","; }
1346
+ result += OSM_DAYS[startWE]+"-"+OSM_DAYS[endWE];
1347
+
1348
+ //Remove processed days
1349
+ var j=0;
1350
+ while(j < wd.length) {
1351
+ if(wd[j] <= endWE || wd[j] >= startWE) {
1352
+ wd.splice(j, 1);
1353
+ }
1354
+ else {
1355
+ j++;
1356
+ }
1357
+ }
1358
+ }
1359
+ }
1360
+
1361
+ //Process only if not empty weekday list
1362
+ if(wd.length > 1 || (wd.length == 1 && wd[0] != -1)) {
1363
+ result += (result.length > 0) ? ","+OSM_DAYS[wd[0]] : OSM_DAYS[wd[0]];
1364
+ var firstInRow = wd[0];
1365
+
1366
+ for(var i=1; i < wd.length; i++) {
1367
+ //When days aren't following
1368
+ if(wd[i-1] != wd[i] - 1) {
1369
+ //Previous day range length > 1
1370
+ if(firstInRow != wd[i-1]) {
1371
+ //Two days
1372
+ if(wd[i-1] - firstInRow == 1) {
1373
+ result += ","+OSM_DAYS[wd[i-1]];
1374
+ }
1375
+ else {
1376
+ result += "-"+OSM_DAYS[wd[i-1]];
1377
+ }
1378
+ }
1379
+
1380
+ //Add the current day
1381
+ result += ","+OSM_DAYS[wd[i]];
1382
+ firstInRow = wd[i];
1383
+ }
1384
+ else if(i==wd.length-1) {
1385
+ if(wd[i] - firstInRow == 1) {
1386
+ result += ","+OSM_DAYS[wd[i]];
1387
+ }
1388
+ else {
1389
+ result += "-"+OSM_DAYS[wd[i]];
1390
+ }
1391
+ }
1392
+ }
1393
+ }
1394
+
1395
+ if(result == "Mo-Su") { result = ""; }
1396
+
1397
+ return result;
1398
+ };
1399
+
1400
+ /**
1401
+ * Is the given object of the same kind as this one
1402
+ * @return True if same weekdays and same wide type
1403
+ */
1404
+ OhDate.prototype.sameKindAs = function(d) {
1405
+ return this._wideType == d.getWideType() && d.sameWd(this._weekdays);
1406
+ };
1407
+
1408
+ /**
1409
+ * @return True if this object is equal to the given one
1410
+ */
1411
+ OhDate.prototype.equals = function(o) {
1412
+ return o instanceof OhDate && this._wideType == o.getWideType() && this._wide == o.getWideValue() && o.sameWd(this._weekdays);
1413
+ };
1414
+
1415
+ //MODIFIERS
1416
+ /**
1417
+ * Adds a new weekday in this date
1418
+ */
1419
+ OhDate.prototype.addWeekday = function(wd) {
1420
+ if(!this._weekdays.contains(wd) && !this._wdOver.contains(wd)) {
1421
+ this._weekdays.push(wd);
1422
+ this._weekdays = this._weekdays.sort();
1423
+ }
1424
+ };
1425
+
1426
+ /**
1427
+ * Adds public holiday as a weekday of this date
1428
+ */
1429
+ OhDate.prototype.addPhWeekday = function() {
1430
+ this.addWeekday(PH_WEEKDAY);
1431
+ };
1432
+
1433
+ /**
1434
+ * Adds an overwritten weekday, which can be included in this date and that will be overwritten in a following rule
1435
+ */
1436
+ OhDate.prototype.addOverwrittenWeekday = function(wd) {
1437
+ if(!this._wdOver.contains(wd) && !this._weekdays.contains(wd)) {
1438
+ this._wdOver.push(wd);
1439
+ this._wdOver = this._wdOver.sort();
1440
+ }
1441
+ }
1442
+
1443
+
1444
+
1445
+ /**
1446
+ * An opening_hours rule, such as "Mo,Tu 08:00-18:00"
1447
+ */
1448
+ var OhRule = function() {
1449
+ //ATTRIBUTES
1450
+ /** The date selectors **/
1451
+ this._date = [];
1452
+
1453
+ /** The time selectors **/
1454
+ this._time = [];
1455
+ };
1456
+
1457
+ //ACCESSORS
1458
+ /**
1459
+ * @return The date selectors, as an array
1460
+ */
1461
+ OhRule.prototype.getDate = function() {
1462
+ return this._date;
1463
+ };
1464
+
1465
+ /**
1466
+ * @return The time selectors, as an array
1467
+ */
1468
+ OhRule.prototype.getTime = function() {
1469
+ return this._time;
1470
+ };
1471
+
1472
+ /**
1473
+ * @return The opening_hours value
1474
+ */
1475
+ OhRule.prototype.get = function() {
1476
+ var result = "";
1477
+
1478
+ //Create date part
1479
+ if(this._date.length > 1 || this._date[0].getWideValue() != "") {
1480
+ //Add wide selectors
1481
+ for(var i=0, l=this._date.length; i < l; i++) {
1482
+ if(i > 0) {
1483
+ result += ",";
1484
+ }
1485
+ result += this._date[i].getWideValue();
1486
+ }
1487
+ }
1488
+
1489
+ //Add weekdays
1490
+ if(this._date.length > 0) {
1491
+ var wd = this._date[0].getWeekdays();
1492
+ if(wd.length > 0) {
1493
+ result += " "+wd;
1494
+ }
1495
+ }
1496
+
1497
+ //Create time part
1498
+ if(this._time.length > 0) {
1499
+ result += " ";
1500
+ for(var i=0, l=this._time.length; i < l; i++) {
1501
+ if(i > 0) {
1502
+ result += ",";
1503
+ }
1504
+ result += this._time[i].get();
1505
+ }
1506
+ }
1507
+ else {
1508
+ result += " off";
1509
+ }
1510
+
1511
+ if(result.trim() == "00:00-24:00") { result = "24/7"; }
1512
+
1513
+ return result.trim();
1514
+ };
1515
+
1516
+ /**
1517
+ * @return True if the given rule has the same time as this one
1518
+ */
1519
+ OhRule.prototype.sameTime = function(o) {
1520
+ if(o == undefined || o == null || o.getTime().length != this._time.length) {
1521
+ return false;
1522
+ }
1523
+ else {
1524
+ for(var i=0, l=this._time.length; i < l; i++) {
1525
+ if(!this._time[i].equals(o.getTime()[i])) {
1526
+ return false;
1527
+ }
1528
+ }
1529
+ return true;
1530
+ }
1531
+ };
1532
+
1533
+ /**
1534
+ * Is this rule concerning off time ?
1535
+ */
1536
+ OhRule.prototype.isOff = function() {
1537
+ return this._time.length == 0 || (this._time.length == 1 && this._time[0].getStart() == null);
1538
+ };
1539
+
1540
+ /**
1541
+ * Does the rule have any overwritten weekday ?
1542
+ */
1543
+ OhRule.prototype.hasOverwrittenWeekday = function() {
1544
+ return this._date.length > 0 && this._date[0]._wdOver.length > 0;
1545
+ };
1546
+
1547
+ //MODIFIERS
1548
+ /**
1549
+ * Adds a weekday to all the dates
1550
+ */
1551
+ OhRule.prototype.addWeekday = function(wd) {
1552
+ for(var i=0; i < this._date.length; i++) {
1553
+ this._date[i].addWeekday(wd);
1554
+ }
1555
+ };
1556
+
1557
+ /**
1558
+ * Adds public holidays as weekday to all dates
1559
+ */
1560
+ OhRule.prototype.addPhWeekday = function() {
1561
+ for(var i=0; i < this._date.length; i++) {
1562
+ this._date[i].addPhWeekday();
1563
+ }
1564
+ };
1565
+
1566
+ /**
1567
+ * Adds an overwritten weekday to all the dates
1568
+ */
1569
+ OhRule.prototype.addOverwrittenWeekday = function(wd) {
1570
+ for(var i=0; i < this._date.length; i++) {
1571
+ this._date[i].addOverwrittenWeekday(wd);
1572
+ }
1573
+ };
1574
+
1575
+ /**
1576
+ * @param d A new date selector
1577
+ */
1578
+ OhRule.prototype.addDate = function(d) {
1579
+ //Check param
1580
+ if(d == null || d == undefined || !d instanceof OhDate) {
1581
+ throw Error("Invalid parameter");
1582
+ }
1583
+
1584
+ //Check if date can be added
1585
+ if(this._date.length == 0 || (this._date[0].getWideType() != "always" && this._date[0].sameKindAs(d))) {
1586
+ this._date.push(d);
1587
+ }
1588
+ else {
1589
+ if(this._date.length != 1 || this._date[0].getWideType() != "always" || !this._date[0].sameWd(d.getWd())) {
1590
+ throw Error("This date can't be added to this rule");
1591
+ }
1592
+ }
1593
+ };
1594
+
1595
+ /**
1596
+ * @param t A new time selector
1597
+ */
1598
+ OhRule.prototype.addTime = function(t) {
1599
+ if((this._time.length == 0 || this._time[0].get() != "off") && !this._time.contains(t)) {
1600
+ this._time.push(t);
1601
+ }
1602
+ else {
1603
+ throw Error("This time can't be added to this rule");
1604
+ }
1605
+ };
1606
+
1607
+
1608
+
1609
+ /**
1610
+ * Class OpeningHoursBuilder, creates opening_hours value from date range object
1611
+ */
1612
+ var OpeningHoursBuilder = function() {};
1613
+
1614
+ //OTHER METHODS
1615
+ /**
1616
+ * Parses several date ranges to create an opening_hours string
1617
+ * @param dateRanges The date ranges to parse
1618
+ * @return The opening_hours string
1619
+ */
1620
+ OpeningHoursBuilder.prototype.build = function(dateRanges) {
1621
+ var rules = [];
1622
+ var dateRange, ohrules, ohrule, ohruleAdded, ruleId, rangeGeneral, rangeGeneralFor;
1623
+
1624
+ //Read each date range
1625
+ for(var rangeId=0, l=dateRanges.length; rangeId < l; rangeId++) {
1626
+ dateRange = dateRanges[rangeId];
1627
+
1628
+ if(dateRange != undefined) {
1629
+ //Check if the defined typical week/day is not strictly equal to a previous wider rule
1630
+ rangeGeneral = null;
1631
+ rangeGeneralFor = null;
1632
+ var rangeGenId=rangeId-1;
1633
+ while(rangeGenId >= 0 && rangeGeneral == null) {
1634
+ if(dateRanges[rangeGenId] != undefined) {
1635
+ generalFor = dateRanges[rangeGenId].isGeneralFor(dateRange);
1636
+ if(
1637
+ dateRanges[rangeGenId].hasSameTypical(dateRange)
1638
+ && (
1639
+ dateRanges[rangeGenId].getInterval().equals(dateRange.getInterval())
1640
+ || generalFor
1641
+ )
1642
+ ) {
1643
+ rangeGeneral = rangeGenId;
1644
+ }
1645
+ else if(generalFor && dateRanges[rangeGenId].definesTypicalWeek() && dateRange.definesTypicalWeek()) {
1646
+ rangeGeneralFor = rangeGenId; //Keep this ID to make differences in order to simplify result
1647
+ }
1648
+ }
1649
+ rangeGenId--;
1650
+ }
1651
+
1652
+ if(rangeId == 0 || rangeGeneral == null) {
1653
+ //Get rules for this date range
1654
+ if(dateRange.definesTypicalWeek()) {
1655
+ if(rangeGeneralFor != null) {
1656
+ ohrules = this._buildWeekDiff(dateRange, dateRanges[rangeGeneralFor]);
1657
+ }
1658
+ else {
1659
+ ohrules = this._buildWeek(dateRange);
1660
+ }
1661
+ }
1662
+ else {
1663
+ ohrules = this._buildDay(dateRange);
1664
+ }
1665
+
1666
+ //Process each rule
1667
+ for(var ohruleId=0, orl=ohrules.length; ohruleId < orl; ohruleId++) {
1668
+ ohrule = ohrules[ohruleId];
1669
+ ohruleAdded = false;
1670
+ ruleId = 0;
1671
+
1672
+ //Try to add them to previously defined ones
1673
+ while(!ohruleAdded && ruleId < rules.length) {
1674
+ //Identical one
1675
+ if(rules[ruleId].sameTime(ohrule)) {
1676
+ try {
1677
+ for(var dateId=0, dl=ohrule.getDate().length; dateId < dl; dateId++) {
1678
+ rules[ruleId].addDate(ohrule.getDate()[dateId]);
1679
+ }
1680
+ ohruleAdded = true;
1681
+ }
1682
+ //If first date not same kind as in found rule, continue
1683
+ catch(e) {
1684
+ //But before, try to merge PH with always weekdays
1685
+ if(
1686
+ ohrule.getDate()[0].getWideType() == "holiday"
1687
+ && ohrule.getDate()[0].getWideValue() == "PH"
1688
+ && rules[ruleId].getDate()[0].getWideType() == "always"
1689
+ ) {
1690
+ rules[ruleId].addPhWeekday();
1691
+ ohruleAdded = true;
1692
+ }
1693
+ else if(
1694
+ rules[ruleId].getDate()[0].getWideType() == "holiday"
1695
+ && rules[ruleId].getDate()[0].getWideValue() == "PH"
1696
+ && ohrule.getDate()[0].getWideType() == "always"
1697
+ ) {
1698
+ ohrule.addPhWeekday();
1699
+ rules[ruleId] = ohrule;
1700
+ ohruleAdded = true;
1701
+ }
1702
+ else {
1703
+ ruleId++;
1704
+ }
1705
+ }
1706
+ }
1707
+ else {
1708
+ ruleId++;
1709
+ }
1710
+ }
1711
+
1712
+ //If not, add as new rule
1713
+ if(!ohruleAdded) {
1714
+ rules.push(ohrule);
1715
+ }
1716
+
1717
+ //If some overwritten weekdays are still in last rule
1718
+ if(ohruleId == orl - 1 && ohrule.hasOverwrittenWeekday()) {
1719
+ var ohruleOWD = new OhRule();
1720
+ for(var ohruleDateId = 0; ohruleDateId < ohrule.getDate().length; ohruleDateId++) {
1721
+ ohruleOWD.addDate(
1722
+ new OhDate(
1723
+ ohrule.getDate()[ohruleDateId].getWideValue(),
1724
+ ohrule.getDate()[ohruleDateId].getWideType(),
1725
+ ohrule.getDate()[ohruleDateId].getWdOver()
1726
+ )
1727
+ );
1728
+ }
1729
+ ohruleOWD.addTime(new OhTime());
1730
+ ohrules.push(ohruleOWD);
1731
+ orl++;
1732
+ }
1733
+ }
1734
+ }
1735
+ }
1736
+ }
1737
+
1738
+ //Create result string
1739
+ var result = "";
1740
+ for(var ruleId=0, l=rules.length; ruleId < l; ruleId++) {
1741
+ if(ruleId > 0) { result += "; "; }
1742
+ result += rules[ruleId].get();
1743
+ }
1744
+
1745
+ return result;
1746
+ };
1747
+
1748
+
1749
+ /***********************
1750
+ * Top level functions *
1751
+ ***********************/
1752
+
1753
+ /**
1754
+ * Creates rules for a given typical day
1755
+ * @param dateRange The date range defining a typical day
1756
+ * @return An array of OhRules
1757
+ */
1758
+ OpeningHoursBuilder.prototype._buildDay = function(dateRange) {
1759
+ var intervals = dateRange.getTypical().getIntervals(true);
1760
+ var interval;
1761
+
1762
+ //Create rule
1763
+ var rule = new OhRule();
1764
+ var date = new OhDate(dateRange.getInterval().getTimeSelector(), dateRange.getInterval().getType(), [ -1 ]);
1765
+ rule.addDate(date);
1766
+
1767
+ //Read time
1768
+ for(var i=0, l=intervals.length; i < l; i++) {
1769
+ interval = intervals[i];
1770
+
1771
+ if(interval != undefined) {
1772
+ rule.addTime(new OhTime(interval.getFrom(), interval.getTo()));
1773
+ }
1774
+ }
1775
+
1776
+ return [ rule ];
1777
+ };
1778
+
1779
+ /**
1780
+ * Create rules for a date range defining a typical week
1781
+ * Algorithm inspired by OpeningHoursEdit plugin for JOSM
1782
+ * @param dateRange The date range defining a typical day
1783
+ * @return An array of OhRules
1784
+ */
1785
+ OpeningHoursBuilder.prototype._buildWeek = function(dateRange) {
1786
+ var result = [];
1787
+ var intervals = dateRange.getTypical().getIntervals(true);
1788
+ var interval, rule, date;
1789
+
1790
+ /*
1791
+ * Create time intervals per day
1792
+ */
1793
+ var timeIntervals = this._createTimeIntervals(dateRange.getInterval().getTimeSelector(), dateRange.getInterval().getType(), intervals);
1794
+ var monday0 = timeIntervals[0];
1795
+ var sunday24 = timeIntervals[1];
1796
+ var days = timeIntervals[2];
1797
+
1798
+ //Create continuous night for monday-sunday
1799
+ days = this._nightMonSun(days, monday0, sunday24);
1800
+
1801
+ /*
1802
+ * Group rules with same time
1803
+ */
1804
+ // 0 means nothing done with this day yet
1805
+ // 8 means the day is off
1806
+ // -8 means the day is off and should be shown
1807
+ // 0<x<8 means the day have the openinghours of day x
1808
+ // -8<x<0 means nothing done with this day yet, but it intersects a
1809
+ // range of days with same opening_hours
1810
+ var daysStatus = [];
1811
+
1812
+ //Init status
1813
+ for(var i=0; i < OSM_DAYS.length; i++) {
1814
+ daysStatus[i] = 0;
1815
+ }
1816
+
1817
+ //Read status
1818
+ for(var i=0; i < days.length; i++) {
1819
+ if(days[i].isOff() && daysStatus[i] == 0) {
1820
+ daysStatus[i] = 8;
1821
+ }
1822
+ else if(days[i].isOff() && daysStatus[i] < 0 && daysStatus[i] > -8) {
1823
+ daysStatus[i] = -8;
1824
+
1825
+ //Try to merge with another off day
1826
+ var merged = false, mdOff = 0;
1827
+ while(!merged && mdOff < i) {
1828
+ if(days[mdOff].isOff()) {
1829
+ days[mdOff].addWeekday(i);
1830
+ merged = true;
1831
+ }
1832
+ else {
1833
+ mdOff++;
1834
+ }
1835
+ }
1836
+
1837
+ //If not merged, add it
1838
+ if(!merged) {
1839
+ result.push(days[i]);
1840
+ }
1841
+ } else if (daysStatus[i] <= 0 && daysStatus[i] > -8) {
1842
+ daysStatus[i] = i + 1;
1843
+ var lastSameDay = i;
1844
+ var sameDayCount = 1;
1845
+
1846
+ for(let j = i + 1; j < days.length; j++) {
1847
+ if (days[i].sameTime(days[j])) {
1848
+ daysStatus[j] = i + 1;
1849
+ days[i].addWeekday(j);
1850
+ lastSameDay = j;
1851
+ sameDayCount++;
1852
+ }
1853
+ }
1854
+ if (sameDayCount == 1) {
1855
+ // a single Day with this special opening_hours
1856
+ result.push(days[i]);
1857
+ } else if (sameDayCount == 2) {
1858
+ // exactly two Days with this special opening_hours
1859
+ days[i].addWeekday(lastSameDay);
1860
+ result.push(days[i]);
1861
+ } else if (sameDayCount > 2) {
1862
+ // more than two Days with this special opening_hours
1863
+ for (let j = i + 1; j < lastSameDay; j++) {
1864
+ if (daysStatus[j] == 0) {
1865
+ daysStatus[j] = -i - 1;
1866
+ days[i].addOverwrittenWeekday(j);
1867
+ }
1868
+ }
1869
+ days[i].addWeekday(lastSameDay);
1870
+ result.push(days[i]);
1871
+ }
1872
+ }
1873
+ }
1874
+
1875
+ result = this._mergeDays(result);
1876
+
1877
+ return result;
1878
+ };
1879
+
1880
+ /**
1881
+ * Reads a week to create an opening_hours string for weeks which are overwriting a previous one
1882
+ * @param dateRange The date range defining a typical day
1883
+ * @param generalDateRange The date range which is wider than this one
1884
+ * @return An array of OhRules
1885
+ */
1886
+ OpeningHoursBuilder.prototype._buildWeekDiff = function(dateRange, generalDateRange) {
1887
+ var intervals = dateRange.getTypical().getIntervalsDiff(generalDateRange.getTypical());
1888
+
1889
+ /*
1890
+ * Create time intervals per day
1891
+ */
1892
+ //Open
1893
+ var timeIntervals = this._createTimeIntervals(dateRange.getInterval().getTimeSelector(), dateRange.getInterval().getType(), intervals.open);
1894
+ var monday0 = timeIntervals[0];
1895
+ var sunday24 = timeIntervals[1];
1896
+ var days = timeIntervals[2];
1897
+
1898
+ //Closed
1899
+ for(var i=0, l=intervals.closed.length; i < l; i++) {
1900
+ interval = intervals.closed[i];
1901
+
1902
+ for(var j=interval.getStartDay(); j <= interval.getEndDay(); j++) {
1903
+ days[j].addTime(new OhTime());
1904
+ }
1905
+ }
1906
+
1907
+ //Create continuous night for monday-sunday
1908
+ days = this._nightMonSun(days, monday0, sunday24);
1909
+
1910
+ /*
1911
+ * Group rules with same time
1912
+ */
1913
+ // 0 means nothing done with this day yet
1914
+ // 8 means the day is off
1915
+ // -8 means the day is off and should be shown
1916
+ // 0<x<8 means the day have the openinghours of day x
1917
+ // -8<x<0 means nothing done with this day yet, but it intersects a
1918
+ // range of days with same opening_hours
1919
+ var daysStatus = [];
1920
+
1921
+ //Init status
1922
+ for(var i=0; i < OSM_DAYS.length; i++) {
1923
+ daysStatus[i] = 0;
1924
+ }
1925
+
1926
+ //Read rules
1927
+ var result = [];
1928
+ for(var i=0; i < days.length; i++) {
1929
+ //Off day which must be shown
1930
+ if(days[i].isOff() && days[i].getTime().length == 1) {
1931
+ daysStatus[i] = -8;
1932
+
1933
+ //Try to merge with another off day
1934
+ var merged = false, mdOff = 0;
1935
+ while(!merged && mdOff < i) {
1936
+ if(days[mdOff].isOff() && days[mdOff].getTime().length == 1) {
1937
+ days[mdOff].addWeekday(i);
1938
+ merged = true;
1939
+ }
1940
+ else {
1941
+ mdOff++;
1942
+ }
1943
+ }
1944
+
1945
+ //If not merged, add it
1946
+ if(!merged) {
1947
+ result.push(days[i]);
1948
+ }
1949
+ }
1950
+ //Off day which must be hidden
1951
+ else if(days[i].isOff() && days[i].getTime().length == 0) {
1952
+ daysStatus[i] = 8;
1953
+ }
1954
+ //Non-processed day
1955
+ else if(daysStatus[i] <= 0 && daysStatus[i] > -8) {
1956
+ daysStatus[i] = i+1;
1957
+ var sameDayCount = 1;
1958
+ var lastSameDay = i;
1959
+
1960
+ result.push(days[i]);
1961
+
1962
+ for(let j = i + 1; j < days.length; j++) {
1963
+ if (days[i].sameTime(days[j])) {
1964
+ daysStatus[j] = i + 1;
1965
+ days[i].addWeekday(j);
1966
+ lastSameDay = j;
1967
+ sameDayCount++;
1968
+ }
1969
+ }
1970
+ if (sameDayCount == 1) {
1971
+ // a single Day with this special opening_hours
1972
+ result.push(days[i]);
1973
+ } else if (sameDayCount == 2) {
1974
+ // exactly two Days with this special opening_hours
1975
+ days[i].addWeekday(lastSameDay);
1976
+ result.push(days[i]);
1977
+ } else if (sameDayCount > 2) {
1978
+ // more than two Days with this special opening_hours
1979
+ for (let j = i + 1; j < lastSameDay; j++) {
1980
+ if (daysStatus[j] == 0) {
1981
+ daysStatus[j] = -i - 1;
1982
+ if(days[j].getTime().length > 0) {
1983
+ days[i].addOverwrittenWeekday(j);
1984
+ }
1985
+ }
1986
+ }
1987
+ days[i].addWeekday(lastSameDay);
1988
+ result.push(days[i]);
1989
+ }
1990
+ }
1991
+ }
1992
+
1993
+ result = this._mergeDays(result);
1994
+
1995
+ return result;
1996
+ };
1997
+
1998
+
1999
+ /****************************************
2000
+ * Utility functions for top-level ones *
2001
+ ****************************************/
2002
+
2003
+ /**
2004
+ * Merge days with same opening time
2005
+ */
2006
+ OpeningHoursBuilder.prototype._mergeDays = function(rules) {
2007
+ if(rules.length == 0) { return rules; }
2008
+
2009
+ var result = [];
2010
+ var dateMerged;
2011
+
2012
+ result.push(rules[0]);
2013
+ var dm=0, wds;
2014
+ for(var d=1; d < rules.length; d++) {
2015
+ dateMerged = false;
2016
+ dm = 0;
2017
+ while(!dateMerged && dm < d) {
2018
+ if(rules[dm].sameTime(rules[d])) {
2019
+ wds = rules[d].getDate()[0].getWd();
2020
+ for(var wd=0; wd < wds.length; wd++) {
2021
+ rules[dm].addWeekday(wds[wd]);
2022
+ }
2023
+ dateMerged = true;
2024
+ }
2025
+ dm++;
2026
+ }
2027
+
2028
+ if(!dateMerged) {
2029
+ result.push(rules[d]);
2030
+ }
2031
+ }
2032
+
2033
+ return result;
2034
+ };
2035
+
2036
+ /**
2037
+ * Creates time intervals for each day
2038
+ * @return [ monday0, sunday24, days ]
2039
+ */
2040
+ OpeningHoursBuilder.prototype._createTimeIntervals = function(timeSelector, type, intervals) {
2041
+ var monday0 = -1;
2042
+ var sunday24 = -1;
2043
+ var days = [];
2044
+ var interval;
2045
+
2046
+ //Create rule for each day of the week
2047
+ for(var i=0; i < 7; i++) {
2048
+ days.push(new OhRule());
2049
+ days[i].addDate(new OhDate(timeSelector, type, [ i ]));
2050
+ }
2051
+
2052
+ for(var i=0, l=intervals.length; i < l; i++) {
2053
+ interval = intervals[i];
2054
+
2055
+ if(interval != undefined) {
2056
+ //Handle sunday 24:00 with monday 00:00
2057
+ if(interval.getStartDay() == DAYS_MAX && interval.getEndDay() == DAYS_MAX && interval.getTo() == MINUTES_MAX) {
2058
+ sunday24 = interval.getFrom();
2059
+ }
2060
+ if(interval.getStartDay() == 0 && interval.getEndDay() == 0 && interval.getFrom() == 0) {
2061
+ monday0 = interval.getTo();
2062
+ }
2063
+
2064
+ try {
2065
+ //Interval in a single day
2066
+ if(interval.getStartDay() == interval.getEndDay()) {
2067
+ days[interval.getStartDay()].addTime(
2068
+ new OhTime(interval.getFrom(), interval.getTo())
2069
+ );
2070
+ }
2071
+ //Interval on two days
2072
+ else if(interval.getEndDay() - interval.getStartDay() == 1) {
2073
+ //Continuous night
2074
+ if(interval.getFrom() > interval.getTo()) {
2075
+ days[interval.getStartDay()].addTime(
2076
+ new OhTime(interval.getFrom(), interval.getTo())
2077
+ );
2078
+ }
2079
+ //Separated days
2080
+ else {
2081
+ days[interval.getStartDay()].addTime(
2082
+ new OhTime(interval.getFrom(), MINUTES_MAX)
2083
+ );
2084
+ days[interval.getEndDay()].addTime(
2085
+ new OhTime(0, interval.getTo())
2086
+ );
2087
+ }
2088
+ }
2089
+ //Interval on more than two days
2090
+ else {
2091
+ for(var j=interval.getStartDay(), end=interval.getEndDay(); j <= end; j++) {
2092
+ if(j == interval.getStartDay()) {
2093
+ days[j].addTime(
2094
+ new OhTime(interval.getFrom(), MINUTES_MAX)
2095
+ );
2096
+ }
2097
+ else if(j == interval.getEndDay()) {
2098
+ days[j].addTime(
2099
+ new OhTime(0, interval.getTo())
2100
+ );
2101
+ }
2102
+ else {
2103
+ days[j].addTime(
2104
+ new OhTime(0, MINUTES_MAX)
2105
+ );
2106
+ }
2107
+ }
2108
+ }
2109
+ }
2110
+ catch(e) {
2111
+ console.warn(e);
2112
+ }
2113
+ }
2114
+ }
2115
+
2116
+ return [ monday0, sunday24, days ];
2117
+ };
2118
+
2119
+ /**
2120
+ * Changes days array to make sunday - monday night continuous if needed
2121
+ */
2122
+ OpeningHoursBuilder.prototype._nightMonSun = function(days, monday0, sunday24) {
2123
+ if(monday0 >= 0 && sunday24 >= 0 && monday0 < sunday24) {
2124
+ days[0].getTime().sort(this._sortOhTime);
2125
+ days[6].getTime().sort(this._sortOhTime);
2126
+
2127
+ //Change sunday interval
2128
+ days[6].getTime()[days[6].getTime().length-1] = new OhTime(sunday24, monday0);
2129
+
2130
+ //Remove monday interval
2131
+ days[0].getTime().shift();
2132
+ }
2133
+ return days;
2134
+ };
2135
+
2136
+ /**
2137
+ * Sort OhTime objects by start hour
2138
+ */
2139
+ OpeningHoursBuilder.prototype._sortOhTime = function(a, b) {
2140
+ return a.getStart() - b.getStart();
2141
+ };
2142
+
2143
+
2144
+
2145
+ /**
2146
+ * Class OpeningHoursParser, creates DateRange/Week/Day objects from opening_hours string
2147
+ * Based on a subpart of grammar defined at https://wiki.openstreetmap.org/wiki/Key:opening_hours/specification
2148
+ */
2149
+ var OpeningHoursParser = function() {
2150
+ //CONSTANTS
2151
+ this.RGX_RULE_MODIFIER = /^(open|closed|off)$/i;
2152
+ this.RGX_WEEK_KEY = /^week$/;
2153
+ this.RGX_WEEK_VAL = /^([01234]?[0-9]|5[0123])(\-([01234]?[0-9]|5[0123]))?(,([01234]?[0-9]|5[0123])(\-([01234]?[0-9]|5[0123]))?)*\:?$/;
2154
+ this.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))?\:?$/;
2155
+ this.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]))?\:?$/;
2156
+ this.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))$/;
2157
+ this.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)))*$/;
2158
+ this.RGX_HOLIDAY = /^(PH|SH|easter)$/;
2159
+ this.RGX_WD = /^(Mo|Tu|We|Th|Fr|Sa|Su)(\-(Mo|Tu|We|Th|Fr|Sa|Su))?$/;
2160
+ };
2161
+
2162
+ //OTHER METHODS
2163
+ /**
2164
+ * Parses the given opening_hours string
2165
+ * @param oh The opening_hours string
2166
+ * @return An array of date ranges
2167
+ */
2168
+ OpeningHoursParser.prototype.parse = function(oh) {
2169
+ var result = [];
2170
+
2171
+ //Separate each block
2172
+ var blocks = oh.split(';');
2173
+
2174
+ /*
2175
+ * Blocks parsing
2176
+ * Each block can be divided in three parts: wide range selector, small range selector, rule modifier.
2177
+ * The last two are simpler to parse, so we start to read rule modifier, then small range selector.
2178
+ * All the lasting tokens are part of wide range selector.
2179
+ */
2180
+
2181
+ var block, tokens, currentToken, ruleModifier, timeSelector, weekdaySelector, wideRangeSelector;
2182
+ var singleTime, from, to, times;
2183
+ var singleWeekday, wdStart, wdEnd, wdFrom, wdTo, holidays, weekdays;
2184
+ var monthSelector, weekSelector, weeks, singleWeek, weekFrom, weekTo, singleMonth, months, monthFrom, monthTo;
2185
+ var dateRanges, dateRange, drObj, foundDateRange, resDrId;
2186
+
2187
+ //Read each block
2188
+ for(var i=0, li=blocks.length; i < li; i++) {
2189
+ block = blocks[i].trim();
2190
+
2191
+ if(block.length == 0) { continue; } //Don't parse empty blocks
2192
+
2193
+ tokens = this._tokenize(block);
2194
+ currentToken = tokens.length - 1;
2195
+ ruleModifier = null;
2196
+ timeSelector = null;
2197
+ weekdaySelector = null;
2198
+ wideRangeSelector = null;
2199
+
2200
+ //console.log(tokens);
2201
+
2202
+ /*
2203
+ * Rule modifier (open, closed, off)
2204
+ */
2205
+ if(currentToken >= 0 && this._isRuleModifier(tokens[currentToken])) {
2206
+ //console.log("rule modifier",tokens[currentToken]);
2207
+ ruleModifier = tokens[currentToken].toLowerCase();
2208
+ currentToken--;
2209
+ }
2210
+
2211
+ /*
2212
+ * Small range selectors
2213
+ */
2214
+ from = null;
2215
+ to = null;
2216
+ times = []; //Time intervals in minutes
2217
+
2218
+ //Time selector
2219
+ if(currentToken >= 0 && this._isTime(tokens[currentToken])) {
2220
+ timeSelector = tokens[currentToken];
2221
+
2222
+ if(timeSelector == "24/7") {
2223
+ times.push({from: 0, to: 24*60});
2224
+ }
2225
+ else {
2226
+ //Divide each time interval
2227
+ timeSelector = timeSelector.split(',');
2228
+ for(var ts=0, tsl = timeSelector.length; ts < tsl; ts++) {
2229
+ //Separate start and end values
2230
+ singleTime = timeSelector[ts].split('-');
2231
+ from = this._asMinutes(singleTime[0]);
2232
+ if(singleTime.length > 1) {
2233
+ to = this._asMinutes(singleTime[1]);
2234
+ }
2235
+ else {
2236
+ to = from;
2237
+ }
2238
+ times.push({from: from, to: to});
2239
+ }
2240
+ }
2241
+
2242
+ currentToken--;
2243
+ }
2244
+
2245
+ holidays = [];
2246
+ weekdays = [];
2247
+
2248
+ //Weekday selector
2249
+ if(timeSelector == "24/7") {
2250
+ weekdays.push({from: 0, to: 6});
2251
+ }
2252
+ else if(currentToken >= 0 && this._isWeekday(tokens[currentToken])) {
2253
+ weekdaySelector = tokens[currentToken];
2254
+
2255
+ //Divide each weekday
2256
+ weekdaySelector = weekdaySelector.split(',');
2257
+
2258
+ for(var wds=0, wdsl = weekdaySelector.length; wds < wdsl; wds++) {
2259
+ singleWeekday = weekdaySelector[wds];
2260
+
2261
+ //Holiday
2262
+ if(this.RGX_HOLIDAY.test(singleWeekday)) {
2263
+ holidays.push(singleWeekday);
2264
+ }
2265
+ //Weekday interval
2266
+ else if(this.RGX_WD.test(singleWeekday)) {
2267
+ singleWeekday = singleWeekday.split('-');
2268
+ wdFrom = OSM_DAYS.indexOf(singleWeekday[0]);
2269
+ if(singleWeekday.length > 1) {
2270
+ wdTo = OSM_DAYS.indexOf(singleWeekday[1]);
2271
+ }
2272
+ else {
2273
+ wdTo = wdFrom;
2274
+ }
2275
+ weekdays.push({from: wdFrom, to: wdTo});
2276
+ }
2277
+ else {
2278
+ throw new Error("Invalid weekday interval: "+singleWeekday);
2279
+ }
2280
+ }
2281
+
2282
+ currentToken--;
2283
+ }
2284
+
2285
+ /*
2286
+ * Wide range selector
2287
+ */
2288
+ weeks = [];
2289
+ months = [];
2290
+
2291
+ if(currentToken >= 0) {
2292
+ wideRangeSelector = tokens[0];
2293
+ for(var ct=1; ct <= currentToken; ct++) {
2294
+ wideRangeSelector += " "+tokens[ct];
2295
+ }
2296
+
2297
+ if(wideRangeSelector.length > 0) {
2298
+ wideRangeSelector = wideRangeSelector.replace(/\:$/g, '').split('week'); //0 = Month or SH, 1 = weeks
2299
+
2300
+ //Month or SH
2301
+ monthSelector = wideRangeSelector[0].trim();
2302
+ if(monthSelector.length == 0) { monthSelector = null; }
2303
+
2304
+ //Weeks
2305
+ if(wideRangeSelector.length > 1) {
2306
+ weekSelector = wideRangeSelector[1].trim();
2307
+ if(weekSelector.length == 0) { weekSelector = null; }
2308
+ }
2309
+ else { weekSelector = null; }
2310
+
2311
+ if(monthSelector != null && weekSelector != null) {
2312
+ throw new Error("Unsupported simultaneous month and week selector");
2313
+ }
2314
+ else if(monthSelector != null) {
2315
+ monthSelector = monthSelector.split(',');
2316
+
2317
+ for(var ms=0, msl = monthSelector.length; ms < msl; ms++) {
2318
+ singleMonth = monthSelector[ms];
2319
+
2320
+ //School holidays
2321
+ if(singleMonth == "SH") {
2322
+ months.push({holiday: "SH"});
2323
+ }
2324
+ //Month intervals
2325
+ else if(this.RGX_MONTH.test(singleMonth)) {
2326
+ singleMonth = singleMonth.split('-');
2327
+ monthFrom = OSM_MONTHS.indexOf(singleMonth[0])+1;
2328
+ if(monthFrom < 1) {
2329
+ throw new Error("Invalid month: "+singleMonth[0]);
2330
+ }
2331
+
2332
+ if(singleMonth.length > 1) {
2333
+ monthTo = OSM_MONTHS.indexOf(singleMonth[1])+1;
2334
+ if(monthTo < 1) {
2335
+ throw new Error("Invalid month: "+singleMonth[1]);
2336
+ }
2337
+ }
2338
+ else {
2339
+ monthTo = null;
2340
+ }
2341
+ months.push({from: monthFrom, to: monthTo});
2342
+ }
2343
+ //Monthday intervals
2344
+ else if(this.RGX_MONTHDAY.test(singleMonth)) {
2345
+ singleMonth = singleMonth.replace(/\:/g, '').split('-');
2346
+
2347
+ //Read monthday start
2348
+ monthFrom = singleMonth[0].split(' ');
2349
+ monthFrom = { day: parseInt(monthFrom[1],10), month: OSM_MONTHS.indexOf(monthFrom[0])+1 };
2350
+ if(monthFrom.month < 1) {
2351
+ throw new Error("Invalid month: "+monthFrom[0]);
2352
+ }
2353
+
2354
+ if(singleMonth.length > 1) {
2355
+ monthTo = singleMonth[1].split(' ');
2356
+
2357
+ //Same month as start
2358
+ if(monthTo.length == 1) {
2359
+ monthTo = { day: parseInt(monthTo[0],10), month: monthFrom.month };
2360
+ }
2361
+ //Another month
2362
+ else {
2363
+ monthTo = { day: parseInt(monthTo[1],10), month: OSM_MONTHS.indexOf(monthTo[0])+1 };
2364
+ if(monthTo.month < 1) {
2365
+ throw new Error("Invalid month: "+monthTo[0]);
2366
+ }
2367
+ }
2368
+ }
2369
+ else {
2370
+ monthTo = null;
2371
+ }
2372
+ months.push({fromDay: monthFrom, toDay: monthTo});
2373
+ }
2374
+ //Unsupported
2375
+ else {
2376
+ throw new Error("Unsupported month selector: "+singleMonth);
2377
+ }
2378
+ }
2379
+ }
2380
+ else if(weekSelector != null) {
2381
+ //Divide each week interval
2382
+ weekSelector = weekSelector.split(',');
2383
+
2384
+ for(var ws=0, wsl = weekSelector.length; ws < wsl; ws++) {
2385
+ singleWeek = weekSelector[ws].split('-');
2386
+ weekFrom = parseInt(singleWeek[0],10);
2387
+ if(singleWeek.length > 1) {
2388
+ weekTo = parseInt(singleWeek[1],10);
2389
+ }
2390
+ else {
2391
+ weekTo = null;
2392
+ }
2393
+ weeks.push({from: weekFrom, to: weekTo});
2394
+ }
2395
+ }
2396
+ else {
2397
+ throw Error("Invalid date selector");
2398
+ }
2399
+ }
2400
+ }
2401
+
2402
+ //If no read token, throw error
2403
+ if(currentToken == tokens.length - 1) {
2404
+ throw Error("Unreadable string");
2405
+ }
2406
+
2407
+ // console.log("months",months);
2408
+ // console.log("weeks",weeks);
2409
+ // console.log("holidays",holidays);
2410
+ // console.log("weekdays",weekdays);
2411
+ // console.log("times",times);
2412
+ // console.log("rule",ruleModifier);
2413
+
2414
+ /*
2415
+ * Create date ranges
2416
+ */
2417
+ dateRanges = [];
2418
+
2419
+ //Month range
2420
+ if(months.length > 0) {
2421
+ for(var mId=0, ml = months.length; mId < ml; mId++) {
2422
+ singleMonth = months[mId];
2423
+
2424
+ if(singleMonth.holiday != undefined) {
2425
+ dateRanges.push(new WideInterval().holiday(singleMonth.holiday));
2426
+ }
2427
+ else if(singleMonth.fromDay != undefined) {
2428
+ if(singleMonth.toDay != null) {
2429
+ dateRange = new WideInterval().day(singleMonth.fromDay.day, singleMonth.fromDay.month, singleMonth.toDay.day, singleMonth.toDay.month);
2430
+ }
2431
+ else {
2432
+ dateRange = new WideInterval().day(singleMonth.fromDay.day, singleMonth.fromDay.month);
2433
+ }
2434
+ dateRanges.push(dateRange);
2435
+ }
2436
+ else {
2437
+ if(singleMonth.to != null) {
2438
+ dateRange = new WideInterval().month(singleMonth.from, singleMonth.to);
2439
+ }
2440
+ else {
2441
+ dateRange = new WideInterval().month(singleMonth.from);
2442
+ }
2443
+ dateRanges.push(dateRange);
2444
+ }
2445
+ }
2446
+ }
2447
+ //Week range
2448
+ else if(weeks.length > 0) {
2449
+ for(var wId=0, wl = weeks.length; wId < wl; wId++) {
2450
+ if(weeks[wId].to != null) {
2451
+ dateRange = new WideInterval().week(weeks[wId].from, weeks[wId].to);
2452
+ }
2453
+ else {
2454
+ dateRange = new WideInterval().week(weeks[wId].from);
2455
+ }
2456
+ dateRanges.push(dateRange);
2457
+ }
2458
+ }
2459
+ //Holiday range
2460
+ else if(holidays.length > 0) {
2461
+ for(var hId=0, hl = holidays.length; hId < hl; hId++) {
2462
+ dateRanges.push(new WideInterval().holiday(holidays[hId]));
2463
+ if(holidays[hId] == "PH" && weekdays.length > 0 && months.length == 0 && weeks.length == 0) {
2464
+ dateRanges.push(new WideInterval().always());
2465
+ }
2466
+ }
2467
+ }
2468
+ //Full year range
2469
+ else {
2470
+ dateRanges.push(new WideInterval().always());
2471
+ }
2472
+
2473
+ //Case of no weekday defined = all week
2474
+ if(weekdays.length == 0) {
2475
+ if(holidays.length == 0 || (holidays.length == 1 && holidays[0] == "SH")) {
2476
+ weekdays.push({from: 0, to: OSM_DAYS.length -1 });
2477
+ }
2478
+ else {
2479
+ weekdays.push({from: 0, to: 0 });
2480
+ }
2481
+ }
2482
+
2483
+ //Case of no time defined = all day
2484
+ if(times.length == 0) {
2485
+ times.push({from: 0, to: 24*60});
2486
+ }
2487
+
2488
+ /*
2489
+ * Create date range objects
2490
+ */
2491
+ for(var drId = 0, drl=dateRanges.length; drId < drl; drId++) {
2492
+ /*
2493
+ * Find an already defined date range or create new one
2494
+ */
2495
+ foundDateRange = false;
2496
+ resDrId=0;
2497
+ while(resDrId < result.length && !foundDateRange) {
2498
+ if(result[resDrId].getInterval().equals(dateRanges[drId])) {
2499
+ foundDateRange = true;
2500
+ }
2501
+ else {
2502
+ resDrId++;
2503
+ }
2504
+ }
2505
+
2506
+ if(foundDateRange) {
2507
+ drObj = result[resDrId];
2508
+ }
2509
+ else {
2510
+ drObj = new DateRange(dateRanges[drId]);
2511
+
2512
+ //Find general date range that may be refined by this one
2513
+ var general = -1;
2514
+ for(resDrId=0; resDrId < result.length; resDrId++) {
2515
+ if(result[resDrId].isGeneralFor(new DateRange(dateRanges[drId]))) {
2516
+ general = resDrId;
2517
+ }
2518
+ }
2519
+
2520
+ //Copy general date range intervals
2521
+ if(general >= 0 && drObj.definesTypicalWeek()) {
2522
+ drObj.getTypical().copyIntervals(result[general].getTypical().getIntervals());
2523
+ }
2524
+
2525
+ result.push(drObj);
2526
+ }
2527
+
2528
+ /*
2529
+ * Add time intervals
2530
+ */
2531
+ //For each weekday
2532
+ for(var wdId=0, wdl=weekdays.length; wdId < wdl; wdId++) {
2533
+ //Remove overlapping days
2534
+ if(weekdays[wdId].from <= weekdays[wdId].to) {
2535
+ for(var wdRm=weekdays[wdId].from; wdRm <= weekdays[wdId].to; wdRm++) {
2536
+ if(drObj.definesTypicalWeek()) {
2537
+ drObj.getTypical().removeIntervalsDuringDay(wdRm);
2538
+ }
2539
+ else {
2540
+ drObj.getTypical().clearIntervals();
2541
+ }
2542
+ }
2543
+ }
2544
+ else {
2545
+ for(var wdRm=weekdays[wdId].from; wdRm <= 6; wdRm++) {
2546
+ if(drObj.definesTypicalWeek()) {
2547
+ drObj.getTypical().removeIntervalsDuringDay(wdRm);
2548
+ }
2549
+ else {
2550
+ drObj.getTypical().clearIntervals();
2551
+ }
2552
+ }
2553
+ for(var wdRm=0; wdRm <= weekdays[wdId].to; wdRm++) {
2554
+ if(drObj.definesTypicalWeek()) {
2555
+ drObj.getTypical().removeIntervalsDuringDay(wdRm);
2556
+ }
2557
+ else {
2558
+ drObj.getTypical().clearIntervals();
2559
+ }
2560
+ }
2561
+ }
2562
+
2563
+ //For each time interval
2564
+ for(var tId=0, tl=times.length; tId < tl; tId++) {
2565
+ if(ruleModifier == "closed" || ruleModifier == "off") {
2566
+ this._removeInterval(drObj.getTypical(), weekdays[wdId], times[tId]);
2567
+ }
2568
+ else {
2569
+ this._addInterval(drObj.getTypical(), weekdays[wdId], times[tId]);
2570
+ }
2571
+ }
2572
+ }
2573
+ }
2574
+ }
2575
+
2576
+ return result;
2577
+ };
2578
+
2579
+ /**
2580
+ * Remove intervals from given typical day/week
2581
+ * @param typical The typical day or week
2582
+ * @param weekdays The concerned weekdays
2583
+ * @param times The concerned times
2584
+ */
2585
+ OpeningHoursParser.prototype._removeInterval = function(typical, weekdays, times) {
2586
+ if(weekdays.from <= weekdays.to) {
2587
+ for(var wd=weekdays.from; wd <= weekdays.to; wd++) {
2588
+ this._removeIntervalWd(typical, times, wd);
2589
+ }
2590
+ }
2591
+ else {
2592
+ for(var wd=weekdays.from; wd <= 6; wd++) {
2593
+ this._removeIntervalWd(typical, times, wd);
2594
+ }
2595
+ for(var wd=0; wd <= weekdays.to; wd++) {
2596
+ this._removeIntervalWd(typical, times, wd);
2597
+ }
2598
+ }
2599
+ };
2600
+
2601
+ /**
2602
+ * Remove intervals from given typical day/week for a given weekday
2603
+ * @param typical The typical day or week
2604
+ * @param times The concerned times
2605
+ * @param wd The concerned weekday
2606
+ */
2607
+ OpeningHoursParser.prototype._removeIntervalWd = function(typical, times, wd) {
2608
+ //Interval during day
2609
+ if(times.to >= times.from) {
2610
+ typical.removeInterval(
2611
+ new Interval(wd, wd, times.from, times.to)
2612
+ );
2613
+ }
2614
+ //Interval during night
2615
+ else {
2616
+ //Everyday except sunday
2617
+ if(wd < 6) {
2618
+ typical.removeInterval(
2619
+ new Interval(wd, wd+1, times.from, times.to)
2620
+ );
2621
+ }
2622
+ //Sunday
2623
+ else {
2624
+ typical.removeInterval(
2625
+ new Interval(wd, wd, times.from, 24*60)
2626
+ );
2627
+ typical.removeInterval(
2628
+ new Interval(0, 0, 0, times.to)
2629
+ );
2630
+ }
2631
+ }
2632
+ };
2633
+
2634
+ /**
2635
+ * Adds intervals from given typical day/week
2636
+ * @param typical The typical day or week
2637
+ * @param weekdays The concerned weekdays
2638
+ * @param times The concerned times
2639
+ */
2640
+ OpeningHoursParser.prototype._addInterval = function(typical, weekdays, times) {
2641
+ //Check added interval are OK for days
2642
+ if(typical instanceof Day) {
2643
+ if(weekdays.from != 0 || (weekdays.to != 0 && times.from <= times.to)) {
2644
+ weekdays = Object.assign({}, weekdays);
2645
+ weekdays.from = 0;
2646
+ weekdays.to = (times.from <= times.to) ? 0 : 1;
2647
+ }
2648
+ }
2649
+
2650
+ if(weekdays.from <= weekdays.to) {
2651
+ for(var wd=weekdays.from; wd <= weekdays.to; wd++) {
2652
+ this._addIntervalWd(typical, times, wd);
2653
+ }
2654
+ }
2655
+ else {
2656
+ for(var wd=weekdays.from; wd <= 6; wd++) {
2657
+ this._addIntervalWd(typical, times, wd);
2658
+ }
2659
+ for(var wd=0; wd <= weekdays.to; wd++) {
2660
+ this._addIntervalWd(typical, times, wd);
2661
+ }
2662
+ }
2663
+ };
2664
+
2665
+ /**
2666
+ * Adds intervals from given typical day/week for a given weekday
2667
+ * @param typical The typical day or week
2668
+ * @param times The concerned times
2669
+ * @param wd The concerned weekday
2670
+ */
2671
+ OpeningHoursParser.prototype._addIntervalWd = function(typical, times, wd) {
2672
+ //Interval during day
2673
+ if(times.to >= times.from) {
2674
+ typical.addInterval(
2675
+ new Interval(wd, wd, times.from, times.to)
2676
+ );
2677
+ }
2678
+ //Interval during night
2679
+ else {
2680
+ //Everyday except sunday
2681
+ if(wd < 6) {
2682
+ typical.addInterval(
2683
+ new Interval(wd, wd+1, times.from, times.to)
2684
+ );
2685
+ }
2686
+ //Sunday
2687
+ else {
2688
+ typical.addInterval(
2689
+ new Interval(wd, wd, times.from, 24*60)
2690
+ );
2691
+ typical.addInterval(
2692
+ new Interval(0, 0, 0, times.to)
2693
+ );
2694
+ }
2695
+ }
2696
+ };
2697
+
2698
+ /**
2699
+ * Converts a time string "12:45" into minutes integer
2700
+ * @param time The time string
2701
+ * @return The amount of minutes since midnight
2702
+ */
2703
+ OpeningHoursParser.prototype._asMinutes = function(time) {
2704
+ var values = time.split(':');
2705
+ return parseInt(values[0],10) * 60 + parseInt(values[1],10);
2706
+ };
2707
+
2708
+ /**
2709
+ * Is the given token a weekday selector ?
2710
+ */
2711
+ OpeningHoursParser.prototype._isWeekday = function(token) {
2712
+ return this.RGX_WEEKDAY.test(token);
2713
+ };
2714
+
2715
+ /**
2716
+ * Is the given token a time selector ?
2717
+ */
2718
+ OpeningHoursParser.prototype._isTime = function(token) {
2719
+ return this.RGX_TIME.test(token);
2720
+ };
2721
+
2722
+ /**
2723
+ * Is the given token a rule modifier ?
2724
+ */
2725
+ OpeningHoursParser.prototype._isRuleModifier = function(token) {
2726
+ return this.RGX_RULE_MODIFIER.test(token);
2727
+ };
2728
+
2729
+ /**
2730
+ * Create tokens for a given block
2731
+ */
2732
+ OpeningHoursParser.prototype._tokenize = function(block) {
2733
+ var result = block.trim().split(' ');
2734
+ var position = result.indexOf("");
2735
+ while( ~position ) {
2736
+ result.splice(position, 1);
2737
+ position = result.indexOf("");
2738
+ }
2739
+ return result;
2740
+ };
2741
+
2742
+ OpeningHoursParser.prototype._printIntervals = function(from, intervals) {
2743
+ console.log("From: "+from);
2744
+ if(intervals.length > 0) {
2745
+ console.log("-------------------------");
2746
+ for(var i=0; i < intervals.length; i++) {
2747
+ if(intervals[i] == undefined) {
2748
+ console.log(i+": "+undefined);
2749
+ }
2750
+ else {
2751
+ console.log(i+": "+intervals[i].getStartDay()+", "+intervals[i].getEndDay()+", "+intervals[i].getFrom()+", "+intervals[i].getTo());
2752
+ }
2753
+ }
2754
+ console.log("-------------------------");
2755
+ }
2756
+ else {
2757
+ console.log("Empty intervals");
2758
+ }
2759
+ };
2760
+
2761
+
2762
+ /**
2763
+ * Check compatibility of opening_hours string with YoHours
2764
+ */
2765
+ var YoHoursChecker = function() {
2766
+ //ATTRIBUTES
2767
+ /** The OpeningHoursParser **/
2768
+ this._parser = new OpeningHoursParser();
2769
+ };
2770
+
2771
+ //OTHER METHODS
2772
+ /**
2773
+ * Check if the opening_hours is readable by YoHours
2774
+ * @param oh The opening_hours string
2775
+ * @return True if YoHours can read it and display it
2776
+ */
2777
+ YoHoursChecker.prototype.canRead = function(oh) {
2778
+ var result = false;
2779
+
2780
+ try {
2781
+ var parsed = this._parser.parse(oh);
2782
+ if(parsed != null) {
2783
+ result = true;
2784
+ }
2785
+ }
2786
+ catch(e) {;}
2787
+
2788
+ return result;
2789
+ };
2790
+
2791
+ export { OpeningHoursBuilder, OpeningHoursParser, YoHoursChecker };